1// Copyright (c) 2011 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 <vector>
8
9#include "chrome/browser/debugger/devtools_manager.h"
10#include "chrome/browser/debugger/devtools_toggle_action.h"
11#include "chrome/browser/extensions/extension_host.h"
12#include "chrome/browser/extensions/extension_process_manager.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/browser/ui/views/frame/browser_view.h"
17#include "chrome/common/extensions/extension.h"
18#include "content/browser/renderer_host/render_view_host.h"
19#include "content/browser/renderer_host/render_widget_host_view.h"
20#include "content/common/notification_details.h"
21#include "content/common/notification_source.h"
22#include "content/common/notification_type.h"
23#include "views/widget/root_view.h"
24#include "views/window/window.h"
25
26#if defined(OS_LINUX)
27#include "views/widget/widget_gtk.h"
28#endif
29
30#if defined(OS_CHROMEOS)
31#include "chrome/browser/chromeos/wm_ipc.h"
32#include "third_party/cros/chromeos_wm_ipc_enums.h"
33#endif
34
35using std::vector;
36using views::Widget;
37
38// The minimum/maximum dimensions of the popup.
39// The minimum is just a little larger than the size of the button itself.
40// The maximum is an arbitrary number that should be smaller than most screens.
41const int ExtensionPopup::kMinWidth = 25;
42const int ExtensionPopup::kMinHeight = 25;
43const int ExtensionPopup::kMaxWidth = 800;
44const int ExtensionPopup::kMaxHeight = 600;
45
46ExtensionPopup::ExtensionPopup(ExtensionHost* host,
47                               views::Widget* frame,
48                               const gfx::Rect& relative_to,
49                               BubbleBorder::ArrowLocation arrow_location,
50                               bool inspect_with_devtools,
51                               Observer* observer)
52    : BrowserBubble(host->view(),
53                    frame,
54                    relative_to,
55                    arrow_location),
56      relative_to_(relative_to),
57      extension_host_(host),
58      inspect_with_devtools_(inspect_with_devtools),
59      close_on_lost_focus_(true),
60      closing_(false),
61      observer_(observer) {
62  AddRef();  // Balanced in Close();
63  set_delegate(this);
64  host->view()->SetContainer(this);
65
66  // We wait to show the popup until the contained host finishes loading.
67  registrar_.Add(this,
68                 NotificationType::EXTENSION_HOST_DID_STOP_LOADING,
69                 Source<Profile>(host->profile()));
70
71  // Listen for the containing view calling window.close();
72  registrar_.Add(this, NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE,
73                 Source<Profile>(host->profile()));
74}
75
76ExtensionPopup::~ExtensionPopup() {
77}
78
79void ExtensionPopup::Show(bool activate) {
80  if (visible())
81    return;
82
83#if defined(OS_WIN)
84  frame_->GetWindow()->DisableInactiveRendering();
85#endif
86
87  ResizeToView();
88  BrowserBubble::Show(activate);
89}
90
91void ExtensionPopup::BubbleBrowserWindowMoved(BrowserBubble* bubble) {
92  ResizeToView();
93}
94
95void ExtensionPopup::BubbleBrowserWindowClosing(BrowserBubble* bubble) {
96  if (!closing_)
97    Close();
98}
99
100void ExtensionPopup::BubbleGotFocus(BrowserBubble* bubble) {
101  // Forward the focus to the renderer.
102  host()->render_view_host()->view()->Focus();
103}
104
105void ExtensionPopup::BubbleLostFocus(BrowserBubble* bubble,
106    bool lost_focus_to_child) {
107  if (closing_ ||                // We are already closing.
108      inspect_with_devtools_ ||  // The popup is being inspected.
109      !close_on_lost_focus_ ||   // Our client is handling focus listening.
110      lost_focus_to_child)       // A child of this view got focus.
111    return;
112
113  // When we do close on BubbleLostFocus, we do it in the next event loop
114  // because a subsequent event in this loop may also want to close this popup
115  // and if so, we want to allow that. Example: Clicking the same browser
116  // action button that opened the popup. If we closed immediately, the
117  // browser action container would fail to discover that the same button
118  // was pressed.
119  MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this,
120      &ExtensionPopup::Close));
121}
122
123
124void ExtensionPopup::Observe(NotificationType type,
125                             const NotificationSource& source,
126                             const NotificationDetails& details) {
127  switch (type.value) {
128    case NotificationType::EXTENSION_HOST_DID_STOP_LOADING:
129      // Once we receive did stop loading, the content will be complete and
130      // the width will have been computed.  Now it's safe to show.
131      if (extension_host_.get() == Details<ExtensionHost>(details).ptr()) {
132        Show(true);
133
134        if (inspect_with_devtools_) {
135          // Listen for the the devtools window closing.
136          registrar_.Add(this, NotificationType::DEVTOOLS_WINDOW_CLOSING,
137              Source<Profile>(extension_host_->profile()));
138          DevToolsManager::GetInstance()->ToggleDevToolsWindow(
139              extension_host_->render_view_host(),
140              DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE);
141        }
142      }
143      break;
144    case NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE:
145      // If we aren't the host of the popup, then disregard the notification.
146      if (Details<ExtensionHost>(host()) != details)
147        return;
148      Close();
149
150      break;
151    case NotificationType::DEVTOOLS_WINDOW_CLOSING:
152      // Make sure its the devtools window that inspecting our popup.
153      if (Details<RenderViewHost>(extension_host_->render_view_host()) !=
154          details)
155        return;
156
157      // If the devtools window is closing, we post a task to ourselves to
158      // close the popup. This gives the devtools window a chance to finish
159      // detaching from the inspected RenderViewHost.
160      MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this,
161          &ExtensionPopup::Close));
162
163      break;
164    default:
165      NOTREACHED() << L"Received unexpected notification";
166  }
167}
168
169void ExtensionPopup::OnExtensionPreferredSizeChanged(ExtensionView* view) {
170  // Constrain the size to popup min/max.
171  gfx::Size sz = view->GetPreferredSize();
172  view->SetBounds(view->x(), view->y(),
173      std::max(kMinWidth, std::min(kMaxWidth, sz.width())),
174      std::max(kMinHeight, std::min(kMaxHeight, sz.height())));
175
176  ResizeToView();
177}
178
179// static
180ExtensionPopup* ExtensionPopup::Show(
181    const GURL& url,
182    Browser* browser,
183    const gfx::Rect& relative_to,
184    BubbleBorder::ArrowLocation arrow_location,
185    bool inspect_with_devtools,
186    Observer* observer) {
187  ExtensionProcessManager* manager =
188      browser->profile()->GetExtensionProcessManager();
189  DCHECK(manager);
190  if (!manager)
191    return NULL;
192
193  ExtensionHost* host = manager->CreatePopup(url, browser);
194  views::Widget* frame = BrowserView::GetBrowserViewForNativeWindow(
195      browser->window()->GetNativeHandle())->GetWidget();
196  ExtensionPopup* popup = new ExtensionPopup(host, frame, relative_to,
197                                             arrow_location,
198                                             inspect_with_devtools, observer);
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->Show(true);
204
205  return popup;
206}
207
208void ExtensionPopup::Close() {
209  if (closing_)
210    return;
211  closing_ = true;
212  DetachFromBrowser();
213
214  if (observer_)
215    observer_->ExtensionPopupIsClosing(this);
216
217  Release();  // Balanced in ctor.
218}
219