blob: 1d9d595685ce7d8e7e10a93b6fc114757bb931d9 [file] [log] [blame]
// 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/renderer/print_web_view_helper.h"
#include "app/l10n_util.h"
#include "base/gfx/size.h"
#include "base/logging.h"
#include "chrome/common/render_messages.h"
#include "chrome/renderer/render_view.h"
#include "grit/generated_resources.h"
#include "printing/native_metafile.h"
#include "skia/ext/vector_canvas.h"
#include "third_party/WebKit/WebKit/chromium/public/WebConsoleMessage.h"
#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h"
using WebKit::WebConsoleMessage;
using WebKit::WebFrame;
using WebKit::WebString;
void PrintWebViewHelper::Print(WebFrame* frame, bool script_initiated) {
const int kMinSecondsToIgnoreJavascriptInitiatedPrint = 2;
const int kMaxSecondsToIgnoreJavascriptInitiatedPrint = 2 * 60; // 2 Minutes.
// If still not finished with earlier print request simply ignore.
if (IsPrinting())
return;
// TODO(maruel): Move this out of platform specific code.
// Check if there is script repeatedly trying to print and ignore it if too
// frequent. We use exponential wait time so for a page that calls print() in
// a loop the user will need to cancel the print dialog after 2 seconds, 4
// seconds, 8, ... up to the maximum of 2 minutes.
// This gives the user time to navigate from the page.
if (script_initiated && (user_cancelled_scripted_print_count_ > 0)) {
base::TimeDelta diff = base::Time::Now() - last_cancelled_script_print_;
int min_wait_seconds = std::min(
kMinSecondsToIgnoreJavascriptInitiatedPrint <<
(user_cancelled_scripted_print_count_ - 1),
kMaxSecondsToIgnoreJavascriptInitiatedPrint);
if (diff.InSeconds() < min_wait_seconds) {
WebString message(WebString::fromUTF8(
"Ignoring too frequent calls to print()."));
frame->addMessageToConsole(WebConsoleMessage(
WebConsoleMessage::LevelWarning,
message));
return;
}
}
// Retrieve the default print settings to calculate the expected number of
// pages.
ViewMsg_Print_Params default_settings;
bool user_cancelled_print = false;
IPC::SyncMessage* msg =
new ViewHostMsg_GetDefaultPrintSettings(routing_id(), &default_settings);
if (Send(msg)) {
msg = NULL;
// Check if the printer returned any settings, if the settings is empty, we
// can safely assume there are no printer drivers configured. So we safely
// terminate.
if (default_settings.IsEmpty()) {
// TODO: Create an async alert (http://crbug.com/14918).
render_view_->runModalAlertDialog(frame,
l10n_util::GetString(IDS_DEFAULT_PRINTER_NOT_FOUND_WARNING));
return;
}
// Continue only if the settings are valid.
if (default_settings.dpi && default_settings.document_cookie) {
int expected_pages_count = 0;
// Prepare once to calculate the estimated page count. This must be in
// a scope for itself (see comments on PrepareFrameAndViewForPrint).
{
PrepareFrameAndViewForPrint prep_frame_view(default_settings,
frame,
frame->view());
expected_pages_count = prep_frame_view.GetExpectedPageCount();
DCHECK(expected_pages_count);
}
// Ask the browser to show UI to retrieve the final print settings.
ViewMsg_PrintPages_Params print_settings;
ViewHostMsg_ScriptedPrint_Params params;
// The routing id is sent across as it is needed to look up the
// corresponding RenderViewHost instance to signal and reset the
// pump messages event.
params.routing_id = routing_id();
// host_window_ may be NULL at this point if the current window is a popup
// and the print() command has been issued from the parent. The receiver
// of this message has to deal with this.
params.host_window_id = render_view_->host_window();
params.cookie = default_settings.document_cookie;
// TODO(maruel): Reenable once http://crbug.com/22937 is fixed.
// Print selection is broken because DidStopLoading is never called.
//params.has_selection = frame->hasSelection();
params.has_selection = false;
params.expected_pages_count = expected_pages_count;
msg = new ViewHostMsg_ScriptedPrint(params, &print_settings);
if (render_view_->SendAndRunNestedMessageLoop(msg)) {
msg = NULL;
// If the settings are invalid, early quit.
if (print_settings.params.dpi &&
print_settings.params.document_cookie) {
if (print_settings.params.selection_only) {
CopyAndPrint(print_settings, frame);
} else {
// TODO: Always copy before printing.
PrintPages(print_settings, frame);
}
// Reset cancel counter on first successful print.
user_cancelled_scripted_print_count_ = 0;
return; // All went well.
} else {
user_cancelled_print = true;
}
} else {
// Send() failed.
NOTREACHED();
}
} else {
// Failed to get default settings.
NOTREACHED();
}
} else {
// Send() failed.
NOTREACHED();
}
if (script_initiated && user_cancelled_print) {
++user_cancelled_scripted_print_count_;
last_cancelled_script_print_ = base::Time::Now();
}
// When |user_cancelled_print| is true, we treat it as success so that
// DidFinishPrinting() won't show any error alert.
// If |user_cancelled_print| is false and we reach here, there must be
// something wrong and hence is not success, DidFinishPrinting() should show
// an error alert.
// In both cases, we have to call DidFinishPrinting() here to release
// printing resources, since we do need them anymore.
DidFinishPrinting(user_cancelled_print);
}
void PrintWebViewHelper::PrintPages(const ViewMsg_PrintPages_Params& params,
WebFrame* frame) {
PrepareFrameAndViewForPrint prep_frame_view(params.params,
frame,
frame->view());
int page_count = prep_frame_view.GetExpectedPageCount();
Send(new ViewHostMsg_DidGetPrintedPagesCount(routing_id(),
params.params.document_cookie,
page_count));
if (page_count) {
ViewMsg_PrintPage_Params page_params;
page_params.params = params.params;
if (params.pages.empty()) {
for (int i = 0; i < page_count; ++i) {
page_params.page_number = i;
PrintPage(page_params, prep_frame_view.GetPrintCanvasSize(), frame);
}
} else {
for (size_t i = 0; i < params.pages.size(); ++i) {
page_params.page_number = params.pages[i];
PrintPage(page_params, prep_frame_view.GetPrintCanvasSize(), frame);
}
}
}
}
void PrintWebViewHelper::PrintPage(const ViewMsg_PrintPage_Params& params,
const gfx::Size& canvas_size,
WebFrame* frame) {
// Generate a memory-based metafile. It will use the current screen's DPI.
printing::NativeMetafile metafile;
metafile.CreateDc(NULL, NULL);
HDC hdc = metafile.hdc();
DCHECK(hdc);
skia::PlatformDevice::InitializeDC(hdc);
// Since WebKit extends the page width depending on the magical shrink
// factor we make sure the canvas covers the worst case scenario
// (x2.0 currently). PrintContext will then set the correct clipping region.
int size_x = static_cast<int>(canvas_size.width() * params.params.max_shrink);
int size_y = static_cast<int>(canvas_size.height() *
params.params.max_shrink);
// Calculate the dpi adjustment.
float shrink = static_cast<float>(canvas_size.width()) /
params.params.printable_size.width();
#if 0
// TODO(maruel): This code is kept for testing until the 100% GDI drawing
// code is stable. maruels use this code's output as a reference when the
// GDI drawing code fails.
// Mix of Skia and GDI based.
skia::PlatformCanvas canvas(size_x, size_y, true);
canvas.drawARGB(255, 255, 255, 255, SkPorterDuff::kSrc_Mode);
float webkit_shrink = frame->PrintPage(params.page_number, &canvas);
if (shrink <= 0 || webkit_shrink <= 0) {
NOTREACHED() << "Printing page " << params.page_number << " failed.";
} else {
// Update the dpi adjustment with the "page shrink" calculated in webkit.
shrink /= webkit_shrink;
}
// Create a BMP v4 header that we can serialize.
BITMAPV4HEADER bitmap_header;
gfx::CreateBitmapV4Header(size_x, size_y, &bitmap_header);
const SkBitmap& src_bmp = canvas.getDevice()->accessBitmap(true);
SkAutoLockPixels src_lock(src_bmp);
int retval = StretchDIBits(hdc,
0,
0,
size_x, size_y,
0, 0,
size_x, size_y,
src_bmp.getPixels(),
reinterpret_cast<BITMAPINFO*>(&bitmap_header),
DIB_RGB_COLORS,
SRCCOPY);
DCHECK(retval != GDI_ERROR);
#else
// 100% GDI based.
skia::VectorCanvas canvas(hdc, size_x, size_y);
float webkit_shrink = frame->printPage(params.page_number, &canvas);
if (shrink <= 0 || webkit_shrink <= 0) {
NOTREACHED() << "Printing page " << params.page_number << " failed.";
} else {
// Update the dpi adjustment with the "page shrink" calculated in webkit.
shrink /= webkit_shrink;
}
#endif
// Done printing. Close the device context to retrieve the compiled metafile.
if (!metafile.CloseDc()) {
NOTREACHED() << "metafile failed";
}
// Get the size of the compiled metafile.
unsigned buf_size = metafile.GetDataSize();
DCHECK_GT(buf_size, 128u);
ViewHostMsg_DidPrintPage_Params page_params;
page_params.data_size = 0;
page_params.metafile_data_handle = NULL;
page_params.page_number = params.page_number;
page_params.document_cookie = params.params.document_cookie;
page_params.actual_shrink = shrink;
base::SharedMemory shared_buf;
// http://msdn2.microsoft.com/en-us/library/ms535522.aspx
// Windows 2000/XP: When a page in a spooled file exceeds approximately 350
// MB, it can fail to print and not send an error message.
if (buf_size < 350*1024*1024) {
// Allocate a shared memory buffer to hold the generated metafile data.
if (shared_buf.Create(L"", false, false, buf_size) &&
shared_buf.Map(buf_size)) {
// Copy the bits into shared memory.
if (metafile.GetData(shared_buf.memory(), buf_size)) {
page_params.metafile_data_handle = shared_buf.handle();
page_params.data_size = buf_size;
} else {
NOTREACHED() << "GetData() failed";
}
shared_buf.Unmap();
} else {
NOTREACHED() << "Buffer allocation failed";
}
} else {
NOTREACHED() << "Buffer too large: " << buf_size;
}
metafile.CloseEmf();
if (Send(new ViewHostMsg_DuplicateSection(
routing_id(),
page_params.metafile_data_handle,
&page_params.metafile_data_handle))) {
Send(new ViewHostMsg_DidPrintPage(routing_id(), page_params));
}
}