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