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/gtk/extensions/extension_popup_gtk.h"
      6 
      7 #include <gtk/gtk.h>
      8 
      9 #include <algorithm>
     10 
     11 #include "base/bind.h"
     12 #include "base/bind_helpers.h"
     13 #include "base/callback.h"
     14 #include "base/i18n/rtl.h"
     15 #include "base/message_loop/message_loop.h"
     16 #include "chrome/browser/chrome_notification_types.h"
     17 #include "chrome/browser/devtools/devtools_window.h"
     18 #include "chrome/browser/extensions/extension_host.h"
     19 #include "chrome/browser/extensions/extension_process_manager.h"
     20 #include "chrome/browser/extensions/extension_system.h"
     21 #include "chrome/browser/profiles/profile.h"
     22 #include "chrome/browser/ui/browser.h"
     23 #include "chrome/browser/ui/browser_window.h"
     24 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     25 #include "content/public/browser/devtools_agent_host.h"
     26 #include "content/public/browser/devtools_manager.h"
     27 #include "content/public/browser/notification_details.h"
     28 #include "content/public/browser/notification_source.h"
     29 #include "content/public/browser/render_view_host.h"
     30 #include "content/public/browser/render_widget_host_view.h"
     31 #include "url/gurl.h"
     32 
     33 using content::RenderViewHost;
     34 
     35 ExtensionPopupGtk* ExtensionPopupGtk::current_extension_popup_ = NULL;
     36 
     37 // The minimum/maximum dimensions of the extension popup.
     38 // The minimum is just a little larger than the size of a browser action button.
     39 // The maximum is an arbitrary number that should be smaller than most screens.
     40 const int ExtensionPopupGtk::kMinWidth = 25;
     41 const int ExtensionPopupGtk::kMinHeight = 25;
     42 const int ExtensionPopupGtk::kMaxWidth = 800;
     43 const int ExtensionPopupGtk::kMaxHeight = 600;
     44 
     45 ExtensionPopupGtk::ExtensionPopupGtk(Browser* browser,
     46                                      extensions::ExtensionHost* host,
     47                                      GtkWidget* anchor,
     48                                      ShowAction show_action)
     49     : browser_(browser),
     50       bubble_(NULL),
     51       host_(host),
     52       anchor_(anchor),
     53       weak_factory_(this),
     54       devtools_callback_(base::Bind(
     55           &ExtensionPopupGtk::OnDevToolsStateChanged, base::Unretained(this))) {
     56   host_->view()->SetContainer(this);
     57   being_inspected_ = show_action == SHOW_AND_INSPECT;
     58 
     59   // If the host had somehow finished loading, then we'd miss the notification
     60   // and not show.  This seems to happen in single-process mode.
     61   if (host->did_stop_loading()) {
     62     ShowPopup();
     63   } else {
     64     registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
     65                    content::Source<Profile>(host->profile()));
     66   }
     67 
     68   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
     69                  content::Source<Profile>(host->profile()));
     70   content::DevToolsManager::GetInstance()->AddAgentStateCallback(
     71       devtools_callback_);
     72 }
     73 
     74 ExtensionPopupGtk::~ExtensionPopupGtk() {
     75   content::DevToolsManager::GetInstance()->RemoveAgentStateCallback(
     76       devtools_callback_);
     77 }
     78 
     79 // static
     80 void ExtensionPopupGtk::Show(const GURL& url, Browser* browser,
     81     GtkWidget* anchor, ShowAction show_action) {
     82   ExtensionProcessManager* manager =
     83       extensions::ExtensionSystem::Get(browser->profile())->process_manager();
     84   DCHECK(manager);
     85   if (!manager)
     86     return;
     87 
     88   extensions::ExtensionHost* host = manager->CreatePopupHost(url, browser);
     89   // This object will delete itself when the bubble is closed.
     90   new ExtensionPopupGtk(browser, host, anchor, show_action);
     91 }
     92 
     93 void ExtensionPopupGtk::Observe(int type,
     94                                 const content::NotificationSource& source,
     95                                 const content::NotificationDetails& details) {
     96   switch (type) {
     97     case chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING:
     98       if (content::Details<extensions::ExtensionHost>(host_.get()) == details)
     99         ShowPopup();
    100       break;
    101     case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE:
    102       if (content::Details<extensions::ExtensionHost>(host_.get()) == details)
    103         DestroyPopup();
    104       break;
    105     default:
    106       NOTREACHED() << "Received unexpected notification";
    107   }
    108 }
    109 
    110 void ExtensionPopupGtk::OnDevToolsStateChanged(
    111     content::DevToolsAgentHost* agent_host, bool attached) {
    112   // Make sure it's the devtools window that is inspecting our popup.
    113   if (host_->render_view_host() != agent_host->GetRenderViewHost())
    114     return;
    115   if (attached) {
    116     // Make sure that the popup won't go away when the inspector is activated.
    117     if (bubble_)
    118       bubble_->StopGrabbingInput();
    119 
    120     being_inspected_ = true;
    121   } else {
    122     // If the devtools window is closing, we post a task to ourselves to
    123     // close the popup. This gives the devtools window a chance to finish
    124     // detaching from the inspected RenderViewHost.
    125     base::MessageLoop::current()->PostTask(
    126         FROM_HERE,
    127         base::Bind(&ExtensionPopupGtk::DestroyPopupWithoutResult,
    128                    weak_factory_.GetWeakPtr()));
    129   }
    130 }
    131 
    132 void ExtensionPopupGtk::BubbleClosing(BubbleGtk* bubble,
    133                                       bool closed_by_escape) {
    134   current_extension_popup_ = NULL;
    135   delete this;
    136 }
    137 
    138 void ExtensionPopupGtk::OnExtensionSizeChanged(
    139     ExtensionViewGtk* view,
    140     const gfx::Size& new_size) {
    141   int width = std::max(kMinWidth, std::min(kMaxWidth, new_size.width()));
    142   int height = std::max(kMinHeight, std::min(kMaxHeight, new_size.height()));
    143 
    144   view->render_view_host()->GetView()->SetSize(gfx::Size(width, height));
    145   gtk_widget_set_size_request(view->native_view(), width, height);
    146 }
    147 
    148 bool ExtensionPopupGtk::DestroyPopup() {
    149   if (!bubble_) {
    150     NOTREACHED();
    151     return false;
    152   }
    153 
    154   bubble_->Close();
    155   return true;
    156 }
    157 
    158 void ExtensionPopupGtk::ShowPopup() {
    159   if (bubble_) {
    160     NOTREACHED();
    161     return;
    162   }
    163 
    164   if (being_inspected_)
    165     DevToolsWindow::OpenDevToolsWindow(host_->render_view_host());
    166 
    167   // Only one instance should be showing at a time. Get rid of the old one, if
    168   // any. Typically, |current_extension_popup_| will be NULL, but it can be
    169   // non-NULL if a browser action button is clicked while another extension
    170   // popup's extension host is still loading.
    171   if (current_extension_popup_)
    172     current_extension_popup_->DestroyPopup();
    173   current_extension_popup_ = this;
    174 
    175   GtkWidget* border_box = gtk_alignment_new(0, 0, 1.0, 1.0);
    176   // This border is necessary so the bubble's corners do not get cut off by the
    177   // render view.
    178   gtk_container_set_border_width(GTK_CONTAINER(border_box), 2);
    179   gtk_container_add(GTK_CONTAINER(border_box), host_->view()->native_view());
    180 
    181   // We'll be in the upper-right corner of the window for LTR languages, so we
    182   // want to put the arrow at the upper-right corner of the bubble to match the
    183   // page and app menus.
    184   bubble_ = BubbleGtk::Show(anchor_,
    185                             NULL,
    186                             border_box,
    187                             BubbleGtk::ANCHOR_TOP_RIGHT,
    188                             being_inspected_ ? 0 :
    189                                 BubbleGtk::POPUP_WINDOW | BubbleGtk::GRAB_INPUT,
    190                             GtkThemeService::GetFrom(browser_->profile()),
    191                             this);
    192 }
    193 
    194 void ExtensionPopupGtk::DestroyPopupWithoutResult() {
    195   DestroyPopup();
    196 }
    197 
    198 gfx::Rect ExtensionPopupGtk::GetViewBounds() {
    199   GtkAllocation allocation;
    200   gtk_widget_get_allocation(host_->view()->native_view(), &allocation);
    201   return gfx::Rect(allocation);
    202 }
    203