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