Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package

extension_popup.cc

// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/views/extensions/extension_popup.h"

#include "chrome/browser/browser.h"
#include "chrome/browser/browser_list.h"
#include "chrome/browser/browser_window.h"
#include "chrome/browser/debugger/devtools_manager.h"
#include "chrome/browser/extensions/extension_host.h"
#include "chrome/browser/extensions/extension_process_manager.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/renderer_host/render_widget_host_view.h"
#include "chrome/browser/renderer_host/render_view_host.h"
#include "chrome/browser/views/frame/browser_view.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/notification_details.h"
#include "chrome/common/notification_source.h"
#include "chrome/common/notification_type.h"
#include "third_party/skia/include/core/SkColor.h"
#include "views/widget/root_view.h"
#include "views/window/window.h"

#if defined(OS_LINUX)
#include "views/widget/widget_gtk.h"
#endif

#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/wm_ipc.h"
#endif

using views::Widget;

// The minimum/maximum dimensions of the popup.
// The minimum is just a little larger than the size of the button itself.
// The maximum is an arbitrary number that should be smaller than most screens.
const int ExtensionPopup::kMinWidth = 25;
const int ExtensionPopup::kMinHeight = 25;
const int ExtensionPopup::kMaxWidth = 800;
const int ExtensionPopup::kMaxHeight = 600;

// The width, in pixels, of the black-border on a popup.
const int kPopupBorderWidth = 1;

const int kPopupBubbleCornerRadius = BubbleBorder::GetCornerRadius() / 2;

ExtensionPopup::ExtensionPopup(ExtensionHost* host,
                               views::Widget* frame,
                               const gfx::Rect& relative_to,
                               BubbleBorder::ArrowLocation arrow_location,
                               bool activate_on_show,
                               bool inspect_with_devtools,
                               PopupChrome chrome,
                               Observer* observer)
    : BrowserBubble(host->view(),
                    frame,
                    gfx::Point(),
                    RECTANGLE_CHROME == chrome),  // If no bubble chrome is to
                                                  // be displayed, then enable a
                                                  // drop-shadow on the bubble
                                                  // widget.
      relative_to_(relative_to),
      extension_host_(host),
      activate_on_show_(activate_on_show),
      inspect_with_devtools_(inspect_with_devtools),
      close_on_lost_focus_(true),
      closing_(false),
      border_widget_(NULL),
      border_(NULL),
      border_view_(NULL),
      popup_chrome_(chrome),
      observer_(observer),
      anchor_position_(arrow_location) {
  AddRef();  // Balanced in Close();
  set_delegate(this);
  host->view()->SetContainer(this);

  // We wait to show the popup until the contained host finishes loading.
  registrar_.Add(this,
                 NotificationType::EXTENSION_HOST_DID_STOP_LOADING,
                 Source<Profile>(host->profile()));

  // Listen for the containing view calling window.close();
  registrar_.Add(this, NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE,
                 Source<Profile>(host->profile()));

  // TODO(erikkay) Some of this border code is derived from InfoBubble.
  // We should see if we can unify these classes.

  // Keep relative_to_ in frame-relative coordinates to aid in drag
  // positioning.
  gfx::Point origin = relative_to_.origin();
  views::View::ConvertPointToView(NULL, frame_->GetRootView(), &origin);
  relative_to_.set_origin(origin);

  // The bubble chrome requires a separate window, so construct it here.
  if (BUBBLE_CHROME == popup_chrome_) {
    gfx::NativeView native_window = frame->GetNativeView();
#if defined(OS_LINUX)
    border_widget_ = new views::WidgetGtk(views::WidgetGtk::TYPE_WINDOW);
    static_cast<views::WidgetGtk*>(border_widget_)->MakeTransparent();
    static_cast<views::WidgetGtk*>(border_widget_)->make_transient_to_parent();
#else
    border_widget_ = Widget::CreatePopupWidget(Widget::Transparent,
                                               Widget::NotAcceptEvents,
                                               Widget::DeleteOnDestroy);
#endif
    border_widget_->Init(native_window, bounds());
#if defined(OS_CHROMEOS)
    chromeos::WmIpc::instance()->SetWindowType(
        border_widget_->GetNativeView(),
        chromeos::WmIpc::WINDOW_TYPE_CHROME_INFO_BUBBLE,
        NULL);
#endif
    border_ = new BubbleBorder;
    border_->set_arrow_location(arrow_location);

    border_view_ = new views::View;
    border_view_->set_background(new BubbleBackground(border_));

    border_view_->set_border(border_);
    border_widget_->SetContentsView(border_view_);
    // Ensure that the popup contents are always displayed ontop of the border
    // widget.
    border_widget_->MoveAbove(popup_);
  } else {
    // Otherwise simply set a black-border on the view containing the popup
    // extension view.
    views::Border* border = views::Border::CreateSolidBorder(kPopupBorderWidth,
                                                             SK_ColorBLACK);
    view()->set_border(border);
  }
}

