1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/command_line.h"
6#include "base/path_service.h"
7#include "base/strings/utf_string_conversions.h"
8#include "chrome/app/chrome_command_ids.h"
9#include "chrome/browser/chrome_notification_types.h"
10#include "chrome/browser/extensions/extension_browsertest.h"
11#include "chrome/browser/extensions/extension_test_message_listener.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/renderer_context_menu/render_view_context_menu.h"
14#include "chrome/browser/ui/browser.h"
15#include "chrome/browser/ui/panels/native_panel.h"
16#include "chrome/browser/ui/panels/panel.h"
17#include "chrome/browser/ui/panels/panel_constants.h"
18#include "chrome/browser/ui/panels/panel_manager.h"
19#include "chrome/browser/web_applications/web_app.h"
20#include "chrome/common/chrome_paths.h"
21#include "chrome/common/chrome_switches.h"
22#include "chrome/test/base/ui_test_utils.h"
23#include "content/public/browser/web_contents.h"
24#include "content/public/test/test_utils.h"
25#include "extensions/common/extension.h"
26#include "testing/gtest/include/gtest/gtest.h"
27
28#if defined(OS_MACOSX)
29#include "base/mac/scoped_nsautorelease_pool.h"
30#endif
31
32using extensions::Extension;
33
34class PanelExtensionBrowserTest : public ExtensionBrowserTest {
35 protected:
36  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
37    ExtensionBrowserTest::SetUpCommandLine(command_line);
38    command_line->AppendSwitch(switches::kEnablePanels);
39    PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_);
40    test_data_dir_ = test_data_dir_.AppendASCII("panels");
41  }
42
43  Panel* CreatePanelFromExtension(const Extension* extension) const {
44#if defined(OS_MACOSX)
45    // Opening panels on a Mac causes NSWindowController of the Panel window
46    // to be autoreleased. We need a pool drained after it's done so the test
47    // can close correctly. The NSWindowController of the Panel window controls
48    // lifetime of the Panel object so we want to release it as soon as
49    // possible. In real Chrome, this is done by message pump.
50    // On non-Mac platform, this is an empty class.
51    base::mac::ScopedNSAutoreleasePool autorelease_pool;
52#endif
53
54    Panel* panel = PanelManager::GetInstance()->CreatePanel(
55        web_app::GenerateApplicationNameFromExtensionId(extension->id()),
56        browser()->profile(),
57        GURL(),
58        gfx::Rect(),
59        PanelManager::CREATE_AS_DETACHED);
60    panel->ShowInactive();
61    return panel;
62  }
63
64  void WaitForAppIconAvailable(Panel* panel) const {
65    content::WindowedNotificationObserver signal(
66        chrome::NOTIFICATION_PANEL_APP_ICON_LOADED,
67        content::Source<Panel>(panel));
68    if (!panel->app_icon().IsEmpty())
69      return;
70    signal.Wait();
71    EXPECT_FALSE(panel->app_icon().IsEmpty());
72  }
73
74  static NativePanelTesting* CreateNativePanelTesting(Panel* panel) {
75    return panel->native_panel()->CreateNativePanelTesting();
76  }
77};
78
79// TODO(jschuh): Hanging plugin tests. crbug.com/244653
80#if !defined(OS_WIN) && !defined(ARCH_CPU_X86_64)
81IN_PROC_BROWSER_TEST_F(PanelExtensionBrowserTest, PanelAppIcon) {
82  const Extension* extension =
83      LoadExtension(test_data_dir_.AppendASCII("test_extension"));
84  Panel* panel = CreatePanelFromExtension(extension);
85
86  // Wait for the app icon gets fully loaded.
87  WaitForAppIconAvailable(panel);
88
89  // First verify on the panel level.
90  gfx::ImageSkia app_icon = panel->app_icon().AsImageSkia();
91  EXPECT_EQ(panel::kPanelAppIconSize, app_icon.width());
92  EXPECT_EQ(panel::kPanelAppIconSize, app_icon.height());
93
94  // Then verify on the native panel level.
95#if !defined(OS_WIN) || !defined(USE_AURA)
96  scoped_ptr<NativePanelTesting> native_panel_testing(
97      CreateNativePanelTesting(panel));
98  EXPECT_TRUE(native_panel_testing->VerifyAppIcon());
99#endif
100
101  panel->Close();
102}
103#endif
104
105IN_PROC_BROWSER_TEST_F(PanelExtensionBrowserTest,
106                       ClosePanelBeforeIconLoadingCompleted) {
107  const Extension* extension =
108      LoadExtension(test_data_dir_.AppendASCII("test_extension"));
109  Panel* panel = CreatePanelFromExtension(extension);
110
111  // Close tha panel without waiting for the app icon loaded.
112  panel->Close();
113}
114
115// Non-abstract RenderViewContextMenu class for testing context menus in Panels.
116class PanelContextMenu : public RenderViewContextMenu {
117 public:
118  PanelContextMenu(content::RenderFrameHost* render_frame_host,
119                   const content::ContextMenuParams& params)
120      : RenderViewContextMenu(render_frame_host, params) {}
121
122  bool HasCommandWithId(int command_id) {
123    return menu_model_.GetIndexOfCommandId(command_id) != -1;
124  }
125
126 protected:
127  // RenderViewContextMenu implementation.
128  virtual bool GetAcceleratorForCommandId(
129      int command_id,
130      ui::Accelerator* accelerator) OVERRIDE {
131    return false;
132  }
133  virtual void PlatformInit() OVERRIDE {}
134  virtual void PlatformCancel() OVERRIDE {}
135};
136
137IN_PROC_BROWSER_TEST_F(PanelExtensionBrowserTest, BasicContextMenu) {
138  ExtensionTestMessageListener listener("panel loaded", false);
139  LoadExtension(test_data_dir_.AppendASCII("basic"));
140  ASSERT_TRUE(listener.WaitUntilSatisfied());
141
142  // There should only be one panel.
143  PanelManager* panel_manager = PanelManager::GetInstance();
144  EXPECT_EQ(1, panel_manager->num_panels());
145  Panel* panel = panel_manager->panels().front();
146  content::WebContents* web_contents = panel->GetWebContents();
147  ASSERT_TRUE(web_contents);
148
149  // Verify basic menu contents. The basic extension does not add any
150  // context menu items so the panel's menu should include only the
151  // developer tools.
152  {
153    content::ContextMenuParams params;
154    params.page_url = web_contents->GetURL();
155    // Ensure context menu isn't swallowed by WebContentsDelegate (the panel).
156    EXPECT_FALSE(web_contents->GetDelegate()->HandleContextMenu(params));
157
158    scoped_ptr<PanelContextMenu> menu(
159        new PanelContextMenu(web_contents->GetMainFrame(), params));
160    menu->Init();
161
162    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_INSPECTELEMENT));
163    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_UNDO));
164    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_PASTE));
165    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_COPY));
166    EXPECT_FALSE(menu->HasCommandWithId(IDC_BACK));
167    EXPECT_FALSE(menu->HasCommandWithId(IDC_SAVE_PAGE));
168    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_COPYLINKLOCATION));
169  }
170
171  // Verify expected menu contents for editable item.
172  {
173    content::ContextMenuParams params;
174    params.is_editable = true;
175    params.page_url = web_contents->GetURL();
176    // Ensure context menu isn't swallowed by WebContentsDelegate (the panel).
177    EXPECT_FALSE(web_contents->GetDelegate()->HandleContextMenu(params));
178
179    scoped_ptr<PanelContextMenu> menu(
180        new PanelContextMenu(web_contents->GetMainFrame(), params));
181    menu->Init();
182
183    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_INSPECTELEMENT));
184    EXPECT_TRUE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_UNDO));
185    EXPECT_TRUE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_PASTE));
186    EXPECT_TRUE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_COPY));
187    EXPECT_FALSE(menu->HasCommandWithId(IDC_BACK));
188    EXPECT_FALSE(menu->HasCommandWithId(IDC_SAVE_PAGE));
189    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_COPYLINKLOCATION));
190  }
191
192  // Verify expected menu contents for text selection.
193  {
194    content::ContextMenuParams params;
195    params.page_url = web_contents->GetURL();
196    params.selection_text = base::ASCIIToUTF16("Select me");
197    // Ensure context menu isn't swallowed by WebContentsDelegate (the panel).
198    EXPECT_FALSE(web_contents->GetDelegate()->HandleContextMenu(params));
199
200    scoped_ptr<PanelContextMenu> menu(
201        new PanelContextMenu(web_contents->GetMainFrame(), params));
202    menu->Init();
203
204    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_INSPECTELEMENT));
205    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_UNDO));
206    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_PASTE));
207    EXPECT_TRUE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_COPY));
208    EXPECT_FALSE(menu->HasCommandWithId(IDC_BACK));
209    EXPECT_FALSE(menu->HasCommandWithId(IDC_SAVE_PAGE));
210    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_COPYLINKLOCATION));
211  }
212
213  // Verify expected menu contexts for a link.
214  {
215    content::ContextMenuParams params;
216    params.page_url = web_contents->GetURL();
217    params.unfiltered_link_url = GURL("http://google.com/");
218    // Ensure context menu isn't swallowed by WebContentsDelegate (the panel).
219    EXPECT_FALSE(web_contents->GetDelegate()->HandleContextMenu(params));
220
221    scoped_ptr<PanelContextMenu> menu(
222        new PanelContextMenu(web_contents->GetMainFrame(), params));
223    menu->Init();
224
225    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_INSPECTELEMENT));
226    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_UNDO));
227    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_PASTE));
228    EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_COPY));
229    EXPECT_FALSE(menu->HasCommandWithId(IDC_BACK));
230    EXPECT_FALSE(menu->HasCommandWithId(IDC_SAVE_PAGE));
231    EXPECT_TRUE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_COPYLINKLOCATION));
232  }
233}
234
235IN_PROC_BROWSER_TEST_F(PanelExtensionBrowserTest, CustomContextMenu) {
236  ExtensionTestMessageListener listener("created item", false);
237  LoadExtension(test_data_dir_.AppendASCII("context_menu"));
238  ASSERT_TRUE(listener.WaitUntilSatisfied());
239
240  // Load a second extension that also creates a custom context menu item.
241  ExtensionTestMessageListener bogey_listener("created bogey item", false);
242  LoadExtension(test_data_dir_.AppendASCII("context_menu2"));
243  ASSERT_TRUE(bogey_listener.WaitUntilSatisfied());
244
245  // There should only be one panel.
246  PanelManager* panel_manager = PanelManager::GetInstance();
247  EXPECT_EQ(1, panel_manager->num_panels());
248  Panel* panel = panel_manager->panels().front();
249  content::WebContents* web_contents = panel->GetWebContents();
250  ASSERT_TRUE(web_contents);
251
252  content::ContextMenuParams params;
253  params.page_url = web_contents->GetURL();
254
255  // Ensure context menu isn't swallowed by WebContentsDelegate (the panel).
256  EXPECT_FALSE(web_contents->GetDelegate()->HandleContextMenu(params));
257
258  // Verify menu contents contains the custom item added by their own extension.
259  scoped_ptr<PanelContextMenu> menu;
260  menu.reset(new PanelContextMenu(web_contents->GetMainFrame(), params));
261  menu->Init();
262  EXPECT_TRUE(menu->HasCommandWithId(IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST));
263  EXPECT_FALSE(menu->HasCommandWithId(IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST + 1));
264  EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_INSPECTELEMENT));
265  EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_UNDO));
266  EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_PASTE));
267  EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_COPY));
268  EXPECT_FALSE(menu->HasCommandWithId(IDC_BACK));
269  EXPECT_FALSE(menu->HasCommandWithId(IDC_SAVE_PAGE));
270  EXPECT_FALSE(menu->HasCommandWithId(IDC_CONTENT_CONTEXT_COPYLINKLOCATION));
271
272  // Execute the extension's custom menu item and wait for the extension's
273  // script to tell us its onclick fired.
274  ExtensionTestMessageListener onclick_listener("clicked", false);
275  int command_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST;
276  ASSERT_TRUE(menu->IsCommandIdEnabled(command_id));
277  menu->ExecuteCommand(command_id, 0);
278  EXPECT_TRUE(onclick_listener.WaitUntilSatisfied());
279}
280