extension_popup.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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 "chrome/browser/ui/views/extensions/extension_popup.h"
6
7#include "base/bind.h"
8#include "base/message_loop/message_loop.h"
9#include "chrome/browser/chrome_notification_types.h"
10#include "chrome/browser/devtools/devtools_window.h"
11#include "chrome/browser/extensions/extension_view_host.h"
12#include "chrome/browser/extensions/extension_view_host_factory.h"
13#include "chrome/browser/platform_util.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/ui/browser.h"
16#include "chrome/browser/ui/browser_window.h"
17#include "chrome/browser/ui/host_desktop.h"
18#include "chrome/browser/ui/views/frame/browser_view.h"
19#include "content/public/browser/devtools_agent_host.h"
20#include "content/public/browser/devtools_manager.h"
21#include "content/public/browser/notification_details.h"
22#include "content/public/browser/notification_source.h"
23#include "content/public/browser/render_view_host.h"
24#include "content/public/browser/web_contents.h"
25#include "content/public/browser/web_contents_view.h"
26#include "ui/gfx/insets.h"
27#include "ui/views/layout/fill_layout.h"
28#include "ui/views/widget/widget.h"
29
30#if defined(USE_AURA)
31#include "ui/aura/client/activation_client.h"
32#include "ui/aura/window.h"
33#include "ui/views/corewm/window_animations.h"
34#endif
35
36#if defined(OS_WIN)
37#include "ui/views/win/hwnd_util.h"
38#endif
39
40using content::BrowserContext;
41using content::RenderViewHost;
42using content::WebContents;
43
44namespace {
45
46// Returns true if |possible_owner| is the owner of |child|.
47bool IsOwnerOf(gfx::NativeView child, gfx::NativeView possible_owner) {
48  if (!child)
49    return false;
50#if defined(OS_WIN)
51  if (::GetWindow(views::HWNDForNativeView(child), GW_OWNER) ==
52      views::HWNDForNativeView(possible_owner))
53    return true;
54#endif
55  return false;
56}
57
58}  // namespace
59
60// The minimum/maximum dimensions of the popup.
61// The minimum is just a little larger than the size of the button itself.
62// The maximum is an arbitrary number that should be smaller than most screens.
63const int ExtensionPopup::kMinWidth = 25;
64const int ExtensionPopup::kMinHeight = 25;
65const int ExtensionPopup::kMaxWidth = 800;
66const int ExtensionPopup::kMaxHeight = 600;
67
68ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost* host,
69                               views::View* anchor_view,
70                               views::BubbleBorder::Arrow arrow,
71                               ShowAction show_action)
72    : BubbleDelegateView(anchor_view, arrow),
73      host_(host),
74      devtools_callback_(base::Bind(
75          &ExtensionPopup::OnDevToolsStateChanged, base::Unretained(this))) {
76  inspect_with_devtools_ = show_action == SHOW_AND_INSPECT;
77  // Adjust the margin so that contents fit better.
78  const int margin = views::BubbleBorder::GetCornerRadius() / 2;
79  set_margins(gfx::Insets(margin, margin, margin, margin));
80  SetLayoutManager(new views::FillLayout());
81  AddChildView(host->view());
82  host->view()->SetContainer(this);
83  // Use OnNativeFocusChange to check for child window activation on deactivate.
84  set_close_on_deactivate(false);
85  // Make the bubble move with its anchor (during inspection, etc.).
86  set_move_with_anchor(true);
87
88  // Wait to show the popup until the contained host finishes loading.
89  registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
90                 content::Source<WebContents>(host->host_contents()));
91
92  // Listen for the containing view calling window.close();
93  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
94                 content::Source<BrowserContext>(host->browser_context()));
95  content::DevToolsManager::GetInstance()->AddAgentStateCallback(
96      devtools_callback_);
97}
98
99ExtensionPopup::~ExtensionPopup() {
100  content::DevToolsManager::GetInstance()->RemoveAgentStateCallback(
101      devtools_callback_);
102}
103
104void ExtensionPopup::Observe(int type,
105                             const content::NotificationSource& source,
106                             const content::NotificationDetails& details) {
107  switch (type) {
108    case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME:
109      DCHECK(content::Source<WebContents>(host()->host_contents()) == source);
110      // Show when the content finishes loading and its width is computed.
111      ShowBubble();
112      break;
113    case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE:
114      // If we aren't the host of the popup, then disregard the notification.
115      if (content::Details<extensions::ExtensionHost>(host()) == details)
116        GetWidget()->Close();
117      break;
118    default:
119      NOTREACHED() << L"Received unexpected notification";
120  }
121}
122
123void ExtensionPopup::OnDevToolsStateChanged(
124    content::DevToolsAgentHost* agent_host, bool attached) {
125  // First check that the devtools are being opened on this popup.
126  if (host()->render_view_host() != agent_host->GetRenderViewHost())
127    return;
128
129  if (attached) {
130    // Set inspect_with_devtools_ so the popup will be kept open while
131    // the devtools are open.
132    inspect_with_devtools_ = true;
133  } else {
134    // Widget::Close posts a task, which should give the devtools window a
135    // chance to finish detaching from the inspected RenderViewHost.
136    GetWidget()->Close();
137  }
138}
139
140void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews* view) {
141  SizeToContents();
142}
143
144gfx::Size ExtensionPopup::GetPreferredSize() {
145  // Constrain the size to popup min/max.
146  gfx::Size sz = views::View::GetPreferredSize();
147  sz.set_width(std::max(kMinWidth, std::min(kMaxWidth, sz.width())));
148  sz.set_height(std::max(kMinHeight, std::min(kMaxHeight, sz.height())));
149  return sz;
150}
151
152void ExtensionPopup::OnWidgetDestroying(views::Widget* widget) {
153  BubbleDelegateView::OnWidgetDestroying(widget);
154#if defined(USE_AURA)
155  aura::Window* bubble_window = GetWidget()->GetNativeWindow();
156  aura::client::ActivationClient* activation_client =
157      aura::client::GetActivationClient(bubble_window->GetRootWindow());
158  activation_client->RemoveObserver(this);
159#endif
160}
161
162void ExtensionPopup::OnWidgetActivationChanged(views::Widget* widget,
163                                               bool active) {
164  BubbleDelegateView::OnWidgetActivationChanged(widget, active);
165  // Dismiss only if the window being activated is not owned by this popup's
166  // window. In particular, don't dismiss when we lose activation to a child
167  // dialog box. Possibly relevant: http://crbug.com/106723 and
168  // http://crbug.com/179786
169  views::Widget* this_widget = GetWidget();
170  gfx::NativeView activated_view = widget->GetNativeView();
171  gfx::NativeView this_view = this_widget->GetNativeView();
172  if (active && !inspect_with_devtools_ && activated_view != this_view &&
173      !IsOwnerOf(activated_view, this_view))
174    this_widget->Close();
175}
176
177#if defined(USE_AURA)
178void ExtensionPopup::OnWindowActivated(aura::Window* gained_active,
179                                       aura::Window* lost_active) {
180  // DesktopNativeWidgetAura does not trigger the expected browser widget
181  // [de]activation events when activating widgets in its own root window.
182  // This additional check handles those cases. See: http://crbug.com/320889
183  aura::Window* this_window = GetWidget()->GetNativeWindow();
184  aura::Window* anchor_window = anchor_widget()->GetNativeWindow();
185  chrome::HostDesktopType host_desktop_type =
186      chrome::GetHostDesktopTypeForNativeWindow(this_window);
187  if (!inspect_with_devtools_ && anchor_window == gained_active &&
188      host_desktop_type != chrome::HOST_DESKTOP_TYPE_ASH &&
189      this_window->GetRootWindow() == anchor_window->GetRootWindow() &&
190      gained_active->transient_parent() != this_window)
191    GetWidget()->Close();
192}
193#endif
194
195// static
196ExtensionPopup* ExtensionPopup::ShowPopup(const GURL& url,
197                                          Browser* browser,
198                                          views::View* anchor_view,
199                                          views::BubbleBorder::Arrow arrow,
200                                          ShowAction show_action) {
201  extensions::ExtensionViewHost* host =
202      extensions::ExtensionViewHostFactory::CreatePopupHost(url, browser);
203  ExtensionPopup* popup = new ExtensionPopup(host, anchor_view, arrow,
204      show_action);
205  views::BubbleDelegateView::CreateBubble(popup);
206
207#if defined(USE_AURA)
208  gfx::NativeView native_view = popup->GetWidget()->GetNativeView();
209  views::corewm::SetWindowVisibilityAnimationType(
210      native_view,
211      views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
212  views::corewm::SetWindowVisibilityAnimationVerticalPosition(
213      native_view,
214      -3.0f);
215#endif
216
217  // If the host had somehow finished loading, then we'd miss the notification
218  // and not show.  This seems to happen in single-process mode.
219  if (host->did_stop_loading())
220    popup->ShowBubble();
221
222#if defined(USE_AURA)
223  aura::Window* bubble_window = popup->GetWidget()->GetNativeWindow();
224  aura::client::ActivationClient* activation_client =
225      aura::client::GetActivationClient(bubble_window->GetRootWindow());
226  activation_client->AddObserver(popup);
227#endif
228
229  return popup;
230}
231
232void ExtensionPopup::ShowBubble() {
233  GetWidget()->Show();
234
235  // Focus on the host contents when the bubble is first shown.
236  host()->host_contents()->GetView()->Focus();
237
238  if (inspect_with_devtools_) {
239    DevToolsWindow::ToggleDevToolsWindow(host()->render_view_host(),
240        true,
241        DevToolsToggleAction::ShowConsole());
242  }
243}
244