#include "chrome/browser/printing/print_view_manager.h"
#include <map>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h"
#include "base/timer.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/printing/print_error_dialog.h"
#include "chrome/browser/printing/print_job.h"
#include "chrome/browser/printing/print_job_manager.h"
#include "chrome/browser/printing/print_preview_tab_controller.h"
#include "chrome/browser/printing/print_view_manager_observer.h"
#include "chrome/browser/printing/printer_query.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_ui.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/print_messages.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "grit/generated_resources.h"
#include "printing/metafile.h"
#include "printing/metafile_impl.h"
#include "printing/print_destination_interface.h"
#include "printing/printed_document.h"
#include "ui/base/l10n/l10n_util.h"
using base::TimeDelta;
using content::BrowserThread;
int printing::PrintViewManager::kUserDataKey;
namespace {
typedef std::map<content::RenderProcessHost*, base::Closure>
ScriptedPrintPreviewClosureMap;
static base::LazyInstance<ScriptedPrintPreviewClosureMap>
g_scripted_print_preview_closure_map = LAZY_INSTANCE_INITIALIZER;
const int kMaxRasterSizeInPixels = 16*1024*1024;
}
namespace printing {
PrintViewManager::PrintViewManager(content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
number_pages_(0),
printing_succeeded_(false),
inside_inner_message_loop_(false),
observer_(NULL),
cookie_(0),
print_preview_state_(NOT_PREVIEWING),
scripted_print_preview_rph_(NULL),
tab_content_blocked_(false) {
#if defined(OS_POSIX) && !defined(OS_MACOSX)
expecting_first_page_ = true;
#endif
registrar_.Add(this, chrome::NOTIFICATION_CONTENT_BLOCKED_STATE_CHANGED,
content::Source<content::WebContents>(web_contents));
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
printing_enabled_.Init(prefs::kPrintingEnabled,
profile->GetPrefs(),
this);
}
PrintViewManager::~PrintViewManager() {
DCHECK_EQ(NOT_PREVIEWING, print_preview_state_);
ReleasePrinterQuery();
DisconnectFromCurrentPrintJob();
}
bool PrintViewManager::PrintNow() {
return PrintNowInternal(new PrintMsg_PrintPages(routing_id()));
}
bool PrintViewManager::PrintForSystemDialogNow() {
return PrintNowInternal(new PrintMsg_PrintForSystemDialog(routing_id()));
}
bool PrintViewManager::AdvancedPrintNow() {
PrintPreviewTabController* tab_controller =
PrintPreviewTabController::GetInstance();
if (!tab_controller)
return false;
TabContents* print_preview_tab = tab_controller->GetPrintPreviewForTab(
TabContents::FromWebContents(web_contents()));
if (print_preview_tab) {
if (!print_preview_tab->web_contents()->GetWebUI())
return false;
PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(
print_preview_tab->web_contents()->GetWebUI()->GetController());
print_preview_ui->OnShowSystemDialog();
return true;
} else {
return PrintNow();
}
}
bool PrintViewManager::PrintToDestination() {
UMA_HISTOGRAM_ENUMERATION("Metro.Print", 0, 2);
g_browser_process->print_job_manager()->SetPrintDestination(
printing::CreatePrintDestination());
return PrintNowInternal(new PrintMsg_PrintPages(routing_id()));
}
bool PrintViewManager::PrintPreviewNow() {
if (print_preview_state_ != NOT_PREVIEWING)
return false;
if (!PrintNowInternal(new PrintMsg_InitiatePrintPreview(routing_id())))
return false;
print_preview_state_ = USER_INITIATED_PREVIEW;
return true;
}
void PrintViewManager::PrintPreviewForWebNode() {
DCHECK_EQ(NOT_PREVIEWING, print_preview_state_);
print_preview_state_ = USER_INITIATED_PREVIEW;
}
void PrintViewManager::PrintPreviewDone() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK_NE(NOT_PREVIEWING, print_preview_state_);
if (print_preview_state_ == SCRIPTED_PREVIEW) {
ScriptedPrintPreviewClosureMap& map =
g_scripted_print_preview_closure_map.Get();
ScriptedPrintPreviewClosureMap::iterator it =
map.find(scripted_print_preview_rph_);
CHECK(it != map.end());
it->second.Run();
map.erase(scripted_print_preview_rph_);
scripted_print_preview_rph_ = NULL;
}
print_preview_state_ = NOT_PREVIEWING;
}
void PrintViewManager::UpdateScriptedPrintingBlocked() {
Send(new PrintMsg_SetScriptedPrintingBlocked(
routing_id(),
!printing_enabled_.GetValue() || tab_content_blocked_));
}
void PrintViewManager::set_observer(PrintViewManagerObserver* observer) {
DCHECK(!observer || !observer_);
observer_ = observer;
}
void PrintViewManager::StopNavigation() {
TerminatePrintJob(true);
}
void PrintViewManager::RenderViewGone(base::TerminationStatus status) {
print_preview_state_ = NOT_PREVIEWING;
ReleasePrinterQuery();
if (!print_job_.get())
return;
scoped_refptr<PrintedDocument> document(print_job_->document());
if (document) {
TerminatePrintJob(!document->IsComplete());
}
}
string16 PrintViewManager::RenderSourceName() {
string16 name(web_contents()->GetTitle());
if (name.empty())
name = l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE);
return name;
}
void PrintViewManager::OnDidGetPrintedPagesCount(int cookie, int number_pages) {
DCHECK_GT(cookie, 0);
DCHECK_GT(number_pages, 0);
number_pages_ = number_pages;
OpportunisticallyCreatePrintJob(cookie);
}
void PrintViewManager::OnDidGetDocumentCookie(int cookie) {
cookie_ = cookie;
}
void PrintViewManager::OnDidShowPrintDialog() {
if (observer_)
observer_->OnPrintDialogShown();
}
void PrintViewManager::OnDidPrintPage(
const PrintHostMsg_DidPrintPage_Params& params) {
if (!OpportunisticallyCreatePrintJob(params.document_cookie))
return;
PrintedDocument* document = print_job_->document();
if (!document || params.document_cookie != document->cookie()) {
return;
}
#if defined(OS_WIN) || defined(OS_MACOSX)
const bool metafile_must_be_valid = true;
#elif defined(OS_POSIX)
const bool metafile_must_be_valid = expecting_first_page_;
expecting_first_page_ = false;
#endif
base::SharedMemory shared_buf(params.metafile_data_handle, true);
if (metafile_must_be_valid) {
if (!shared_buf.Map(params.data_size)) {
NOTREACHED() << "couldn't map";
web_contents()->Stop();
return;
}
}
scoped_ptr<NativeMetafile> metafile(new NativeMetafile);
if (metafile_must_be_valid) {
if (!metafile->InitFromData(shared_buf.memory(), params.data_size)) {
NOTREACHED() << "Invalid metafile header";
web_contents()->Stop();
return;
}
}
#if defined(OS_WIN)
bool big_emf = (params.data_size && params.data_size >= kMetafileMaxSize);
const CommandLine* cmdline = CommandLine::ForCurrentProcess();
if (big_emf ||
(cmdline && cmdline->HasSwitch(switches::kPrintRaster)) ||
(!print_job_->settings().supports_alpha_blend() &&
metafile->IsAlphaBlendUsed())) {
int raster_size = std::min(params.page_size.GetArea(),
kMaxRasterSizeInPixels);
scoped_ptr<NativeMetafile> raster_metafile(
metafile->RasterizeMetafile(raster_size));
if (raster_metafile.get()) {
metafile.swap(raster_metafile);
} else if (big_emf) {
NOTREACHED() << "size:" << params.data_size;
TerminatePrintJob(true);
web_contents()->Stop();
return;
}
}
#endif
document->SetPage(params.page_number,
metafile.release(),
params.actual_shrink,
params.page_size,
params.content_area);
ShouldQuitFromInnerMessageLoop();
}
void PrintViewManager::OnPrintingFailed(int cookie) {
if (cookie != cookie_) {
NOTREACHED();
return;
}
chrome::ShowPrintErrorDialog(
web_contents()->GetView()->GetTopLevelNativeWindow());
ReleasePrinterQuery();
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_PRINT_JOB_RELEASED,
content::Source<TabContents>(
TabContents::FromWebContents(web_contents())),
content::NotificationService::NoDetails());
}
void PrintViewManager::OnScriptedPrintPreview(bool source_is_modifiable,
IPC::Message* reply_msg) {
BrowserThread::CurrentlyOn(BrowserThread::UI);
ScriptedPrintPreviewClosureMap& map =
g_scripted_print_preview_closure_map.Get();
content::RenderProcessHost* rph = web_contents()->GetRenderProcessHost();
if (map.count(rph) != 0) {
Send(reply_msg);
return;
}
if (print_preview_state_ != NOT_PREVIEWING) {
DCHECK_EQ(USER_INITIATED_PREVIEW, print_preview_state_);
Send(reply_msg);
return;
}
PrintPreviewTabController* tab_controller =
PrintPreviewTabController::GetInstance();
if (!tab_controller) {
Send(reply_msg);
return;
}
print_preview_state_ = SCRIPTED_PREVIEW;
base::Closure callback =
base::Bind(&PrintViewManager::OnScriptedPrintPreviewReply,
base::Unretained(this),
reply_msg);
map[rph] = callback;
scripted_print_preview_rph_ = rph;
TabContents* tab_contents = TabContents::FromWebContents(web_contents());
tab_controller->PrintPreview(tab_contents);
PrintPreviewUI::SetSourceIsModifiable(
tab_controller->GetPrintPreviewForTab(tab_contents),
source_is_modifiable);
}
void PrintViewManager::OnScriptedPrintPreviewReply(IPC::Message* reply_msg) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
Send(reply_msg);
}
void PrintViewManager::DidStartLoading(
content::RenderViewHost* render_view_host) {
UpdateScriptedPrintingBlocked();
}
bool PrintViewManager::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(PrintViewManager, message)
IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetPrintedPagesCount,
OnDidGetPrintedPagesCount)
IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetDocumentCookie,
OnDidGetDocumentCookie)
IPC_MESSAGE_HANDLER(PrintHostMsg_DidShowPrintDialog, OnDidShowPrintDialog)
IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintPage, OnDidPrintPage)
IPC_MESSAGE_HANDLER(PrintHostMsg_PrintingFailed, OnPrintingFailed)
IPC_MESSAGE_HANDLER_DELAY_REPLY(PrintHostMsg_ScriptedPrintPreview,
OnScriptedPrintPreview)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void PrintViewManager::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case chrome::NOTIFICATION_PRINT_JOB_EVENT: {
OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
break;
}
case chrome::NOTIFICATION_PREF_CHANGED: {
UpdateScriptedPrintingBlocked();
break;
}
case chrome::NOTIFICATION_CONTENT_BLOCKED_STATE_CHANGED: {
tab_content_blocked_ = *content::Details<const bool>(details).ptr();
UpdateScriptedPrintingBlocked();
break;
}
default: {
NOTREACHED();
break;
}
}
}
void PrintViewManager::OnNotifyPrintJobEvent(
const JobEventDetails& event_details) {
switch (event_details.type()) {
case JobEventDetails::FAILED: {
TerminatePrintJob(true);
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_PRINT_JOB_RELEASED,
content::Source<TabContents>(
TabContents::FromWebContents(web_contents())),
content::NotificationService::NoDetails());
break;
}
case JobEventDetails::USER_INIT_DONE:
case JobEventDetails::DEFAULT_INIT_DONE:
case JobEventDetails::USER_INIT_CANCELED: {
NOTREACHED();
break;
}
case JobEventDetails::ALL_PAGES_REQUESTED: {
ShouldQuitFromInnerMessageLoop();
break;
}
case JobEventDetails::NEW_DOC:
case JobEventDetails::NEW_PAGE:
case JobEventDetails::PAGE_DONE:
case JobEventDetails::DOC_DONE: {
break;
}
case JobEventDetails::JOB_DONE: {
printing_succeeded_ = true;
ReleasePrintJob();
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_PRINT_JOB_RELEASED,
content::Source<TabContents>(
TabContents::FromWebContents(web_contents())),
content::NotificationService::NoDetails());
break;
}
default: {
NOTREACHED();
break;
}
}
}
bool PrintViewManager::RenderAllMissingPagesNow() {
if (!print_job_.get() || !print_job_->is_job_pending())
return false;
if (!web_contents() ||
!web_contents()->GetRenderViewHost() ||
!web_contents()->GetRenderViewHost()->IsRenderViewLive()) {
return false;
}
if (print_job_->document() && print_job_->document()->IsComplete()) {
printing_succeeded_ = true;
return true;
}
if (!RunInnerMessageLoop()) {
return false;
}
return true;
}
void PrintViewManager::ShouldQuitFromInnerMessageLoop() {
DCHECK(print_job_->document());
if (print_job_->document() &&
print_job_->document()->IsComplete() &&
inside_inner_message_loop_) {
MessageLoop::current()->Quit();
inside_inner_message_loop_ = false;
}
}
bool PrintViewManager::CreateNewPrintJob(PrintJobWorkerOwner* job) {
DCHECK(!inside_inner_message_loop_);
DisconnectFromCurrentPrintJob();
if (!web_contents()->GetRenderViewHost() ||
!web_contents()->GetRenderViewHost()->IsRenderViewLive()) {
return false;
}
DCHECK(!print_job_.get());
DCHECK(job);
if (!job)
return false;
print_job_ = new PrintJob();
print_job_->Initialize(job, this, number_pages_);
registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
content::Source<PrintJob>(print_job_.get()));
printing_succeeded_ = false;
return true;
}
void PrintViewManager::DisconnectFromCurrentPrintJob() {
bool result = RenderAllMissingPagesNow();
if (print_job_.get() &&
print_job_->document() &&
!print_job_->document()->IsComplete()) {
DCHECK(!result);
TerminatePrintJob(true);
} else {
ReleasePrintJob();
}
#if defined(OS_POSIX) && !defined(OS_MACOSX)
expecting_first_page_ = true;
#endif
}
void PrintViewManager::PrintingDone(bool success) {
if (!print_job_.get())
return;
Send(new PrintMsg_PrintingDone(routing_id(), success));
}
void PrintViewManager::TerminatePrintJob(bool cancel) {
if (!print_job_.get())
return;
if (cancel) {
print_job_->Cancel();
inside_inner_message_loop_ = false;
} else {
DCHECK(!inside_inner_message_loop_);
DCHECK(!print_job_->document() || print_job_->document()->IsComplete());
print_job_->Stop();
}
ReleasePrintJob();
}
void PrintViewManager::ReleasePrintJob() {
if (!print_job_.get())
return;
PrintingDone(printing_succeeded_);
registrar_.Remove(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
content::Source<PrintJob>(print_job_.get()));
print_job_->DisconnectSource();
print_job_ = NULL;
}
bool PrintViewManager::RunInnerMessageLoop() {
static const int kPrinterSettingsTimeout = 60000;
base::OneShotTimer<MessageLoop> quit_timer;
quit_timer.Start(FROM_HERE,
TimeDelta::FromMilliseconds(kPrinterSettingsTimeout),
MessageLoop::current(), &MessageLoop::Quit);
inside_inner_message_loop_ = true;
{
MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
MessageLoop::current()->Run();
}
bool success = true;
if (inside_inner_message_loop_) {
inside_inner_message_loop_ = false;
success = false;
}
return success;
}
bool PrintViewManager::OpportunisticallyCreatePrintJob(int cookie) {
if (print_job_.get())
return true;
if (!cookie) {
return false;
}
scoped_refptr<PrinterQuery> queued_query;
g_browser_process->print_job_manager()->PopPrinterQuery(cookie,
&queued_query);
DCHECK(queued_query.get());
if (!queued_query.get())
return false;
if (!CreateNewPrintJob(queued_query.get())) {
return false;
}
print_job_->StartPrinting();
return true;
}
bool PrintViewManager::PrintNowInternal(IPC::Message* message) {
if (web_contents()->ShowingInterstitialPage()) {
delete message;
return false;
}
return Send(message);
}
void PrintViewManager::ReleasePrinterQuery() {
if (!cookie_)
return;
int cookie = cookie_;
cookie_ = 0;
g_browser_process->print_job_manager()->SetPrintDestination(NULL);
printing::PrintJobManager* print_job_manager =
g_browser_process->print_job_manager();
if (!print_job_manager)
return;
scoped_refptr<printing::PrinterQuery> printer_query;
print_job_manager->PopPrinterQuery(cookie, &printer_query);
if (!printer_query.get())
return;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&PrinterQuery::StopWorker, printer_query.get()));
}
}