Home | History | Annotate | Download | only in extensions
      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/message_loop.h"
      9 #include "chrome/browser/chrome_notification_types.h"
     10 #include "chrome/browser/devtools/devtools_window.h"
     11 #include "chrome/browser/extensions/extension_view_host.h"
     12 #include "chrome/browser/extensions/extension_view_host_factory.h"
     13 #include "chrome/browser/platform_util.h"
     14 #include "chrome/browser/profiles/profile.h"
     15 #include "chrome/browser/ui/browser.h"
     16 #include "chrome/browser/ui/browser_window.h"
     17 #include "chrome/browser/ui/host_desktop.h"
     18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     19 #include "chrome/browser/ui/views/frame/browser_view.h"
     20 #include "content/public/browser/devtools_agent_host.h"
     21 #include "content/public/browser/devtools_manager.h"
     22 #include "content/public/browser/notification_details.h"
     23 #include "content/public/browser/notification_source.h"
     24 #include "content/public/browser/render_view_host.h"
     25 #include "content/public/browser/web_contents.h"
     26 #include "content/public/browser/web_contents_view.h"
     27 #include "ui/gfx/insets.h"
     28 #include "ui/views/layout/fill_layout.h"
     29 #include "ui/views/widget/widget.h"
     30 
     31 #if defined(USE_AURA)
     32 #include "ui/aura/client/activation_client.h"
     33 #include "ui/aura/window.h"
     34 #include "ui/views/corewm/window_animations.h"
     35 #endif
     36 
     37 #if defined(OS_WIN)
     38 #include "ui/views/win/hwnd_util.h"
     39 #endif
     40 
     41 using content::BrowserContext;
     42 using content::RenderViewHost;
     43 using content::WebContents;
     44 
     45 namespace {
     46 
     47 // Returns true if |possible_owner| is the owner of |child|.
     48 bool IsOwnerOf(gfx::NativeView child, gfx::NativeView possible_owner) {
     49   if (!child)
     50     return false;
     51 #if defined(OS_WIN)
     52   if (::GetWindow(views::HWNDForNativeView(child), GW_OWNER) ==
     53       views::HWNDForNativeView(possible_owner))
     54     return true;
     55 #endif
     56   return false;
     57 }
     58 
     59 }  // namespace
     60 
     61 // The minimum/maximum dimensions of the popup.
     62 // The minimum is just a little larger than the size of the button itself.
     63 // The maximum is an arbitrary number that should be smaller than most screens.
     64 const int ExtensionPopup::kMinWidth = 25;
     65 const int ExtensionPopup::kMinHeight = 25;
     66 const int ExtensionPopup::kMaxWidth = 800;
     67 const int ExtensionPopup::kMaxHeight = 600;
     68 
     69 ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost* host,
     70                                views::View* anchor_view,
     71                                views::BubbleBorder::Arrow arrow,
     72                                ShowAction show_action)
     73     : BubbleDelegateView(anchor_view, arrow),
     74       host_(host),
     75       devtools_callback_(base::Bind(
     76           &ExtensionPopup::OnDevToolsStateChanged, base::Unretained(this))) {
     77   inspect_with_devtools_ = show_action == SHOW_AND_INSPECT;
     78   // Adjust the margin so that contents fit better.
     79   const int margin = views::BubbleBorder::GetCornerRadius() / 2;
     80   set_margins(gfx::Insets(margin, margin, margin, margin));
     81   SetLayoutManager(new views::FillLayout());
     82   AddChildView(host->view());
     83   host->view()->SetContainer(this);
     84   // Use OnNativeFocusChange to check for child window activation on deactivate.
     85   set_close_on_deactivate(false);
     86   // Make the bubble move with its anchor (during inspection, etc.).
     87   set_move_with_anchor(true);
     88 
     89   // Wait to show the popup until the contained host finishes loading.
     90   registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
     91                  content::Source<WebContents>(host->host_contents()));
     92 
     93   // Listen for the containing view calling window.close();
     94   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
     95                  content::Source<BrowserContext>(host->browser_context()));
     96   content::DevToolsManager::GetInstance()->AddAgentStateCallback(
     97       devtools_callback_);
     98 
     99   host_->view()->browser()->tab_strip_model()->AddObserver(this);
    100 }
    101 
    102 ExtensionPopup::~ExtensionPopup() {
    103   content::DevToolsManager::GetInstance()->RemoveAgentStateCallback(
    104       devtools_callback_);
    105 
    106   host_->view()->browser()->tab_strip_model()->RemoveObserver(this);
    107 }
    108 
    109 void 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     default:
    124       NOTREACHED() << L"Received unexpected notification";
    125   }
    126 }
    127 
    128 void ExtensionPopup::OnDevToolsStateChanged(
    129     content::DevToolsAgentHost* agent_host, bool attached) {
    130   // First check that the devtools are being opened on this popup.
    131   if (host()->render_view_host() != agent_host->GetRenderViewHost())
    132     return;
    133 
    134   if (attached) {
    135     // Set inspect_with_devtools_ so the popup will be kept open while
    136     // the devtools are open.
    137     inspect_with_devtools_ = true;
    138   } else {
    139     // Widget::Close posts a task, which should give the devtools window a
    140     // chance to finish detaching from the inspected RenderViewHost.
    141     GetWidget()->Close();
    142   }
    143 }
    144 
    145 void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews* view) {
    146   SizeToContents();
    147 }
    148 
    149 gfx::Size ExtensionPopup::GetPreferredSize() {
    150   // Constrain the size to popup min/max.
    151   gfx::Size sz = views::View::GetPreferredSize();
    152   sz.set_width(std::max(kMinWidth, std::min(kMaxWidth, sz.width())));
    153   sz.set_height(std::max(kMinHeight, std::min(kMaxHeight, sz.height())));
    154   return sz;
    155 }
    156 
    157 void ExtensionPopup::OnWidgetDestroying(views::Widget* widget) {
    158   BubbleDelegateView::OnWidgetDestroying(widget);
    159 #if defined(USE_AURA)
    160   aura::Window* bubble_window = GetWidget()->GetNativeWindow();
    161   aura::client::ActivationClient* activation_client =
    162       aura::client::GetActivationClient(bubble_window->GetRootWindow());
    163   activation_client->RemoveObserver(this);
    164 #endif
    165 }
    166 
    167 void ExtensionPopup::OnWidgetActivationChanged(views::Widget* widget,
    168                                                bool active) {
    169   BubbleDelegateView::OnWidgetActivationChanged(widget, active);
    170   // Dismiss only if the window being activated is not owned by this popup's
    171   // window. In particular, don't dismiss when we lose activation to a child
    172   // dialog box. Possibly relevant: http://crbug.com/106723 and
    173   // http://crbug.com/179786
    174   views::Widget* this_widget = GetWidget();
    175   gfx::NativeView activated_view = widget->GetNativeView();
    176   gfx::NativeView this_view = this_widget->GetNativeView();
    177   if (active && !inspect_with_devtools_ && activated_view != this_view &&
    178       !IsOwnerOf(activated_view, this_view))
    179     this_widget->Close();
    180 }
    181 
    182 #if defined(USE_AURA)
    183 void ExtensionPopup::OnWindowActivated(aura::Window* gained_active,
    184                                        aura::Window* lost_active) {
    185   // DesktopNativeWidgetAura does not trigger the expected browser widget
    186   // [de]activation events when activating widgets in its own root window.
    187   // This additional check handles those cases. See: http://crbug.com/320889
    188   aura::Window* this_window = GetWidget()->GetNativeWindow();
    189   aura::Window* anchor_window = anchor_widget()->GetNativeWindow();
    190   chrome::HostDesktopType host_desktop_type =
    191       chrome::GetHostDesktopTypeForNativeWindow(this_window);
    192   if (!inspect_with_devtools_ && anchor_window == gained_active &&
    193       host_desktop_type != chrome::HOST_DESKTOP_TYPE_ASH &&
    194       this_window->GetRootWindow() == anchor_window->GetRootWindow() &&
    195       gained_active->transient_parent() != this_window)
    196     GetWidget()->Close();
    197 }
    198 #endif
    199 
    200 void ExtensionPopup::ActiveTabChanged(content::WebContents* old_contents,
    201                                       content::WebContents* new_contents,
    202                                       int index,
    203                                       int reason) {
    204   GetWidget()->Close();
    205 }
    206 
    207 // static
    208 ExtensionPopup* ExtensionPopup::ShowPopup(const GURL& url,
    209                                           Browser* browser,
    210                                           views::View* anchor_view,
    211                                           views::BubbleBorder::Arrow arrow,
    212                                           ShowAction show_action) {
    213   extensions::ExtensionViewHost* host =
    214       extensions::ExtensionViewHostFactory::CreatePopupHost(url, browser);
    215   ExtensionPopup* popup = new ExtensionPopup(host, anchor_view, arrow,
    216       show_action);
    217   views::BubbleDelegateView::CreateBubble(popup);
    218 
    219 #if defined(USE_AURA)
    220   gfx::NativeView native_view = popup->GetWidget()->GetNativeView();
    221   views::corewm::SetWindowVisibilityAnimationType(
    222       native_view,
    223       views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
    224   views::corewm::SetWindowVisibilityAnimationVerticalPosition(
    225       native_view,
    226       -3.0f);
    227 #endif
    228 
    229   // If the host had somehow finished loading, then we'd miss the notification
    230   // and not show.  This seems to happen in single-process mode.
    231   if (host->did_stop_loading())
    232     popup->ShowBubble();
    233 
    234 #if defined(USE_AURA)
    235   aura::Window* bubble_window = popup->GetWidget()->GetNativeWindow();
    236   aura::client::ActivationClient* activation_client =
    237       aura::client::GetActivationClient(bubble_window->GetRootWindow());
    238   activation_client->AddObserver(popup);
    239 #endif
    240 
    241   return popup;
    242 }
    243 
    244 void ExtensionPopup::ShowBubble() {
    245   GetWidget()->Show();
    246 
    247   // Focus on the host contents when the bubble is first shown.
    248   host()->host_contents()->GetView()->Focus();
    249 
    250   if (inspect_with_devtools_) {
    251     DevToolsWindow::ToggleDevToolsWindow(host()->render_view_host(),
    252         true,
    253         DevToolsToggleAction::ShowConsole());
    254   }
    255 }
    256