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