Home | History | Annotate | Download | only in extensions
      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 
     35 using std::vector;
     36 using 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.
     41 const int ExtensionPopup::kMinWidth = 25;
     42 const int ExtensionPopup::kMinHeight = 25;
     43 const int ExtensionPopup::kMaxWidth = 800;
     44 const int ExtensionPopup::kMaxHeight = 600;
     45 
     46 ExtensionPopup::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 
     76 ExtensionPopup::~ExtensionPopup() {
     77 }
     78 
     79 void 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 
     91 void ExtensionPopup::BubbleBrowserWindowMoved(BrowserBubble* bubble) {
     92   ResizeToView();
     93 }
     94 
     95 void ExtensionPopup::BubbleBrowserWindowClosing(BrowserBubble* bubble) {
     96   if (!closing_)
     97     Close();
     98 }
     99 
    100 void ExtensionPopup::BubbleGotFocus(BrowserBubble* bubble) {
    101   // Forward the focus to the renderer.
    102   host()->render_view_host()->view()->Focus();
    103 }
    104 
    105 void 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 
    124 void 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 
    169 void 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
    180 ExtensionPopup* 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 
    208 void 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