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_process_manager.h"
     12 #include "chrome/browser/extensions/extension_system.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/views/frame/browser_view.h"
     18 #include "content/public/browser/devtools_agent_host.h"
     19 #include "content/public/browser/devtools_manager.h"
     20 #include "content/public/browser/notification_details.h"
     21 #include "content/public/browser/notification_source.h"
     22 #include "content/public/browser/render_view_host.h"
     23 #include "content/public/browser/web_contents.h"
     24 #include "content/public/browser/web_contents_view.h"
     25 #include "ui/gfx/insets.h"
     26 #include "ui/views/layout/fill_layout.h"
     27 #include "ui/views/widget/widget.h"
     28 
     29 #if defined(USE_AURA)
     30 #include "ui/aura/window.h"
     31 #include "ui/views/corewm/window_animations.h"
     32 #endif
     33 
     34 #if defined(OS_WIN)
     35 #include "ui/views/win/hwnd_util.h"
     36 #endif
     37 
     38 using content::RenderViewHost;
     39 using content::WebContents;
     40 
     41 namespace {
     42 
     43 // Returns true if |possible_owner| is the owner of |child|.
     44 bool IsOwnerOf(gfx::NativeView child, gfx::NativeView possible_owner) {
     45   if (!child)
     46     return false;
     47 #if defined(OS_WIN)
     48   if (::GetWindow(views::HWNDForNativeView(child), GW_OWNER) ==
     49       views::HWNDForNativeView(possible_owner))
     50     return true;
     51 #endif
     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.
     60 const int ExtensionPopup::kMinWidth = 25;
     61 const int ExtensionPopup::kMinHeight = 25;
     62 const int ExtensionPopup::kMaxWidth = 800;
     63 const int ExtensionPopup::kMaxHeight = 600;
     64 
     65 ExtensionPopup::ExtensionPopup(extensions::ExtensionHost* host,
     66                                views::View* anchor_view,
     67                                views::BubbleBorder::Arrow arrow,
     68                                ShowAction show_action)
     69     : BubbleDelegateView(anchor_view, arrow),
     70       extension_host_(host),
     71       devtools_callback_(base::Bind(
     72           &ExtensionPopup::OnDevToolsStateChanged, base::Unretained(this))) {
     73   inspect_with_devtools_ = show_action == SHOW_AND_INSPECT;
     74   // Adjust the margin so that contents fit better.
     75   const int margin = views::BubbleBorder::GetCornerRadius() / 2;
     76   set_margins(gfx::Insets(margin, margin, margin, margin));
     77   SetLayoutManager(new views::FillLayout());
     78   AddChildView(host->view());
     79   host->view()->SetContainer(this);
     80   // Use OnNativeFocusChange to check for child window activation on deactivate.
     81   set_close_on_deactivate(false);
     82   // Make the bubble move with its anchor (during inspection, etc.).
     83   set_move_with_anchor(true);
     84 
     85   // Wait to show the popup until the contained host finishes loading.
     86   registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
     87                  content::Source<WebContents>(host->host_contents()));
     88 
     89   // Listen for the containing view calling window.close();
     90   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
     91                  content::Source<Profile>(host->profile()));
     92   content::DevToolsManager::GetInstance()->AddAgentStateCallback(
     93       devtools_callback_);
     94 }
     95 
     96 ExtensionPopup::~ExtensionPopup() {
     97   content::DevToolsManager::GetInstance()->RemoveAgentStateCallback(
     98       devtools_callback_);
     99 }
    100 
    101 void ExtensionPopup::Observe(int type,
    102                              const content::NotificationSource& source,
    103                              const content::NotificationDetails& details) {
    104   switch (type) {
    105     case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME:
    106       DCHECK(content::Source<WebContents>(host()->host_contents()) == source);
    107       // Show when the content finishes loading and its width is computed.
    108       ShowBubble();
    109       break;
    110     case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE:
    111       // If we aren't the host of the popup, then disregard the notification.
    112       if (content::Details<extensions::ExtensionHost>(host()) == details)
    113         GetWidget()->Close();
    114       break;
    115     default:
    116       NOTREACHED() << L"Received unexpected notification";
    117   }
    118 }
    119 
    120 void ExtensionPopup::OnDevToolsStateChanged(
    121     content::DevToolsAgentHost* agent_host, bool attached) {
    122   // First check that the devtools are being opened on this popup.
    123   if (host()->render_view_host() != agent_host->GetRenderViewHost())
    124     return;
    125 
    126   if (attached) {
    127     // Set inspect_with_devtools_ so the popup will be kept open while
    128     // the devtools are open.
    129     inspect_with_devtools_ = true;
    130   } else {
    131     // Widget::Close posts a task, which should give the devtools window a
    132     // chance to finish detaching from the inspected RenderViewHost.
    133     GetWidget()->Close();
    134   }
    135 }
    136 
    137 void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews* view) {
    138   SizeToContents();
    139 }
    140 
    141 gfx::Size ExtensionPopup::GetPreferredSize() {
    142   // Constrain the size to popup min/max.
    143   gfx::Size sz = views::View::GetPreferredSize();
    144   sz.set_width(std::max(kMinWidth, std::min(kMaxWidth, sz.width())));
    145   sz.set_height(std::max(kMinHeight, std::min(kMaxHeight, sz.height())));
    146   return sz;
    147 }
    148 
    149 void ExtensionPopup::OnWidgetActivationChanged(views::Widget* widget,
    150                                                bool active) {
    151   BubbleDelegateView::OnWidgetActivationChanged(widget, active);
    152   // Dismiss only if the window being activated is not owned by this popup's
    153   // window. In particular, don't dismiss when we lose activation to a child
    154   // dialog box. Possibly relevant: http://crbug.com/106723 and
    155   // http://crbug.com/179786
    156   views::Widget* this_widget = GetWidget();
    157   gfx::NativeView activated_view = widget->GetNativeView();
    158   gfx::NativeView this_view = this_widget->GetNativeView();
    159   if (active && !inspect_with_devtools_ && activated_view != this_view &&
    160       !IsOwnerOf(activated_view, this_view))
    161     this_widget->Close();
    162 }
    163 
    164 // static
    165 ExtensionPopup* ExtensionPopup::ShowPopup(const GURL& url,
    166                                           Browser* browser,
    167                                           views::View* anchor_view,
    168                                           views::BubbleBorder::Arrow arrow,
    169                                           ShowAction show_action) {
    170   ExtensionProcessManager* manager =
    171       extensions::ExtensionSystem::Get(browser->profile())->process_manager();
    172   extensions::ExtensionHost* host = manager->CreatePopupHost(url, browser);
    173   ExtensionPopup* popup = new ExtensionPopup(host, anchor_view, arrow,
    174       show_action);
    175   views::BubbleDelegateView::CreateBubble(popup);
    176 
    177 #if defined(USE_AURA)
    178   gfx::NativeView native_view = popup->GetWidget()->GetNativeView();
    179   views::corewm::SetWindowVisibilityAnimationType(
    180       native_view,
    181       views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
    182   views::corewm::SetWindowVisibilityAnimationVerticalPosition(
    183       native_view,
    184       -3.0f);
    185 #endif
    186 
    187   // If the host had somehow finished loading, then we'd miss the notification
    188   // and not show.  This seems to happen in single-process mode.
    189   if (host->did_stop_loading())
    190     popup->ShowBubble();
    191 
    192   return popup;
    193 }
    194 
    195 void ExtensionPopup::ShowBubble() {
    196   GetWidget()->Show();
    197 
    198   // Focus on the host contents when the bubble is first shown.
    199   host()->host_contents()->GetView()->Focus();
    200 
    201   if (inspect_with_devtools_) {
    202     DevToolsWindow::ToggleDevToolsWindow(host()->render_view_host(),
    203         true,
    204         DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE);
    205   }
    206 }
    207