blob: 12c001853c25000b4f1b4ea6ea3f7d09f6811440 [file] [log] [blame]
// 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.
#import <Cocoa/Cocoa.h>
#include <stddef.h>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/values.h"
#include "chrome/browser/extensions/api/commands/command_service.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/ui/browser_window.h"
#import "chrome/browser/ui/cocoa/cocoa_profile_test.h"
#import "chrome/browser/ui/cocoa/extensions/extension_installed_bubble_controller.h"
#import "chrome/browser/ui/cocoa/info_bubble_window.h"
#include "chrome/browser/ui/extensions/extension_installed_bubble.h"
#include "chrome/browser/ui/location_bar/location_bar.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/testing_profile.h"
#include "components/crx_file/id_util.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/value_builder.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#include "third_party/ocmock/gtest_support.h"
#include "ui/gfx/codec/png_codec.h"
using extensions::Extension;
using extensions::DictionaryBuilder;
class ExtensionInstalledBubbleControllerTest : public CocoaProfileTest {
protected:
ExtensionInstalledBubbleControllerTest() {}
~ExtensionInstalledBubbleControllerTest() override {}
enum ExtensionType {
BROWSER_ACTION,
PAGE_ACTION,
APP,
};
void SetUp() override {
CocoaProfileTest::SetUp();
ASSERT_TRUE(browser());
window_ = browser()->window()->GetNativeWindow();
icon_ = LoadTestIcon();
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
extensionService_ = static_cast<extensions::TestExtensionSystem*>(
extensions::ExtensionSystem::Get(profile()))->CreateExtensionService(
&command_line, base::FilePath(), false);
}
// Adds a WebContents to the tab strip.
void AddWebContents() {
content::SiteInstance* instance = content::SiteInstance::Create(profile());
content::WebContents* web_contents = content::WebContents::Create(
content::WebContents::CreateParams(profile(), instance));
browser()->tab_strip_model()->AppendWebContents(web_contents, true);
}
// Create a simple extension of the given |type| and manifest |location|, and
// optionally with an associated keybinding.
void CreateExtension(ExtensionType type,
bool has_keybinding,
extensions::Manifest::Location location) {
DictionaryBuilder manifest;
manifest.Set("version", "1.0");
manifest.Set("name", "extension");
manifest.Set("manifest_version", 2);
switch (type) {
case PAGE_ACTION:
manifest.Set("page_action", DictionaryBuilder().Build());
break;
case BROWSER_ACTION:
manifest.Set("browser_action", DictionaryBuilder().Build());
break;
case APP:
manifest.Set(
"app",
DictionaryBuilder()
.Set("launch", DictionaryBuilder()
.Set("web_url", "http://www.example.com")
.Build())
.Build());
break;
}
if (has_keybinding) {
DictionaryBuilder command;
command.Set(type == PAGE_ACTION ? "_execute_page_action"
: "_execute_browser_action",
DictionaryBuilder()
.Set("suggested_key", DictionaryBuilder()
.Set("mac", "MacCtrl+Shift+E")
.Set("default", "Ctrl+Shift+E")
.Build())
.Build());
manifest.Set("commands", command.Build());
}
extension_ = extensions::ExtensionBuilder()
.SetManifest(manifest.Build())
.SetID(crx_file::id_util::GenerateId("foo"))
.SetLocation(location)
.Build();
extensionService_->AddExtension(extension_.get());
if (has_keybinding) {
// Slight hack: manually notify the command service of the extension since
// it doesn't go through the normal installation flow.
extensions::CommandService::Get(profile())->UpdateKeybindingsForTest(
extension_.get());
}
}
void CreateExtension(ExtensionType type, bool has_keybinding) {
CreateExtension(type, has_keybinding, extensions::Manifest::INTERNAL);
}
// Create and return an ExtensionInstalledBubbleController and instruct it to
// show itself.
ExtensionInstalledBubbleController* CreateController() {
extensionBubble_.reset(
new ExtensionInstalledBubble(extension_.get(), browser(), icon_));
extensionBubble_->Initialize();
ExtensionInstalledBubbleController* controller =
[[ExtensionInstalledBubbleController alloc]
initWithParentWindow:window_
extensionBubble:extensionBubble_.get()];
// Bring up the window and disable close animation.
[controller showWindow:nil];
NSWindow* bubbleWindow = [controller window];
CHECK([bubbleWindow isKindOfClass:[InfoBubbleWindow class]]);
[static_cast<InfoBubbleWindow*>(bubbleWindow)
setAllowedAnimations:info_bubble::kAnimateNone];
return controller;
}
NSWindow* window() { return window_; }
private:
// Load test icon from extension test directory.
SkBitmap LoadTestIcon() {
base::FilePath path;
PathService::Get(chrome::DIR_TEST_DATA, &path);
path = path.AppendASCII("extensions").AppendASCII("icon1.png");
std::string file_contents;
base::ReadFileToString(path, &file_contents);
const unsigned char* data =
reinterpret_cast<const unsigned char*>(file_contents.data());
SkBitmap bitmap;
gfx::PNGCodec::Decode(data, file_contents.length(), &bitmap);
return bitmap;
}
// Required to initialize the extension installed bubble.
NSWindow* window_; // weak, owned by CocoaProfileTest.
// The associated ExtensionService, owned by the ExtensionSystem.
ExtensionService* extensionService_;
// Skeleton extension to be tested; reinitialized for each test.
scoped_refptr<Extension> extension_;
// The bubble that tests are run on.
scoped_ptr<ExtensionInstalledBubble> extensionBubble_;
// The icon_ to be loaded into the bubble window.
SkBitmap icon_;
DISALLOW_COPY_AND_ASSIGN(ExtensionInstalledBubbleControllerTest);
};
// We don't want to just test the bounds of these frames, because that results
// in a change detector test (and just duplicates the logic in the class).
// Instead, we do a few sanity checks.
void SanityCheckFrames(NSRect frames[], size_t size) {
for (size_t i = 0; i < size; ++i) {
// Check 1: Non-hidden views should have a non-empty frame.
EXPECT_FALSE(NSIsEmptyRect(frames[i])) <<
"Frame at index " << i << " is empty";
// Check 2: No frames should overlap.
for (size_t j = 0; j < i; ++j) {
EXPECT_FALSE(NSIntersectsRect(frames[i], frames[j])) <<
"Frame at index " << i << " intersects frame at index " << j;
}
}
}
// Test the basic layout of the bubble for an extension that is from the store.
TEST_F(ExtensionInstalledBubbleControllerTest,
BubbleLayoutFromStoreNoKeybinding) {
CreateExtension(BROWSER_ACTION, false);
ExtensionInstalledBubbleController* controller = CreateController();
ASSERT_TRUE(controller);
// The extension bubble should have the "how to use", "how to manage", and
// "sign in promo" areas. Since it doesn't have an associated keybinding, it
// shouldn't have the "manage shortcut" view.
EXPECT_FALSE([[controller howToUse] isHidden]);
EXPECT_FALSE([[controller howToManage] isHidden]);
EXPECT_FALSE([[controller promoContainer] isHidden]);
EXPECT_TRUE([[controller manageShortcutLink] isHidden]);
NSRect headingFrame = [[controller heading] frame];
NSRect closeFrame = [[controller closeButton] frame];
NSRect howToUseFrame = [[controller howToUse] frame];
NSRect howToManageFrame = [[controller howToManage] frame];
NSRect syncPromoFrame = [[controller promoContainer] frame];
NSRect iconFrame = [[controller iconImage] frame];
NSRect frames[] = {headingFrame, closeFrame, howToUseFrame, howToManageFrame,
syncPromoFrame, iconFrame};
SanityCheckFrames(frames, arraysize(frames));
// Check the overall layout of the bubble; it should be:
// |------| | Heading |
// | icon | | How to Use |
// |------| | How to Manage |
// |-------------------------|
// | Sync Promo |
// |-------------------------|
EXPECT_GT(NSMinY(headingFrame), NSMinY(howToUseFrame));
EXPECT_GT(NSMinY(howToUseFrame), NSMinY(howToManageFrame));
EXPECT_GT(NSMinY(howToManageFrame), NSMinY(syncPromoFrame));
EXPECT_GT(NSMinY(iconFrame), NSMinY(syncPromoFrame));
EXPECT_GT(NSMinY(iconFrame), 0);
EXPECT_EQ(NSMinY(syncPromoFrame), 0);
[controller close];
}
// Test the layout of a bubble for an extension that is from the store with an
// associated keybinding.
TEST_F(ExtensionInstalledBubbleControllerTest,
BubbleLayoutFromStoreWithKeybinding) {
CreateExtension(BROWSER_ACTION, true);
ExtensionInstalledBubbleController* controller = CreateController();
ASSERT_TRUE(controller);
// Since the extension has a keybinding, the "how to manage" section is
// hidden. The other fields are present.
EXPECT_FALSE([[controller howToUse] isHidden]);
EXPECT_TRUE([[controller howToManage] isHidden]);
EXPECT_FALSE([[controller manageShortcutLink] isHidden]);
EXPECT_FALSE([[controller promoContainer] isHidden]);
NSRect headingFrame = [[controller heading] frame];
NSRect closeFrame = [[controller closeButton] frame];
NSRect howToUseFrame = [[controller howToUse] frame];
NSRect manageShortcutFrame = [[controller manageShortcutLink] frame];
NSRect syncPromoFrame = [[controller promoContainer] frame];
NSRect iconFrame = [[controller iconImage] frame];
NSRect frames[] = {headingFrame, closeFrame, howToUseFrame,
manageShortcutFrame, syncPromoFrame, iconFrame};
SanityCheckFrames(frames, arraysize(frames));
// Layout should be:
// |------| | Heading |
// | icon | | How to Use |
// |------| | Manage shortcut |
// |--------------------------|
// | Sync Promo |
// |--------------------------|
EXPECT_GT(NSMinY(headingFrame), NSMinY(howToUseFrame));
EXPECT_GT(NSMinY(howToUseFrame), NSMinY(manageShortcutFrame));
EXPECT_GT(NSMinY(manageShortcutFrame), NSMinY(syncPromoFrame));
EXPECT_GT(NSMinY(iconFrame), NSMinY(syncPromoFrame));
EXPECT_GT(NSMinY(iconFrame), 0);
EXPECT_EQ(NSMinY(syncPromoFrame), 0);
[controller close];
}
// Test the layout of a bubble for an unpacked extension (which is not syncable)
// and verify that the page action preview is enabled.
TEST_F(ExtensionInstalledBubbleControllerTest, BubbleLayoutPageActionUnpacked) {
// Page actions need a web contents (for the location bar to not break).
AddWebContents();
LocationBarTesting* locationBar =
browser()->window()->GetLocationBar()->GetLocationBarForTesting();
// To start, there should be no visible page actions.
EXPECT_EQ(0, locationBar->PageActionVisibleCount());
CreateExtension(PAGE_ACTION, true, extensions::Manifest::UNPACKED);
ExtensionInstalledBubbleController* controller = CreateController();
ASSERT_TRUE(controller);
// The extension has a keybinding (so the "how to manage" view is hidden) and
// is an unpacked extension (so the "sign in promo" view is also hidden).
EXPECT_FALSE([[controller howToUse] isHidden]);
EXPECT_TRUE([[controller howToManage] isHidden]);
EXPECT_TRUE([[controller promoContainer] isHidden]);
EXPECT_FALSE([[controller manageShortcutLink] isHidden]);
NSRect headingFrame = [[controller heading] frame];
NSRect closeFrame = [[controller closeButton] frame];
NSRect howToUseFrame = [[controller howToUse] frame];
NSRect howToManageFrame = [[controller howToManage] frame];
NSRect iconFrame = [[controller iconImage] frame];
NSRect frames[] = {headingFrame, closeFrame, howToUseFrame, howToManageFrame,
iconFrame};
SanityCheckFrames(frames, arraysize(frames));
// Layout should be:
// |------| | Heading |
// | icon | | How to Use |
// |------| | Manage shortcut |
EXPECT_FALSE(NSIntersectsRect(howToUseFrame, howToManageFrame));
EXPECT_GT(NSMinY(headingFrame), NSMinY(howToManageFrame));
EXPECT_GT(NSMinY(howToUseFrame), NSMinY(howToManageFrame));
EXPECT_GT(NSMinY(iconFrame), 0);
// The page action preview should be visible.
EXPECT_TRUE([controller pageActionPreviewShowing]);
EXPECT_EQ(1, locationBar->PageActionVisibleCount());
[controller close];
// The page action preview should have ended.
EXPECT_EQ(0, locationBar->PageActionVisibleCount());
}
TEST_F(ExtensionInstalledBubbleControllerTest, ParentClose) {
CreateExtension(BROWSER_ACTION, false);
ExtensionInstalledBubbleController* controller = CreateController();
EXPECT_TRUE(controller);
NSWindow* bubbleWindow = [controller window];
// Observe whether the bubble window closes.
NSString* notification = NSWindowWillCloseNotification;
id observer = [OCMockObject observerMock];
[[observer expect] notificationWithName:notification object:bubbleWindow];
[[NSNotificationCenter defaultCenter]
addMockObserver:observer name:notification object:bubbleWindow];
// The bubble window goes from visible to not-visible.
EXPECT_TRUE([bubbleWindow isVisible]);
[window() close];
EXPECT_FALSE([bubbleWindow isVisible]);
[[NSNotificationCenter defaultCenter] removeObserver:observer];
// Check that the appropriate notification was received.
EXPECT_OCMOCK_VERIFY(observer);
}
TEST_F(ExtensionInstalledBubbleControllerTest, AppTest) {
CreateExtension(APP, false);
ExtensionInstalledBubbleController* controller = CreateController();
EXPECT_TRUE(controller);
int height = NSHeight([[controller window] frame]);
// Make sure there is always enough room for the icon and margin.
int minHeight = extension_installed_bubble::kIconSize +
(2 * extension_installed_bubble::kOuterVerticalMargin);
EXPECT_GT(height, minHeight);
// Make sure the "show me" link is visible.
EXPECT_FALSE([[controller appInstalledShortcutLink] isHidden]);
[controller close];
}