ExtensionPopup::~ExtensionPopup() {
  // The widget is set to delete on destroy, so no leak here.
  if (border_widget_)
    border_widget_->Close();
}

void ExtensionPopup::Hide() {
  BrowserBubble::Hide();
  if (border_widget_)
    border_widget_->Hide();
}

void ExtensionPopup::Show(bool activate) {
  if (visible())
    return;

#if defined(OS_WIN)
  if (frame_->GetWindow())
    frame_->GetWindow()->DisableInactiveRendering();
#endif

  ResizeToView();

  // Show the border first, then the popup overlaid on top.
  if (border_widget_)
    border_widget_->Show();
  BrowserBubble::Show(activate);
}

void ExtensionPopup::ResizeToView() {
  // We'll be sizing ourselves to this size shortly, but wait until we
  // know our position to do it.
  gfx::Size new_size = view()->size();

  // Convert rect to screen coordinates.
  gfx::Rect rect = relative_to_;
  gfx::Point origin = rect.origin();
  views::View::ConvertPointToScreen(frame_->GetRootView(), &origin);
  rect.set_origin(origin);

  rect = GetOuterBounds(rect, new_size);
  origin = rect.origin();
  views::View::ConvertPointToView(NULL, frame_->GetRootView(), &origin);
  if (border_widget_) {
    // Set the bubble-chrome widget according to the outer bounds of the entire
    // popup.
    border_widget_->SetBounds(rect);

    // Now calculate the inner bounds.  This is a bit more convoluted than
    // it should be because BrowserBubble coordinates are in Browser coordinates
    // while |rect| is in screen coordinates.
    gfx::Insets border_insets;
    border_->GetInsets(&border_insets);

    origin.set_x(origin.x() + border_insets.left() + kPopupBubbleCornerRadius);
    origin.set_y(origin.y() + border_insets.top() + kPopupBubbleCornerRadius);

    SetBounds(origin.x(), origin.y(), new_size.width(), new_size.height());
  } else {
    SetBounds(origin.x(), origin.y(), rect.width(), rect.height());
  }
}

void ExtensionPopup::BubbleBrowserWindowMoved(BrowserBubble* bubble) {
  ResizeToView();
}

void ExtensionPopup::BubbleBrowserWindowClosing(BrowserBubble* bubble) {
  if (!closing_)
    Close();
}

void ExtensionPopup::BubbleGotFocus(BrowserBubble* bubble) {
  // Forward the focus to the renderer.
  host()->render_view_host()->view()->Focus();
}

void ExtensionPopup::BubbleLostFocus(BrowserBubble* bubble,
    bool lost_focus_to_child) {
  if (closing_ ||                // We are already closing.
      inspect_with_devtools_ ||  // The popup is being inspected.
      !close_on_lost_focus_ ||   // Our client is handling focus listening.
      lost_focus_to_child)       // A child of this view got focus.
    return;

  // When we do close on BubbleLostFocus, we do it in the next event loop
  // because a subsequent event in this loop may also want to close this popup
  // and if so, we want to allow that. Example: Clicking the same browser
  // action button that opened the popup. If we closed immediately, the
  // browser action container would fail to discover that the same button
  // was pressed.
  MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this,
      &ExtensionPopup::Close));
}


