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