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