extension_popup.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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 "chrome/browser/chrome_notification_types.h"
9#include "chrome/browser/devtools/devtools_window.h"
10#include "chrome/browser/extensions/extension_view_host.h"
11#include "chrome/browser/extensions/extension_view_host_factory.h"
12#include "chrome/browser/ui/browser.h"
13#include "chrome/browser/ui/tabs/tab_strip_model.h"
14#include "content/public/browser/devtools_agent_host.h"
15#include "content/public/browser/devtools_manager.h"
16#include "content/public/browser/notification_details.h"
17#include "content/public/browser/notification_source.h"
18#include "content/public/browser/render_view_host.h"
19#include "content/public/browser/web_contents.h"
20#include "ui/aura/window.h"
21#include "ui/gfx/insets.h"
22#include "ui/views/layout/fill_layout.h"
23#include "ui/views/widget/widget.h"
24#include "ui/wm/core/window_animations.h"
25#include "ui/wm/core/window_util.h"
26#include "ui/wm/public/activation_client.h"
27
28namespace {
29
30ExtensionViewViews* GetExtensionView(extensions::ExtensionViewHost* host) {
31  return static_cast<ExtensionViewViews*>(host->view());
32}
33
34}  // namespace
35
36// The minimum/maximum dimensions of the popup.
37// The minimum is just a little larger than the size of the button itself.
38// The maximum is an arbitrary number that should be smaller than most screens.
39const int ExtensionPopup::kMinWidth = 25;
40const int ExtensionPopup::kMinHeight = 25;
41const int ExtensionPopup::kMaxWidth = 800;
42const int ExtensionPopup::kMaxHeight = 600;
43
44ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost* host,
45                               views::View* anchor_view,
46                               views::BubbleBorder::Arrow arrow,
47                               ShowAction show_action)
48    : BubbleDelegateView(anchor_view, arrow),
49      host_(host),
50      devtools_callback_(base::Bind(
51          &ExtensionPopup::OnDevToolsStateChanged, base::Unretained(this))),
52      widget_initialized_(false) {
53  inspect_with_devtools_ = show_action == SHOW_AND_INSPECT;
54  // Adjust the margin so that contents fit better.
55  const int margin = views::BubbleBorder::GetCornerRadius() / 2;
56  set_margins(gfx::Insets(margin, margin, margin, margin));
57  SetLayoutManager(new views::FillLayout());
58  AddChildView(GetExtensionView(host));
59  GetExtensionView(host)->set_container(this);
60  // ExtensionPopup closes itself on very specific de-activation conditions.
61  set_close_on_deactivate(false);
62
63  // Wait to show the popup until the contained host finishes loading.
64  registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
65                 content::Source<content::WebContents>(host->host_contents()));
66
67  // Listen for the containing view calling window.close();
68  registrar_.Add(
69      this,
70      extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
71      content::Source<content::BrowserContext>(host->browser_context()));
72  content::DevToolsManager::GetInstance()->AddAgentStateCallback(
73      devtools_callback_);
74
75  GetExtensionView(host)->GetBrowser()->tab_strip_model()->AddObserver(this);
76}
77
78ExtensionPopup::~ExtensionPopup() {
79  content::DevToolsManager::GetInstance()->RemoveAgentStateCallback(
80      devtools_callback_);
81
82  GetExtensionView(
83      host_.get())->GetBrowser()->tab_strip_model()->RemoveObserver(this);
84}
85
86void ExtensionPopup::Observe(int type,
87                             const content::NotificationSource& source,
88                             const content::NotificationDetails& details) {
89  switch (type) {
90    case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME:
91      DCHECK_EQ(host()->host_contents(),
92                content::Source<content::WebContents>(source).ptr());
93      // Show when the content finishes loading and its width is computed.
94      ShowBubble();
95      break;
96    case extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE:
97      // If we aren't the host of the popup, then disregard the notification.
98      if (content::Details<extensions::ExtensionHost>(host()) == details)
99        GetWidget()->Close();
100      break;
101    default:
102      NOTREACHED() << L"Received unexpected notification";
103  }
104}
105
106void ExtensionPopup::OnDevToolsStateChanged(
107    content::DevToolsAgentHost* agent_host,
108    bool attached) {
109  // First check that the devtools are being opened on this popup.
110  if (host()->host_contents() != agent_host->GetWebContents())
111    return;
112
113  if (attached) {
114    // Set inspect_with_devtools_ so the popup will be kept open while
115    // the devtools are open.
116    inspect_with_devtools_ = true;
117  } else {
118    // Widget::Close posts a task, which should give the devtools window a
119    // chance to finish detaching from the inspected RenderViewHost.
120    GetWidget()->Close();
121  }
122}
123
124void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews* view) {
125  SizeToContents();
126}
127
128gfx::Size ExtensionPopup::GetPreferredSize() const {
129  // Constrain the size to popup min/max.
130  gfx::Size sz = views::View::GetPreferredSize();
131  sz.set_width(std::max(kMinWidth, std::min(kMaxWidth, sz.width())));
132  sz.set_height(std::max(kMinHeight, std::min(kMaxHeight, sz.height())));
133  return sz;
134}
135
136void ExtensionPopup::ViewHierarchyChanged(
137  const ViewHierarchyChangedDetails& details) {
138  // TODO(msw): Find any remaining crashes related to http://crbug.com/327776
139  // No view hierarchy changes are expected if the widget no longer exists.
140  widget_initialized_ |= details.child == this && details.is_add && GetWidget();
141  CHECK(GetWidget() || !widget_initialized_);
142}
143
144void ExtensionPopup::OnWidgetDestroying(views::Widget* widget) {
145  BubbleDelegateView::OnWidgetDestroying(widget);
146  aura::Window* bubble_window = GetWidget()->GetNativeWindow();
147  aura::client::ActivationClient* activation_client =
148      aura::client::GetActivationClient(bubble_window->GetRootWindow());
149  // If the popup was being inspected with devtools and the browser window was
150  // closed, then the root window and activation client are already destroyed.
151  if (activation_client)
152    activation_client->RemoveObserver(this);
153}
154
155void ExtensionPopup::OnWidgetActivationChanged(views::Widget* widget,
156                                               bool active) {
157  // TODO(msw): Find any remaining crashes related to http://crbug.com/327776
158  // No calls are expected if the widget isn't initialized or no longer exists.
159  CHECK(widget_initialized_);
160  CHECK(GetWidget());
161
162  // Close on anchor window activation (ie. user clicked the browser window).
163  if (!inspect_with_devtools_ && widget && active &&
164      widget->GetNativeWindow() == anchor_widget()->GetNativeWindow())
165    GetWidget()->Close();
166}
167
168void ExtensionPopup::OnWindowActivated(aura::Window* gained_active,
169                                       aura::Window* lost_active) {
170  // TODO(msw): Find any remaining crashes related to http://crbug.com/327776
171  // No calls are expected if the widget isn't initialized or no longer exists.
172  CHECK(widget_initialized_);
173  CHECK(GetWidget());
174
175  // Close on anchor window activation (ie. user clicked the browser window).
176  // DesktopNativeWidgetAura does not trigger the expected browser widget
177  // [de]activation events when activating widgets in its own root window.
178  // This additional check handles those cases. See: http://crbug.com/320889
179  if (!inspect_with_devtools_ &&
180      gained_active == anchor_widget()->GetNativeWindow())
181    GetWidget()->Close();
182}
183
184void ExtensionPopup::ActiveTabChanged(content::WebContents* old_contents,
185                                      content::WebContents* new_contents,
186                                      int index,
187                                      int reason) {
188  GetWidget()->Close();
189}
190
191// static
192ExtensionPopup* ExtensionPopup::ShowPopup(const GURL& url,
193                                          Browser* browser,
194                                          views::View* anchor_view,
195                                          views::BubbleBorder::Arrow arrow,
196                                          ShowAction show_action) {
197  extensions::ExtensionViewHost* host =
198      extensions::ExtensionViewHostFactory::CreatePopupHost(url, browser);
199  ExtensionPopup* popup = new ExtensionPopup(host, anchor_view, arrow,
200      show_action);
201  views::BubbleDelegateView::CreateBubble(popup);
202
203  gfx::NativeView native_view = popup->GetWidget()->GetNativeView();
204  wm::SetWindowVisibilityAnimationType(
205      native_view, wm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
206  wm::SetWindowVisibilityAnimationVerticalPosition(native_view, -3.0f);
207
208  // If the host had somehow finished loading, then we'd miss the notification
209  // and not show.  This seems to happen in single-process mode.
210  if (host->did_stop_loading())
211    popup->ShowBubble();
212
213  aura::Window* bubble_window = popup->GetWidget()->GetNativeWindow();
214  aura::client::ActivationClient* activation_client =
215      aura::client::GetActivationClient(bubble_window->GetRootWindow());
216  activation_client->AddObserver(popup);
217
218  return popup;
219}
220
221void ExtensionPopup::ShowBubble() {
222  GetWidget()->Show();
223
224  // Focus on the host contents when the bubble is first shown.
225  host()->host_contents()->Focus();
226
227  if (inspect_with_devtools_) {
228    DevToolsWindow::OpenDevToolsWindow(host()->host_contents(),
229                                       DevToolsToggleAction::ShowConsole());
230  }
231}
232