extension_popup.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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.h"
9#include "chrome/browser/debugger/devtools_window.h"
10#include "chrome/browser/extensions/extension_process_manager.h"
11#include "chrome/browser/extensions/extension_system.h"
12#include "chrome/browser/platform_util.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/ui/browser.h"
15#include "chrome/browser/ui/browser_window.h"
16#include "chrome/common/chrome_notification_types.h"
17#include "content/public/browser/notification_details.h"
18#include "content/public/browser/notification_source.h"
19#include "content/public/browser/render_view_host.h"
20#include "content/public/browser/web_contents.h"
21#include "ui/gfx/insets.h"
22#include "ui/views/layout/fill_layout.h"
23#include "ui/views/widget/widget.h"
24
25#if defined(USE_AURA)
26#include "ui/aura/window.h"
27#endif
28
29#if defined(USE_ASH)
30#include "ash/wm/window_animations.h"
31#endif
32
33using content::RenderViewHost;
34using content::WebContents;
35
36namespace {
37
38// Returns true if |possible_parent| is a parent window of |child|.
39bool IsParent(gfx::NativeView child, gfx::NativeView possible_parent) {
40  if (!child)
41    return false;
42#if !defined(USE_AURA) && defined(OS_WIN)
43  if (::GetWindow(child, GW_OWNER) == possible_parent)
44    return true;
45#endif
46  gfx::NativeView parent = child;
47  while ((parent = platform_util::GetParent(parent))) {
48    if (possible_parent == parent)
49      return true;
50  }
51
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(
66    Browser* browser,
67    extensions::ExtensionHost* host,
68    views::View* anchor_view,
69    views::BubbleBorder::ArrowLocation arrow_location,
70    ShowAction show_action)
71    : BubbleDelegateView(anchor_view, arrow_location),
72      extension_host_(host),
73      close_bubble_factory_(this) {
74  inspect_with_devtools_ = show_action == SHOW_AND_INSPECT;
75  // Adjust the margin so that contents fit better.
76  const int margin = views::BubbleBorder::GetCornerRadius() / 2;
77  set_margins(gfx::Insets(margin, margin, margin, margin));
78  SetLayoutManager(new views::FillLayout());
79  AddChildView(host->view());
80  host->view()->SetContainer(this);
81  // Use OnNativeFocusChange to check for child window activation on deactivate.
82  set_close_on_deactivate(false);
83  // Make the bubble move with its anchor (during inspection, etc.).
84  set_move_with_anchor(true);
85
86  // Wait to show the popup until the contained host finishes loading.
87  registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
88                 content::Source<WebContents>(host->host_contents()));
89
90  // Listen for the containing view calling window.close();
91  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
92                 content::Source<Profile>(host->profile()));
93
94  // Listen for the dev tools opening on this popup, so we can stop it going
95  // away when the dev tools get focus.
96  registrar_.Add(this, content::NOTIFICATION_DEVTOOLS_AGENT_ATTACHED,
97                 content::Source<Profile>(host->profile()));
98
99  // Listen for the dev tools closing, so we can close this window if it is
100  // being inspected and the inspector is closed.
101  registrar_.Add(this, content::NOTIFICATION_DEVTOOLS_AGENT_DETACHED,
102      content::Source<content::BrowserContext>(host->profile()));
103}
104
105ExtensionPopup::~ExtensionPopup() {
106  views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this);
107}
108
109void ExtensionPopup::Observe(int type,
110                             const content::NotificationSource& source,
111                             const content::NotificationDetails& details) {
112  switch (type) {
113    case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME:
114      DCHECK(content::Source<WebContents>(host()->host_contents()) == source);
115      // Show when the content finishes loading and its width is computed.
116      ShowBubble();
117      break;
118    case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE:
119      // If we aren't the host of the popup, then disregard the notification.
120      if (content::Details<extensions::ExtensionHost>(host()) == details)
121        GetWidget()->Close();
122      break;
123    case content::NOTIFICATION_DEVTOOLS_AGENT_DETACHED:
124      // Make sure it's the devtools window that inspecting our popup.
125      // Widget::Close posts a task, which should give the devtools window a
126      // chance to finish detaching from the inspected RenderViewHost.
127      if (content::Details<RenderViewHost>(host()->render_view_host()) ==
128          details) {
129        GetWidget()->Close();
130      }
131      break;
132    case content::NOTIFICATION_DEVTOOLS_AGENT_ATTACHED:
133      // First check that the devtools are being opened on this popup.
134      if (content::Details<RenderViewHost>(host()->render_view_host()) ==
135          details) {
136        // Set inspect_with_devtools_ so the popup will be kept open while
137        // the devtools are open.
138        inspect_with_devtools_ = true;
139      }
140      break;
141    default:
142      NOTREACHED() << L"Received unexpected notification";
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::OnNativeFocusChange(gfx::NativeView focused_before,
159                                         gfx::NativeView focused_now) {
160  // Don't close if a child of this window is activated (only needed on Win).
161  // ExtensionPopups can create Javascipt dialogs; see crbug.com/106723.
162  gfx::NativeView this_window = GetWidget()->GetNativeView();
163  if (inspect_with_devtools_ || focused_now == this_window ||
164      IsParent(focused_now, this_window))
165    return;
166  // Delay closing the widget because on Aura, closing right away makes the
167  // activation controller trigger another focus change before the current focus
168  // change is complete.
169  if (!close_bubble_factory_.HasWeakPtrs()) {
170    MessageLoop::current()->PostTask(FROM_HERE,
171        base::Bind(&ExtensionPopup::CloseBubble,
172                   close_bubble_factory_.GetWeakPtr()));
173  }
174}
175
176// static
177ExtensionPopup* ExtensionPopup::ShowPopup(
178    const GURL& url,
179    Browser* browser,
180    views::View* anchor_view,
181    views::BubbleBorder::ArrowLocation arrow_location,
182    ShowAction show_action) {
183  ExtensionProcessManager* manager =
184      extensions::ExtensionSystem::Get(browser->profile())->process_manager();
185  extensions::ExtensionHost* host = manager->CreatePopupHost(url, browser);
186  ExtensionPopup* popup = new ExtensionPopup(browser, host, anchor_view,
187      arrow_location, show_action);
188  views::BubbleDelegateView::CreateBubble(popup);
189
190#if defined(USE_ASH)
191  gfx::NativeView native_view = popup->GetWidget()->GetNativeView();
192  ash::SetWindowVisibilityAnimationType(
193      native_view,
194      ash::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
195  ash::SetWindowVisibilityAnimationVerticalPosition(
196      native_view,
197      -3.0f);
198#endif
199
200  // If the host had somehow finished loading, then we'd miss the notification
201  // and not show.  This seems to happen in single-process mode.
202  if (host->did_stop_loading())
203    popup->ShowBubble();
204
205  return popup;
206}
207
208void ExtensionPopup::ShowBubble() {
209  Show();
210
211  // Focus on the host contents when the bubble is first shown.
212  host()->host_contents()->Focus();
213
214  // Listen for widget focus changes after showing (used for non-aura win).
215  views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this);
216
217  if (inspect_with_devtools_) {
218    DevToolsWindow::ToggleDevToolsWindow(host()->render_view_host(),
219        true,
220        DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE);
221  }
222}
223
224void ExtensionPopup::CloseBubble() {
225  GetWidget()->Close();
226}
227