void ExtensionPopup::Observe(NotificationType type,
                             const NotificationSource& source,
                             const NotificationDetails& details) {
  switch (type.value) {
    case NotificationType::EXTENSION_HOST_DID_STOP_LOADING:
      // Once we receive did stop loading, the content will be complete and
      // the width will have been computed.  Now it's safe to show.
      if (extension_host_.get() == Details<ExtensionHost>(details).ptr()) {
        Show(activate_on_show_);

        if (inspect_with_devtools_) {
          // Listen for the the devtools window closing.
          registrar_.Add(this, NotificationType::DEVTOOLS_WINDOW_CLOSING,
              Source<Profile>(extension_host_->profile()));
          DevToolsManager::GetInstance()->ToggleDevToolsWindow(
              extension_host_->render_view_host(), true);
        }
      }
      break;
    case NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE:
      // If we aren't the host of the popup, then disregard the notification.
      if (Details<ExtensionHost>(host()) != details)
        return;
      Close();

      break;
    case NotificationType::DEVTOOLS_WINDOW_CLOSING:
      // Make sure its the devtools window that inspecting our popup.
      if (Details<RenderViewHost>(extension_host_->render_view_host()) !=
          details)
        return;

      // If the devtools window is closing, we post a task to ourselves to
      // close the popup. This gives the devtools window a chance to finish
      // detaching from the inspected RenderViewHost.
      MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this,
          &ExtensionPopup::Close));

      break;
    default:
      NOTREACHED() << L"Received unexpected notification";
  }
}

void ExtensionPopup::OnExtensionPreferredSizeChanged(ExtensionView* view) {
  // Constrain the size to popup min/max.
  gfx::Size sz = view->GetPreferredSize();
  view->SetBounds(view->x(), view->y(),
      std::max(kMinWidth, std::min(kMaxWidth, sz.width())),
      std::max(kMinHeight, std::min(kMaxHeight, sz.height())));

  ResizeToView();
}

gfx::Rect ExtensionPopup::GetOuterBounds(const gfx::Rect& position_relative_to,
                                         const gfx::Size& contents_size) const {
  gfx::Size adjusted_size = contents_size;
  // If the popup has a bubble-chrome, then let the BubbleBorder compute
  // the bounds.
  if (BUBBLE_CHROME == popup_chrome_) {
    // The rounded corners cut off more of the view than the border insets
    // claim. Since we can't clip the ExtensionView's corners, we need to
    // increase the inset by half the corner radius as well as lying about the
    // size of the contents size to compensate.
    adjusted_size.Enlarge(2 * kPopupBubbleCornerRadius,
                          2 * kPopupBubbleCornerRadius);
    return border_->GetBounds(position_relative_to, adjusted_size);
  }

  // Otherwise, enlarge the bounds by the size of the local border.
  gfx::Insets border_insets;
  view()->border()->GetInsets(&border_insets);
  adjusted_size.Enlarge(border_insets.width(), border_insets.height());

  // Position the bounds according to the location of the |anchor_position_|.
  int y;
  if ((anchor_position_ == BubbleBorder::TOP_LEFT) ||
      (anchor_position_ == BubbleBorder::TOP_RIGHT)) {
    y = position_relative_to.bottom();
  } else {
    y = position_relative_to.y() - adjusted_size.height();
  }

  return gfx::Rect(position_relative_to.x(), y, adjusted_size.width(),
                   adjusted_size.height());
}

// static
ExtensionPopup* ExtensionPopup::Show(
    const GURL& url,
    Browser* browser,
    Profile* profile,
    gfx::NativeWindow frame_window,
    const gfx::Rect& relative_to,
    BubbleBorder::ArrowLocation arrow_location,
    bool activate_on_show,
    bool inspect_with_devtools,
    PopupChrome chrome,
    Observer* observer) {
  DCHECK(profile);
  DCHECK(frame_window);
  ExtensionProcessManager* manager = profile->GetExtensionProcessManager();
  DCHECK(manager);
  if (!manager)
    return NULL;

  // If no Browser instance was given, attempt to look up one matching the given
  // profile.
  if (!browser)
    browser = BrowserList::FindBrowserWithProfile(profile);

  Widget* frame_widget = Widget::GetWidgetFromNativeWindow(frame_window);
  DCHECK(frame_widget);
  if (!frame_widget)
    return NULL;

  ExtensionHost* host = manager->CreatePopup(url, browser);
  if (observer)
    observer->ExtensionHostCreated(host);

  ExtensionPopup* popup = new ExtensionPopup(host, frame_widget, relative_to,
                                             arrow_location, activate_on_show,
                                             inspect_with_devtools, chrome,
                                             observer);

  // If the host had somehow finished loading, then we'd miss the notification
  // and not show.  This seems to happen in single-process mode.
  if (host->did_stop_loading())
    popup->Show(activate_on_show);

  return popup;
}

void ExtensionPopup::Close() {
  if (closing_)
    return;
  closing_ = true;
  DetachFromBrowser();
  if (observer_)
    observer_->ExtensionPopupClosed(this);
  Release();  // Balanced in ctor.
}

Generated by  Doxygen 1.6.0   Back to index