blob: 736da9e293f5f4b356a6ffff805811047cc46442 [file] [log] [blame]
// Copyright (c) 2012 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/printing/print_preview_tab_controller.h"
#include <algorithm>
#include <string>
#include <vector>
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_plugin_service_filter.h"
#include "chrome/browser/printing/print_view_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/chrome_web_contents_handler.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_ui.h"
#include "chrome/common/chrome_content_client.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "ui/web_dialogs/constrained_web_dialog_ui.h"
#include "ui/web_dialogs/web_dialog_delegate.h"
#include "ui/web_dialogs/web_dialog_web_contents_delegate.h"
#include "webkit/plugins/webplugininfo.h"
using content::NativeWebKeyboardEvent;
using content::NavigationController;
using content::WebContents;
using content::WebUIMessageHandler;
using ui::ConstrainedWebDialogDelegate;
using ui::WebDialogDelegate;
using ui::WebDialogWebContentsDelegate;
namespace {
void EnableInternalPDFPluginForTab(TabContents* preview_tab) {
// Always enable the internal PDF plugin for the print preview page.
ChromePluginServiceFilter::GetInstance()->OverridePluginForTab(
preview_tab->web_contents()->GetRenderProcessHost()->GetID(),
preview_tab->web_contents()->GetRenderViewHost()->GetRoutingID(),
GURL(),
ASCIIToUTF16(chrome::ChromeContentClient::kPDFPluginName));
}
// WebDialogDelegate that specifies what the print preview dialog
// will look like.
class PrintPreviewTabDelegate : public WebDialogDelegate {
public:
explicit PrintPreviewTabDelegate(TabContents* initiator_tab);
virtual ~PrintPreviewTabDelegate();
virtual ui::ModalType GetDialogModalType() const OVERRIDE;
virtual string16 GetDialogTitle() const OVERRIDE;
virtual GURL GetDialogContentURL() const OVERRIDE;
virtual void GetWebUIMessageHandlers(
std::vector<WebUIMessageHandler*>* handlers) const OVERRIDE;
virtual void GetDialogSize(gfx::Size* size) const OVERRIDE;
virtual std::string GetDialogArgs() const OVERRIDE;
virtual void OnDialogClosed(const std::string& json_retval) OVERRIDE;
virtual void OnCloseContents(WebContents* source,
bool* out_close_dialog) OVERRIDE;
virtual bool ShouldShowDialogTitle() const OVERRIDE;
private:
gfx::Size size_;
DISALLOW_COPY_AND_ASSIGN(PrintPreviewTabDelegate);
};
PrintPreviewTabDelegate::PrintPreviewTabDelegate(TabContents* initiator_tab) {
const gfx::Size kMinDialogSize(800, 480);
const int kBorder = 50;
gfx::Rect rect;
initiator_tab->web_contents()->GetContainerBounds(&rect);
size_.set_width(std::max(rect.width(), kMinDialogSize.width()) - kBorder);
size_.set_height(std::max(rect.height(), kMinDialogSize.height()) - kBorder);
#if defined(OS_MACOSX)
// Limit the maximum size on MacOS X.
// http://crbug.com/105815
const gfx::Size kMaxDialogSize(1000, 660);
size_.set_width(std::min(size_.width(), kMaxDialogSize.width()));
size_.set_height(std::min(size_.height(), kMaxDialogSize.height()));
#endif
}
PrintPreviewTabDelegate::~PrintPreviewTabDelegate() {
}
ui::ModalType PrintPreviewTabDelegate::GetDialogModalType() const {
// Not used, returning dummy value.
NOTREACHED();
return ui::MODAL_TYPE_WINDOW;
}
string16 PrintPreviewTabDelegate::GetDialogTitle() const {
// Only used on Windows? UI folks prefer no title.
return string16();
}
GURL PrintPreviewTabDelegate::GetDialogContentURL() const {
return GURL(chrome::kChromeUIPrintURL);
}
void PrintPreviewTabDelegate::GetWebUIMessageHandlers(
std::vector<WebUIMessageHandler*>* /* handlers */) const {
// PrintPreviewUI adds its own message handlers.
}
void PrintPreviewTabDelegate::GetDialogSize(gfx::Size* size) const {
*size = size_;
}
std::string PrintPreviewTabDelegate::GetDialogArgs() const {
return std::string();
}
void PrintPreviewTabDelegate::OnDialogClosed(
const std::string& /* json_retval */) {
}
void PrintPreviewTabDelegate::OnCloseContents(WebContents* /* source */,
bool* out_close_dialog) {
if (out_close_dialog)
*out_close_dialog = true;
}
bool PrintPreviewTabDelegate::ShouldShowDialogTitle() const {
// Not used, returning dummy value.
NOTREACHED();
return false;
}
// WebContentsDelegate that forwards shortcut keys in the print preview
// renderer to the browser and makes sure users can't reload/save.
class PrintPreviewWebContentDelegate : public WebDialogWebContentsDelegate,
public CoreTabHelperDelegate {
public:
PrintPreviewWebContentDelegate(Profile* profile, TabContents* initiator_tab);
virtual ~PrintPreviewWebContentDelegate();
// Overridden from WebDialogWebContentsDelegate:
virtual void HandleKeyboardEvent(
WebContents* source,
const NativeWebKeyboardEvent& event) OVERRIDE;
// Overridden from CoreTabHelperDelegate:
virtual bool CanReloadContents(
content::WebContents* web_contents) const OVERRIDE;
virtual bool CanSaveContents(
content::WebContents* web_contents) const OVERRIDE;
private:
TabContents* tab_;
DISALLOW_COPY_AND_ASSIGN(PrintPreviewWebContentDelegate);
};
PrintPreviewWebContentDelegate::PrintPreviewWebContentDelegate(
Profile* profile,
TabContents* initiator_tab)
: WebDialogWebContentsDelegate(profile, new ChromeWebContentsHandler),
tab_(initiator_tab) {}
PrintPreviewWebContentDelegate::~PrintPreviewWebContentDelegate() {}
bool PrintPreviewWebContentDelegate::CanReloadContents(
content::WebContents* web_contents) const {
return false;
}
bool PrintPreviewWebContentDelegate::CanSaveContents(
content::WebContents* web_contents) const {
return false;
}
void PrintPreviewWebContentDelegate::HandleKeyboardEvent(
WebContents* source,
const NativeWebKeyboardEvent& event) {
// Disabled on Mac due to http://crbug.com/112173
#if !defined(OS_MACOSX)
Browser* current_browser =
browser::FindBrowserWithWebContents(tab_->web_contents());
if (!current_browser)
return;
current_browser->window()->HandleKeyboardEvent(event);
#endif
}
} // namespace
namespace printing {
PrintPreviewTabController::PrintPreviewTabController()
: waiting_for_new_preview_page_(false),
is_creating_print_preview_tab_(false) {
}
// static
PrintPreviewTabController* PrintPreviewTabController::GetInstance() {
if (!g_browser_process)
return NULL;
return g_browser_process->print_preview_tab_controller();
}
// static
void PrintPreviewTabController::PrintPreview(TabContents* tab) {
if (tab->web_contents()->ShowingInterstitialPage())
return;
PrintPreviewTabController* tab_controller = GetInstance();
if (!tab_controller)
return;
if (!tab_controller->GetOrCreatePreviewTab(tab)) {
printing::PrintViewManager::FromWebContents(tab->web_contents())->
PrintPreviewDone();
}
}
TabContents* PrintPreviewTabController::GetOrCreatePreviewTab(
TabContents* initiator_tab) {
DCHECK(initiator_tab);
// Get the print preview tab for |initiator_tab|.
TabContents* preview_tab = GetPrintPreviewForTab(initiator_tab);
if (!preview_tab)
return CreatePrintPreviewTab(initiator_tab);
// Show the initiator tab holding the existing preview tab.
WebContents* web_contents = initiator_tab->web_contents();
web_contents->GetDelegate()->ActivateContents(web_contents);
return preview_tab;
}
TabContents* PrintPreviewTabController::GetPrintPreviewForTab(
TabContents* tab) const {
// |preview_tab_map_| is keyed by the preview tab, so if find() succeeds, then
// |tab| is the preview tab.
PrintPreviewTabMap::const_iterator it = preview_tab_map_.find(tab);
if (it != preview_tab_map_.end())
return tab;
for (it = preview_tab_map_.begin(); it != preview_tab_map_.end(); ++it) {
// If |tab| is an initiator tab.
if (tab == it->second) {
// Return the associated preview tab.
return it->first;
}
}
return NULL;
}
TabContents* PrintPreviewTabController::GetInitiatorTab(
TabContents* preview_tab) {
PrintPreviewTabMap::iterator it = preview_tab_map_.find(preview_tab);
if (it != preview_tab_map_.end())
return preview_tab_map_[preview_tab];
return NULL;
}
void PrintPreviewTabController::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
if (type == content::NOTIFICATION_RENDERER_PROCESS_CLOSED) {
OnRendererProcessClosed(
content::Source<content::RenderProcessHost>(source).ptr());
} else if (type == chrome::NOTIFICATION_TAB_CONTENTS_DESTROYED) {
OnTabContentsDestroyed(content::Source<TabContents>(source).ptr());
} else {
DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED, type);
TabContents* tab = TabContents::FromWebContents(
content::Source<NavigationController>(source)->GetWebContents());
OnNavEntryCommitted(tab,
content::Details<content::LoadCommittedDetails>(details).ptr());
}
}
// static
bool PrintPreviewTabController::IsPrintPreviewTab(TabContents* tab) {
return IsPrintPreviewURL(tab->web_contents()->GetURL());
}
// static
bool PrintPreviewTabController::IsPrintPreviewURL(const GURL& url) {
return (url.SchemeIs(chrome::kChromeUIScheme) &&
url.host() == chrome::kChromeUIPrintHost);
}
void PrintPreviewTabController::EraseInitiatorTabInfo(
TabContents* preview_tab) {
PrintPreviewTabMap::iterator it = preview_tab_map_.find(preview_tab);
if (it == preview_tab_map_.end())
return;
RemoveObservers(it->second);
preview_tab_map_[preview_tab] = NULL;
}
bool PrintPreviewTabController::is_creating_print_preview_tab() const {
return is_creating_print_preview_tab_;
}
PrintPreviewTabController::~PrintPreviewTabController() {}
void PrintPreviewTabController::OnRendererProcessClosed(
content::RenderProcessHost* rph) {
// Store tabs in a vector and deal with them after iterating through
// |preview_tab_map_| because RemoveFooTab() can change |preview_tab_map_|.
std::vector<TabContents*> closed_initiator_tabs;
std::vector<TabContents*> closed_preview_tabs;
for (PrintPreviewTabMap::iterator iter = preview_tab_map_.begin();
iter != preview_tab_map_.end(); ++iter) {
TabContents* preview_tab = iter->first;
TabContents* initiator_tab = iter->second;
if (preview_tab->web_contents()->GetRenderProcessHost() == rph) {
closed_preview_tabs.push_back(preview_tab);
} else if (initiator_tab &&
initiator_tab->web_contents()->GetRenderProcessHost() == rph) {
closed_initiator_tabs.push_back(initiator_tab);
}
}
for (size_t i = 0; i < closed_preview_tabs.size(); ++i) {
RemovePreviewTab(closed_preview_tabs[i]);
PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
closed_preview_tabs[i]->web_contents()->GetWebUI()->GetController());
if (print_preview_ui)
print_preview_ui->OnPrintPreviewTabClosed();
}
for (size_t i = 0; i < closed_initiator_tabs.size(); ++i)
RemoveInitiatorTab(closed_initiator_tabs[i]);
}
void PrintPreviewTabController::OnTabContentsDestroyed(TabContents* tab) {
TabContents* preview_tab = GetPrintPreviewForTab(tab);
if (!preview_tab) {
NOTREACHED();
return;
}
if (tab == preview_tab)
RemovePreviewTab(tab);
else
RemoveInitiatorTab(tab);
}
void PrintPreviewTabController::OnNavEntryCommitted(
TabContents* tab, content::LoadCommittedDetails* details) {
TabContents* preview_tab = GetPrintPreviewForTab(tab);
if (!preview_tab) {
NOTREACHED();
return;
}
bool source_tab_is_preview_tab = (tab == preview_tab);
if (source_tab_is_preview_tab) {
// Preview tab navigated.
if (details) {
content::PageTransition transition_type =
details->entry->GetTransitionType();
content::NavigationType nav_type = details->type;
// New |preview_tab| is created. Don't update/erase map entry.
if (waiting_for_new_preview_page_ &&
transition_type == content::PAGE_TRANSITION_AUTO_TOPLEVEL &&
nav_type == content::NAVIGATION_TYPE_NEW_PAGE) {
waiting_for_new_preview_page_ = false;
SetInitiatorTabURLAndTitle(preview_tab);
return;
}
// Cloud print sign-in causes a reload.
if (!waiting_for_new_preview_page_ &&
transition_type == content::PAGE_TRANSITION_RELOAD &&
nav_type == content::NAVIGATION_TYPE_EXISTING_PAGE &&
IsPrintPreviewURL(details->previous_url)) {
return;
}
}
NOTREACHED();
return;
}
RemoveInitiatorTab(tab);
}
TabContents* PrintPreviewTabController::CreatePrintPreviewTab(
TabContents* initiator_tab) {
AutoReset<bool> auto_reset(&is_creating_print_preview_tab_, true);
WebContents* web_contents = initiator_tab->web_contents();
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kChromeFrame)) {
Browser* current_browser = new Browser(
Browser::CreateParams(Browser::TYPE_POPUP, profile));
if (!current_browser) {
NOTREACHED() << "Failed to create popup browser window";
return NULL;
}
}
// |web_dialog_ui_delegate| deletes itself in
// PrintPreviewTabDelegate::OnDialogClosed().
WebDialogDelegate* web_dialog_delegate =
new PrintPreviewTabDelegate(initiator_tab);
// |web_tab_content_delegate|'s owner is |constrained_web_ui_delegate|.
PrintPreviewWebContentDelegate* pp_wcd =
new PrintPreviewWebContentDelegate(profile, initiator_tab);
ConstrainedWebDialogDelegate* constrained_delegate =
ui::CreateConstrainedWebDialog(
profile,
web_dialog_delegate,
pp_wcd,
initiator_tab);
TabContents* preview_tab = constrained_delegate->tab();
EnableInternalPDFPluginForTab(preview_tab);
CoreTabHelper::FromWebContents(preview_tab->web_contents())->
set_delegate(pp_wcd);
// Add an entry to the map.
preview_tab_map_[preview_tab] = initiator_tab;
waiting_for_new_preview_page_ = true;
AddObservers(initiator_tab);
AddObservers(preview_tab);
return preview_tab;
}
void PrintPreviewTabController::SetInitiatorTabURLAndTitle(
TabContents* preview_tab) {
TabContents* initiator_tab = GetInitiatorTab(preview_tab);
if (initiator_tab && preview_tab->web_contents()->GetWebUI()) {
PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
preview_tab->web_contents()->GetWebUI()->GetController());
print_preview_ui->SetInitiatorTabURLAndTitle(
initiator_tab->web_contents()->GetURL().spec(),
printing::PrintViewManager::FromWebContents(
initiator_tab->web_contents())->RenderSourceName());
}
}
void PrintPreviewTabController::AddObservers(TabContents* tab) {
WebContents* web_contents = tab->web_contents();
registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_DESTROYED,
content::Source<TabContents>(tab));
registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::Source<NavigationController>(&web_contents->GetController()));
// Multiple sites may share the same RenderProcessHost, so check if this
// notification has already been added.
content::Source<content::RenderProcessHost> rph_source(
web_contents->GetRenderProcessHost());
if (!registrar_.IsRegistered(this,
content::NOTIFICATION_RENDERER_PROCESS_CLOSED, rph_source)) {
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
rph_source);
}
}
void PrintPreviewTabController::RemoveObservers(TabContents* tab) {
WebContents* web_contents = tab->web_contents();
registrar_.Remove(this, chrome::NOTIFICATION_TAB_CONTENTS_DESTROYED,
content::Source<TabContents>(tab));
registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::Source<NavigationController>(&web_contents->GetController()));
// Multiple sites may share the same RenderProcessHost, so check if this
// notification has already been added.
content::Source<content::RenderProcessHost> rph_source(
web_contents->GetRenderProcessHost());
if (registrar_.IsRegistered(this,
content::NOTIFICATION_RENDERER_PROCESS_CLOSED, rph_source)) {
registrar_.Remove(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
rph_source);
}
}
void PrintPreviewTabController::RemoveInitiatorTab(TabContents* initiator_tab) {
TabContents* preview_tab = GetPrintPreviewForTab(initiator_tab);
DCHECK(preview_tab);
// Update the map entry first, so when the print preview tab gets destroyed
// and reaches RemovePreviewTab(), it does not attempt to also remove the
// initiator tab's observers.
preview_tab_map_[preview_tab] = NULL;
RemoveObservers(initiator_tab);
printing::PrintViewManager::FromWebContents(initiator_tab->web_contents())->
PrintPreviewDone();
// Initiator tab is closed. Close the print preview tab too.
PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
preview_tab->web_contents()->GetWebUI()->GetController());
if (print_preview_ui)
print_preview_ui->OnInitiatorTabClosed();
}
void PrintPreviewTabController::RemovePreviewTab(TabContents* preview_tab) {
// Remove the initiator tab's observers before erasing the mapping.
TabContents* initiator_tab = GetInitiatorTab(preview_tab);
if (initiator_tab) {
RemoveObservers(initiator_tab);
printing::PrintViewManager::FromWebContents(initiator_tab->web_contents())->
PrintPreviewDone();
}
// Print preview WebContents is destroyed. Notify |PrintPreviewUI| to abort
// the initiator tab preview request.
PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
preview_tab->web_contents()->GetWebUI()->GetController());
if (print_preview_ui)
print_preview_ui->OnTabDestroyed();
preview_tab_map_.erase(preview_tab);
RemoveObservers(preview_tab);
}
} // namespace printing