0

"Simplify Page" print preview option enables

preprocessing of the page using the DOM Distiller.
Rendering happens in a hidden web contents, that lives
in parallel with the originally printed contents.

Intent to Implement: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-dev/_-zEoPLFKp0

BUG=490809
R=alekseys@chromium.org, avi@chromium.org, jochen@chromium.org, nyquist@chromium.org, vitalybuka@chromium.org

Review URL: https://codereview.chromium.org/1125343004 .

Patch from Arjun Patel <arjunpatel@hp.com>.

Cr-Commit-Position: refs/heads/master@{#343263}
This commit is contained in:
Arjun Patel
2015-08-13 12:59:03 -07:00
committed by Vitaly Buka
parent d3bce9f334
commit ab26826ebd
22 changed files with 641 additions and 9 deletions

@ -7887,7 +7887,7 @@ Keep your key file in a safe place. You will need it to create new versions of y
</message>
<message name="IDS_PASSWORD_MANAGER_UPDATE_BUTTON" desc="Update button text for password manager">
Update password
</message>
</message>
<message name="IDS_PASSWORD_MANAGER_UNBLACKLIST_BUTTON" desc="Buton text to re-enable the password manager after blacklisting.">
Undo
</message>
@ -8865,6 +8865,9 @@ I don't think this site should be blocked!
<message name="IDS_PRINT_PREVIEW_OPTIONS_LABEL" desc="Options label currently providing the choice to print headers and footers.">
Options
</message>
<message name="IDS_PRINT_PREVIEW_OPTION_DISTILL_PAGE" desc="Checkbox label that provides a choice to print a simplified version of the page.">
Simplify page
</message>
<message name="IDS_PRINT_PREVIEW_OPTION_HEADER_FOOTER" desc="Checkbox label that provides a choice to print the headers and footers.">
Headers and footers
</message>

@ -183,6 +183,14 @@ WebContents* PrintPreviewDialogController::GetOrCreatePreviewDialog(
WebContents* PrintPreviewDialogController::GetPrintPreviewForContents(
WebContents* contents) const {
// If this WebContents relies on another for its preview dialog, we
// need to act as if we are looking for the proxied content's dialog.
PrintPreviewDialogMap::const_iterator proxied =
proxied_dialog_map_.find(contents);
if (proxied != proxied_dialog_map_.end()) {
contents = proxied->second;
}
// |preview_dialog_map_| is keyed by the preview dialog, so if find()
// succeeds, then |contents| is the preview dialog.
PrintPreviewDialogMap::const_iterator it = preview_dialog_map_.find(contents);
@ -378,6 +386,17 @@ WebContents* PrintPreviewDialogController::CreatePrintPreviewDialog(
return preview_dialog;
}
void PrintPreviewDialogController::AddProxyDialogForWebContents(
WebContents* source,
WebContents* target) {
proxied_dialog_map_[source] = target;
}
void PrintPreviewDialogController::RemoveProxyDialogForWebContents(
WebContents* source) {
proxied_dialog_map_.erase(source);
}
void PrintPreviewDialogController::SaveInitiatorTitle(
WebContents* preview_dialog) {
WebContents* initiator = GetInitiator(preview_dialog);

@ -79,6 +79,11 @@ class PrintPreviewDialogController
return is_creating_print_preview_dialog_;
}
void AddProxyDialogForWebContents(content::WebContents* source,
content::WebContents* target);
void RemoveProxyDialogForWebContents(content::WebContents* source);
private:
friend class base::RefCounted<PrintPreviewDialogController>;
@ -122,6 +127,8 @@ class PrintPreviewDialogController
// Mapping between print preview dialog and the corresponding initiator.
PrintPreviewDialogMap preview_dialog_map_;
PrintPreviewDialogMap proxied_dialog_map_;
// A registrar for listening to notifications.
content::NotificationRegistrar registrar_;

@ -184,6 +184,14 @@ cr.define('print_preview', function() {
this.selectionOnly_ =
new print_preview.ticket_items.SelectionOnly(this.documentInfo_);
/**
* Print friendly ticket item.
* @type {!print_preview.ticket_items.DistillPage}
* @private
*/
this.distillPage_ = new print_preview.ticket_items.DistillPage(
this.documentInfo_);
/**
* Vendor ticket items.
* @type {!print_preview.ticket_items.VendorItems}
@ -267,6 +275,10 @@ cr.define('print_preview', function() {
return this.headerFooter_;
},
get distillPage() {
return this.distillPage_;
},
get mediaSize() {
return this.mediaSize_;
},

@ -0,0 +1,63 @@
// Copyright 2015 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.
cr.define('print_preview.ticket_items', function() {
'use strict';
/**
* Ticket item whose value is a {@code boolean} that represents whether to
* distill the page before printing.
* @param {!print_preview.DocumentInfo} documentInfo Information about the
* document to print.
* @constructor
* @extends {print_preview.ticket_items.TicketItem}
*/
function DistillPage(documentInfo) {
print_preview.ticket_items.TicketItem.call(
this,
null /*appState*/,
null /*field*/,
null /*destinationStore*/,
documentInfo);
this.isAvailable_ = false;
};
DistillPage.prototype = {
__proto__: print_preview.ticket_items.TicketItem.prototype,
/** @override */
wouldValueBeValid: function(value) {
return true;
},
/** @override */
isCapabilityAvailable: function() {
return this.isAvailable_;
},
/** @override */
getDefaultValueInternal: function() {
return false;
},
/** @override */
getCapabilityNotAvailableValueInternal: function() {
return false;
},
setIsCapabilityAvailable: function(isAvailable) {
if (this.isAvailable_ == isAvailable)
return;
this.isAvailable_ = isAvailable;
this.dispatchChangeEventInternal();
}
};
// Export
return {
DistillPage: DistillPage
};
});

@ -67,6 +67,8 @@ cr.define('print_preview', function() {
this.onEnableManipulateSettingsForTest_.bind(this);
global.printPresetOptionsFromDocument =
this.onPrintPresetOptionsFromDocument_.bind(this);
global.detectDistillablePage =
this.detectDistillablePage_.bind(this);
global.onProvisionalPrinterResolved =
this.onProvisionalDestinationResolved_.bind(this);
global.failedToResolveProvisionalPrinter =
@ -80,6 +82,7 @@ cr.define('print_preview', function() {
*/
NativeLayer.EventType = {
ACCESS_TOKEN_READY: 'print_preview.NativeLayer.ACCESS_TOKEN_READY',
ALLOW_DISTILL_PAGE: 'print_preview.NativeLayer.ALLOW_DISTILL_PAGE',
CAPABILITIES_SET: 'print_preview.NativeLayer.CAPABILITIES_SET',
CLOUD_PRINT_ENABLE: 'print_preview.NativeLayer.CLOUD_PRINT_ENABLE',
DESTINATIONS_RELOAD: 'print_preview.NativeLayer.DESTINATIONS_RELOAD',
@ -273,6 +276,7 @@ cr.define('print_preview', function() {
'landscape': printTicketStore.landscape.getValue(),
'color': this.getNativeColorModel_(destination, printTicketStore.color),
'headerFooterEnabled': printTicketStore.headerFooter.getValue(),
'distillPage': printTicketStore.distillPage.getValue(),
'marginsType': printTicketStore.marginsType.getValue(),
'isFirstRequest': requestId == 0,
'requestID': requestId,
@ -735,6 +739,19 @@ cr.define('print_preview', function() {
this.dispatchEvent(printPresetOptionsEvent);
},
/**
* Updates the interface to show the "Distill Page" option
* when PrintPreviewHandler::HandleIsPageDistillableResult
* determines that this page can be distilled with the
* DOM Distiller.
* @private
*/
detectDistillablePage_: function() {
var allowDistillPageEvent = new Event(
NativeLayer.EventType.ALLOW_DISTILL_PAGE);
this.dispatchEvent(allowDistillPageEvent);
},
/**
* Simulates a user click on the print preview dialog cancel button. Used
* only for testing.

@ -71,6 +71,13 @@ cr.define('print_preview', function() {
*/
this.isLandscapeEnabled_ = false;
/**
* Whether the previews are being generated from a distilled page.
* @type {boolean}
* @private
*/
this.isDistillPageEnabled_ = false;
/**
* Whether the previews are being generated with a header and footer.
* @type {boolean}
@ -175,6 +182,8 @@ cr.define('print_preview', function() {
this.isLandscapeEnabled_ = this.printTicketStore_.landscape.getValue();
this.isHeaderFooterEnabled_ =
this.printTicketStore_.headerFooter.getValue();
this.isDistillPageEnabled_ =
this.printTicketStore_.distillPage.getValue();
this.colorValue_ = this.printTicketStore_.color.getValue();
this.isFitToPageEnabled_ = this.printTicketStore_.fitToPage.getValue();
this.pageRanges_ = this.printTicketStore_.pageRange.getPageRanges();
@ -274,6 +283,8 @@ cr.define('print_preview', function() {
!ticketStore.mediaSize.isValueEqual(this.mediaSize_) ||
!ticketStore.landscape.isValueEqual(this.isLandscapeEnabled_) ||
!ticketStore.headerFooter.isValueEqual(this.isHeaderFooterEnabled_) ||
!ticketStore.distillPage.isValueEqual(
this.isDistillPageEnabled_) ||
!ticketStore.color.isValueEqual(this.colorValue_) ||
!ticketStore.fitToPage.isValueEqual(this.isFitToPageEnabled_) ||
this.pageRanges_ == null ||

@ -349,6 +349,10 @@ cr.define('print_preview', function() {
this.printTicketStore_.fitToPage,
print_preview.ticket_items.TicketItem.EventType.CHANGE,
this.onTicketChange_.bind(this));
this.tracker.add(
this.printTicketStore_.distillPage,
print_preview.ticket_items.TicketItem.EventType.CHANGE,
this.onTicketChange_.bind(this));
this.tracker.add(
this.printTicketStore_.headerFooter,
print_preview.ticket_items.TicketItem.EventType.CHANGE,

@ -169,7 +169,8 @@ cr.define('print_preview', function() {
this.printTicketStore_.fitToPage,
this.printTicketStore_.cssBackground,
this.printTicketStore_.selectionOnly,
this.printTicketStore_.headerFooter);
this.printTicketStore_.headerFooter,
this.printTicketStore_.distillPage);
this.addChild(this.otherOptionsSettings_);
/**
@ -357,6 +358,10 @@ cr.define('print_preview', function() {
this.nativeLayer_,
print_preview.NativeLayer.EventType.MANIPULATE_SETTINGS_FOR_TEST,
this.onManipulateSettingsForTest_.bind(this));
this.tracker.add(
this.nativeLayer_,
print_preview.NativeLayer.EventType.ALLOW_DISTILL_PAGE,
this.onAllowDistillPage_.bind(this));
if ($('system-dialog-link')) {
this.tracker.add(
@ -1013,6 +1018,15 @@ cr.define('print_preview', function() {
loadTimeData.getString('couldNotPrint'));
},
/**
* Called when the native layer has detected that the "Distill page"
* option should be allowed.
* @private
*/
onAllowDistillPage_: function(event) {
this.printTicketStore_.distillPage.setIsCapabilityAvailable(true);
},
/**
* Called when the print preview settings need to be changed for testing.
* @param {Event} event Event object that contains the option that is to
@ -1270,6 +1284,7 @@ cr.define('print_preview', function() {
<include src="data/ticket_items/dpi.js">
<include src="data/ticket_items/duplex.js">
<include src="data/ticket_items/header_footer.js">
<include src="data/ticket_items/distill_page.js">
<include src="data/ticket_items/media_size.js">
<include src="data/ticket_items/landscape.js">
<include src="data/ticket_items/margins_type.js">

@ -5,6 +5,12 @@
</div>
<div class="right-column checkbox">
<div id="other-options-collapsible" class="collapsible">
<div class="distill-page-container checkbox">
<label aria-live="polite">
<input class="distill-page-checkbox" type="checkbox">
<span i18n-content="optionDistillPage"></span>
</label>
</div>
<div class="header-footer-container checkbox">
<label aria-live="polite">
<input class="header-footer-checkbox" type="checkbox">

@ -16,11 +16,14 @@ cr.define('print_preview', function() {
* only ticket item.
* @param {!print_preview.ticket_items.HeaderFooter} headerFooter Header
* footer ticket item.
* @param {!print_preview.ticket_items.DistillPage} distillPage Print
* distill page ticket item.
* @constructor
* @extends {print_preview.SettingsSection}
*/
function OtherOptionsSettings(
duplex, fitToPage, cssBackground, selectionOnly, headerFooter) {
duplex, fitToPage, cssBackground, selectionOnly,
headerFooter, distillPage) {
print_preview.SettingsSection.call(this);
/**
@ -59,6 +62,27 @@ cr.define('print_preview', function() {
this.headerFooterTicketItem_ = headerFooter;
/**
* Distill page ticket item, used to read/write.
* @type {!print_preview.ticket_items.DistillPage}
* @private
*/
this.distillPageTicketItem_ = distillPage;
/**
* Distill page container element.
* @type {HTMLElement}
* @private
*/
this.distillPageContainer_ = null;
/**
* Distill page checkbox.
* @type {HTMLInputElement}
* @private
*/
this.distillPageCheckbox_ = null;
/**
* Header footer container element.
* @type {HTMLElement}
* @private
@ -134,7 +158,8 @@ cr.define('print_preview', function() {
/** @override */
isAvailable: function() {
return this.headerFooterTicketItem_.isCapabilityAvailable() ||
return this.distillPageTicketItem_.isCapabilityAvailable() ||
this.headerFooterTicketItem_.isCapabilityAvailable() ||
this.fitToPageTicketItem_.isCapabilityAvailable() ||
this.duplexTicketItem_.isCapabilityAvailable() ||
this.cssBackgroundTicketItem_.isCapabilityAvailable() ||
@ -154,12 +179,17 @@ cr.define('print_preview', function() {
this.headerFooterCheckbox_.disabled = !isEnabled;
this.fitToPageCheckbox_.disabled = !isEnabled;
this.duplexCheckbox_.disabled = !isEnabled;
this.distillPageCheckbox_.disabled = !isEnabled;
this.cssBackgroundCheckbox_.disabled = !isEnabled;
},
/** @override */
enterDocument: function() {
print_preview.SettingsSection.prototype.enterDocument.call(this);
this.tracker.add(
this.distillPageCheckbox_,
'click',
this.onDistillPageCheckboxClick_.bind(this));
this.tracker.add(
this.headerFooterCheckbox_,
'click',
@ -200,11 +230,17 @@ cr.define('print_preview', function() {
this.headerFooterTicketItem_,
print_preview.ticket_items.TicketItem.EventType.CHANGE,
this.onHeaderFooterChange_.bind(this));
this.tracker.add(
this.distillPageTicketItem_,
print_preview.ticket_items.TicketItem.EventType.CHANGE,
this.onDistillPageChange_.bind(this));
},
/** @override */
exitDocument: function() {
print_preview.SettingsSection.prototype.exitDocument.call(this);
this.distillPageContainer_ = null;
this.distillPageCheckbox_ = null;
this.headerFooterContainer_ = null;
this.headerFooterCheckbox_ = null;
this.fitToPageContainer_ = null;
@ -219,6 +255,10 @@ cr.define('print_preview', function() {
/** @override */
decorateInternal: function() {
this.distillPageContainer_ = this.getElement().querySelector(
'.distill-page-container');
this.distillPageCheckbox_ = this.distillPageContainer_.querySelector(
'.distill-page-checkbox');
this.headerFooterContainer_ = this.getElement().querySelector(
'.header-footer-container');
this.headerFooterCheckbox_ = this.headerFooterContainer_.querySelector(
@ -244,6 +284,8 @@ cr.define('print_preview', function() {
/** @override */
updateUiStateInternal: function() {
if (this.isAvailable()) {
setIsVisible(this.distillPageContainer_,
this.distillPageTicketItem_.isCapabilityAvailable());
setIsVisible(this.headerFooterContainer_,
this.headerFooterTicketItem_.isCapabilityAvailable() &&
!this.collapseContent);
@ -264,11 +306,25 @@ cr.define('print_preview', function() {
/** @override */
isSectionVisibleInternal: function() {
return this.collapseContent ?
this.duplexTicketItem_.isCapabilityAvailable() : this.isAvailable();
if (this.collapseContent) {
return this.distillPageTicketItem_.isCapabilityAvailable() ||
this.duplexTicketItem_.isCapabilityAvailable();
}
return this.isAvailable();
},
/**
* Called when the distill-page checkbox is clicked. Updates the print
* ticket.
* @private
*/
onDistillPageCheckboxClick_: function() {
this.distillPageTicketItem_.updateValue(
this.distillPageCheckbox_.checked);
},
/**
* Called when the header-footer checkbox is clicked. Updates the print
* ticket.
* @private
@ -366,6 +422,17 @@ cr.define('print_preview', function() {
this.headerFooterCheckbox_.checked =
this.headerFooterTicketItem_.getValue();
this.updateUiStateInternal();
},
/**
* Called when the distill-page ticket item has changed. Updates the
* distill-page checkbox.
* @private
*/
onDistillPageChange_: function() {
this.distillPageCheckbox_.checked =
this.distillPageTicketItem_.getValue();
this.updateUiStateInternal();
}
};

@ -0,0 +1,265 @@
// Copyright 2015 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/ui/webui/print_preview/print_preview_distiller.h"
#include <string>
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/dom_distiller/tab_utils.h"
#include "chrome/browser/printing/print_preview_dialog_controller.h"
#include "chrome/browser/printing/print_preview_message_handler.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/web_contents_sizer.h"
#include "chrome/common/prerender_messages.h"
#include "components/dom_distiller/content/browser/distiller_javascript_utils.h"
#include "components/printing/common/print_messages.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/session_storage_namespace.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
using content::OpenURLParams;
using content::RenderViewHost;
using content::SessionStorageNamespace;
using content::WebContents;
class PrintPreviewDistiller::WebContentsDelegateImpl
: public content::WebContentsDelegate,
public content::NotificationObserver,
public content::WebContentsObserver {
public:
explicit WebContentsDelegateImpl(WebContents* web_contents,
scoped_ptr<base::DictionaryValue> settings,
const base::Closure on_failed_callback)
: content::WebContentsObserver(web_contents),
settings_(settings.Pass()),
on_failed_callback_(on_failed_callback) {
web_contents->SetDelegate(this);
// Close ourselves when the application is shutting down.
notification_registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
content::NotificationService::AllSources());
// Register to inform new RenderViews that we're rendering.
notification_registrar_.Add(
this, content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
content::Source<WebContents>(web_contents));
}
~WebContentsDelegateImpl() override { web_contents()->SetDelegate(nullptr); }
// content::WebContentsDelegate implementation.
WebContents* OpenURLFromTab(WebContents* source,
const OpenURLParams& params) override {
on_failed_callback_.Run();
return nullptr;
}
void CloseContents(content::WebContents* contents) override {
on_failed_callback_.Run();
}
void CanDownload(const GURL& url,
const std::string& request_method,
const base::Callback<void(bool)>& callback) override {
on_failed_callback_.Run();
// Cancel the download.
callback.Run(false);
}
bool ShouldCreateWebContents(
WebContents* web_contents,
int route_id,
int main_frame_route_id,
WindowContainerType window_container_type,
const std::string& frame_name,
const GURL& target_url,
const std::string& partition_id,
SessionStorageNamespace* session_storage_namespace) override {
// Since we don't want to permit child windows that would have a
// window.opener property, terminate rendering.
on_failed_callback_.Run();
// Cancel the popup.
return false;
}
bool OnGoToEntryOffset(int offset) override {
// This isn't allowed because the history merge operation
// does not work if there are renderer issued challenges.
// TODO(cbentzel): Cancel in this case? May not need to do
// since render-issued offset navigations are not guaranteed,
// but indicates that the page cares about the history.
return false;
}
bool ShouldSuppressDialogs(WebContents* source) override {
// We still want to show the user the message when they navigate to this
// page, so cancel this render.
on_failed_callback_.Run();
// Always suppress JavaScript messages if they're triggered by a page being
// rendered.
return true;
}
void RegisterProtocolHandler(WebContents* web_contents,
const std::string& protocol,
const GURL& url,
bool user_gesture) override {
on_failed_callback_.Run();
}
void RenderFrameCreated(
content::RenderFrameHost* render_frame_host) override {
// When a new RenderFrame is created for a distilled rendering
// WebContents, tell the new RenderFrame it's being used for
// prerendering before any navigations occur. Note that this is
// always triggered before the first navigation, so there's no
// need to send the message just after the WebContents is created.
render_frame_host->Send(new PrerenderMsg_SetIsPrerendering(
render_frame_host->GetRoutingID(), true));
}
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) override {
// Ask the page to trigger an anchor navigation once the distilled
// contents are added to the page.
dom_distiller::RunIsolatedJavaScript(
web_contents()->GetMainFrame(),
"navigate_on_initial_content_load = true;");
}
void DidNavigateMainFrame(
const content::LoadCommittedDetails& details,
const content::FrameNavigateParams& params) override {
// The second content loads signals that the distilled contents have
// been delivered to the page via inline JavaScript execution.
if (web_contents()->GetController().GetEntryCount() > 1) {
RenderViewHost* rvh = web_contents()->GetRenderViewHost();
rvh->Send(new PrintMsg_InitiatePrintPreview(rvh->GetRoutingID(), false));
rvh->Send(new PrintMsg_PrintPreview(rvh->GetRoutingID(), *settings_));
}
}
void DidGetRedirectForResourceRequest(
content::RenderFrameHost* render_frame_host,
const content::ResourceRedirectDetails& details) override {
// Redirects are unsupported for distilled content renderers.
on_failed_callback_.Run();
}
void RenderProcessGone(base::TerminationStatus status) override {
on_failed_callback_.Run();
}
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
switch (type) {
// TODO(davidben): Try to remove this in favor of relying on
// FINAL_STATUS_PROFILE_DESTROYED.
case chrome::NOTIFICATION_APP_TERMINATING:
on_failed_callback_.Run();
return;
case content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED: {
if (web_contents()) {
DCHECK_EQ(content::Source<WebContents>(source).ptr(), web_contents());
// Make sure the size of the RenderViewHost has been passed to the new
// RenderView. Otherwise, the size may not be sent until the
// RenderViewReady event makes it from the render process to the UI
// thread of the browser process. When the RenderView receives its
// size, is also sets itself to be visible, which would then break the
// visibility API.
content::Details<RenderViewHost> new_render_view_host(details);
new_render_view_host->WasResized();
web_contents()->WasHidden();
}
break;
}
default:
NOTREACHED() << "Unexpected notification sent.";
break;
}
}
private:
scoped_ptr<base::DictionaryValue> settings_;
content::NotificationRegistrar notification_registrar_;
// The callback called when the preview failed.
base::Closure on_failed_callback_;
};
PrintPreviewDistiller::PrintPreviewDistiller(
WebContents* source_web_contents,
const base::Closure on_failed_callback,
scoped_ptr<base::DictionaryValue> settings) {
content::SessionStorageNamespace* session_storage_namespace =
source_web_contents->GetController().GetDefaultSessionStorageNamespace();
CreateDestinationWebContents(session_storage_namespace, source_web_contents,
settings.Pass(), on_failed_callback);
DCHECK(web_contents_);
::DistillAndView(source_web_contents, web_contents_.get());
}
void PrintPreviewDistiller::CreateDestinationWebContents(
SessionStorageNamespace* session_storage_namespace,
WebContents* source_web_contents,
scoped_ptr<base::DictionaryValue> settings,
const base::Closure on_failed_callback) {
DCHECK(!web_contents_);
web_contents_.reset(
CreateWebContents(session_storage_namespace, source_web_contents));
printing::PrintPreviewMessageHandler::CreateForWebContents(
web_contents_.get());
web_contents_delegate_.reset(new WebContentsDelegateImpl(
web_contents_.get(), settings.Pass(), on_failed_callback));
// Set the size of the distilled WebContents.
ResizeWebContents(web_contents_.get(), gfx::Size(1, 1));
printing::PrintPreviewDialogController* dialog_controller =
printing::PrintPreviewDialogController::GetInstance();
if (!dialog_controller)
return;
dialog_controller->AddProxyDialogForWebContents(web_contents_.get(),
source_web_contents);
}
PrintPreviewDistiller::~PrintPreviewDistiller() {
if (web_contents_) {
printing::PrintPreviewDialogController* dialog_controller =
printing::PrintPreviewDialogController::GetInstance();
if (!dialog_controller)
return;
dialog_controller->RemoveProxyDialogForWebContents(web_contents_.get());
}
}
WebContents* PrintPreviewDistiller::CreateWebContents(
SessionStorageNamespace* session_storage_namespace,
WebContents* source_web_contents) {
// TODO(ajwong): Remove the temporary map once prerendering is aware of
// multiple session storage namespaces per tab.
content::SessionStorageNamespaceMap session_storage_namespace_map;
Profile* profile =
Profile::FromBrowserContext(source_web_contents->GetBrowserContext());
session_storage_namespace_map[std::string()] = session_storage_namespace;
return WebContents::CreateWithSessionStorage(
WebContents::CreateParams(profile), session_storage_namespace_map);
}

@ -0,0 +1,51 @@
// Copyright 2015 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.
#ifndef CHROME_BROWSER_UI_WEBUI_PRINT_PREVIEW_PRINT_PREVIEW_DISTILLER_H_
#define CHROME_BROWSER_UI_WEBUI_PRINT_PREVIEW_PRINT_PREVIEW_DISTILLER_H_
#include "base/callback.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "content/public/browser/web_contents_observer.h"
namespace net {
class URLRequestContextGetter;
}
// This class controls the rendering of the distilled contents
// generated by a source web contents
class PrintPreviewDistiller {
public:
PrintPreviewDistiller(content::WebContents* source_web_contents,
base::Closure on_failed_callback,
scoped_ptr<base::DictionaryValue> settings);
~PrintPreviewDistiller();
private:
class WebContentsDelegateImpl;
// Create the web contents with a default
// size. |session_storage_namespace| indicates the namespace that
// the distiller content renderer's page should be part of.
void CreateDestinationWebContents(
content::SessionStorageNamespace* session_storage_namespace,
content::WebContents* source_web_contents,
scoped_ptr<base::DictionaryValue> settings,
base::Closure on_failed_callback);
content::WebContents* CreateWebContents(
content::SessionStorageNamespace* session_storage_namespace,
content::WebContents* source_web_contents);
// The distilled rendered WebContents; may be null.
scoped_ptr<content::WebContents> web_contents_;
scoped_ptr<WebContentsDelegateImpl> web_contents_delegate_;
DISALLOW_COPY_AND_ASSIGN(PrintPreviewDistiller);
};
#endif // CHROME_BROWSER_UI_WEBUI_PRINT_PREVIEW_PRINT_PREVIEW_DISTILLER_H_

@ -30,6 +30,7 @@
#include "base/values.h"
#include "chrome/browser/app_mode/app_mode_utils.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/dom_distiller/tab_utils.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/printing/print_dialog_cloud.h"
#include "chrome/browser/printing/print_error_dialog.h"
@ -57,6 +58,9 @@
#include "components/cloud_devices/common/cloud_device_description.h"
#include "components/cloud_devices/common/cloud_devices_urls.h"
#include "components/cloud_devices/common/printer_description.h"
#include "components/dom_distiller/content/browser/distillable_page_utils.h"
#include "components/dom_distiller/core/dom_distiller_switches.h"
#include "components/dom_distiller/core/url_utils.h"
#include "components/printing/common/print_messages.h"
#include "components/signin/core/browser/gaia_cookie_manager_service.h"
#include "components/signin/core/browser/profile_oauth2_token_service.h"
@ -839,8 +843,28 @@ void PrintPreviewHandler::HandleGetPreview(const base::ListValue* args) {
}
VLOG(1) << "Print preview request start";
RenderViewHost* rvh = initiator->GetRenderViewHost();
rvh->Send(new PrintMsg_PrintPreview(rvh->GetRoutingID(), *settings));
bool distill_page = false;
if (!settings->GetBoolean(printing::kSettingDistillPageEnabled,
&distill_page)) {
NOTREACHED();
}
bool selection_only = false;
if (!settings->GetBoolean(printing::kSettingShouldPrintSelectionOnly,
&selection_only)) {
NOTREACHED();
}
if (distill_page && !selection_only) {
print_preview_distiller_.reset(new PrintPreviewDistiller(
initiator, base::Bind(&PrintPreviewUI::OnPrintPreviewFailed,
print_preview_ui()->GetWeakPtr()),
settings.Pass()));
} else {
RenderViewHost* rvh = initiator->GetRenderViewHost();
rvh->Send(new PrintMsg_PrintPreview(rvh->GetRoutingID(), *settings));
}
}
void PrintPreviewHandler::HandlePrint(const base::ListValue* args) {
@ -1256,6 +1280,22 @@ void PrintPreviewHandler::SendInitialSettings(
if (print_preview_ui()->source_is_modifiable())
GetNumberFormatAndMeasurementSystem(&initial_settings);
web_ui()->CallJavascriptFunction("setInitialSettings", initial_settings);
WebContents* initiator = GetInitiator();
if (initiator && cmdline->HasSwitch(switches::kEnableDomDistiller) &&
dom_distiller::url_utils::IsUrlDistillable(
initiator->GetLastCommittedURL())) {
dom_distiller::IsDistillablePage(
initiator, false,
base::Bind(&PrintPreviewHandler::HandleIsPageDistillableResult,
weak_factory_.GetWeakPtr()));
}
}
void PrintPreviewHandler::HandleIsPageDistillableResult(bool distillable) {
VLOG(1) << "Distillable page detection finished";
if (distillable)
web_ui()->CallJavascriptFunction("detectDistillablePage");
}
void PrintPreviewHandler::ClosePreviewDialog() {

@ -12,6 +12,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/printing/print_view_manager_observer.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_distiller.h"
#include "components/signin/core/browser/gaia_cookie_manager_service.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "ui/shell_dialogs/select_file_dialog.h"
@ -333,6 +334,11 @@ class PrintPreviewHandler
// error.
void OnExtensionPrintResult(bool success, const std::string& status);
// Called when the DOM Distiller determines whether or not this page can
// be distilled.
// |distillable|: Whether or not this page can be distilled.
void HandleIsPageDistillableResult(bool distillable);
// Register/unregister from notifications of changes done to the GAIA
// cookie.
void RegisterForGaiaCookieChanges();
@ -389,6 +395,10 @@ class PrintPreviewHandler
// notify the test if it was a successful save, only that it was attempted.
base::Closure pdf_file_saved_closure_;
// A print preview that is responsible for rendering the page after
// being processed by the DOM Distiller.
scoped_ptr<PrintPreviewDistiller> print_preview_distiller_;
base::WeakPtrFactory<PrintPreviewHandler> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(PrintPreviewHandler);

@ -251,6 +251,8 @@ content::WebUIDataSource* CreatePrintPreviewUISource() {
source->AddLocalizedString("printPagesLabel",
IDS_PRINT_PREVIEW_PRINT_PAGES_LABEL);
source->AddLocalizedString("optionsLabel", IDS_PRINT_PREVIEW_OPTIONS_LABEL);
source->AddLocalizedString("optionDistillPage",
IDS_PRINT_PREVIEW_OPTION_DISTILL_PAGE);
source->AddLocalizedString("optionHeaderFooter",
IDS_PRINT_PREVIEW_OPTION_HEADER_FOOTER);
source->AddLocalizedString("optionFitToPage",
@ -396,7 +398,8 @@ PrintPreviewUI::PrintPreviewUI(content::WebUI* web_ui)
handler_(NULL),
source_is_modifiable_(true),
source_has_selection_(false),
dialog_closed_(false) {
dialog_closed_(false),
weak_ptr_factory_(this) {
// Set up the chrome://print/ data source.
Profile* profile = Profile::FromWebUI(web_ui);
content::WebUIDataSource::Add(profile, CreatePrintPreviewUISource());
@ -661,3 +664,7 @@ void PrintPreviewUI::SetPdfSavedClosureForTesting(
const base::Closure& closure) {
handler_->SetPdfSavedClosureForTesting(closure);
}
base::WeakPtr<PrintPreviewUI> PrintPreviewUI::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}

@ -10,6 +10,7 @@
#include "base/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/ui/webui/constrained_web_dialog_ui.h"
@ -166,6 +167,8 @@ class PrintPreviewUI : public ConstrainedWebDialogUI {
// Passes |closure| to PrintPreviewHandler::SetPdfSavedClosureForTesting().
void SetPdfSavedClosureForTesting(const base::Closure& closure);
base::WeakPtr<PrintPreviewUI> GetWeakPtr();
private:
friend class PrintPreviewHandlerTest;
FRIEND_TEST_ALL_PREFIXES(PrintPreviewHandlerTest, StickyMarginsCustom);
@ -207,6 +210,8 @@ class PrintPreviewUI : public ConstrainedWebDialogUI {
// Keeps track of whether OnClosePrintPreviewDialog() has been called or not.
bool dialog_closed_;
base::WeakPtrFactory<PrintPreviewUI> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(PrintPreviewUI);
};

@ -2696,6 +2696,8 @@
'chrome_browser_ui_print_preview_sources': [
'browser/ui/webui/print_preview/extension_printer_handler.cc',
'browser/ui/webui/print_preview/extension_printer_handler.h',
'browser/ui/webui/print_preview/print_preview_distiller.cc',
'browser/ui/webui/print_preview/print_preview_distiller.h',
'browser/ui/webui/print_preview/print_preview_handler.cc',
'browser/ui/webui/print_preview/print_preview_handler.h',
'browser/ui/webui/print_preview/print_preview_ui.cc',

@ -255,6 +255,12 @@ h6 {
width: 100%;
}
@media print {
#closeReaderView {
display: none;
}
}
#content {
margin: 24px 16px 24px 16px;
}

@ -10,6 +10,14 @@ function addToPage(html) {
div.innerHTML = html;
document.getElementById('content').appendChild(div);
fillYouTubePlaceholders();
if (typeof navigate_on_initial_content_load !== 'undefined' &&
navigate_on_initial_content_load) {
navigate_on_initial_content_load = false;
setTimeout(function() {
window.location = window.location + "#loaded";
}, 0);
}
}
function fillYouTubePlaceholders() {
@ -131,6 +139,16 @@ function showFeedbackForm(questionText, yesText, noText) {
document.getElementById('contentWrap').style.paddingBottom = '120px';
document.getElementById('feedbackContainer').style.display = 'block';
var mediaQuery = window.matchMedia("print");
mediaQuery.addListener(function (query) {
if (query.matches) {
document.getElementById('contentWrap').style.paddingBottom = '0px';
document.getElementById('feedbackContainer').style.display = 'none';
} else {
document.getElementById('contentWrap').style.paddingBottom = '120px';
document.getElementById('feedbackContainer').style.display = 'block';
}
});
}
/**

@ -54,6 +54,9 @@ const char kSettingDeviceName[] = "deviceName";
// Option to disable scaling. True if scaling is disabled else false.
const char kSettingDisableScaling[] = "disableScaling";
// Option to print a distilled page: true if requested, false if not.
const char kSettingDistillPageEnabled[] = "distillPage";
// Print job duplex mode.
const char kSettingDuplexMode[] = "duplex";

@ -26,6 +26,7 @@ PRINTING_EXPORT extern const char kSettingContentWidth[];
PRINTING_EXPORT extern const char kSettingCopies[];
PRINTING_EXPORT extern const char kSettingDeviceName[];
PRINTING_EXPORT extern const char kSettingDisableScaling[];
PRINTING_EXPORT extern const char kSettingDistillPageEnabled[];
PRINTING_EXPORT extern const char kSettingDuplexMode[];
PRINTING_EXPORT extern const char kSettingFitToPageEnabled[];
PRINTING_EXPORT extern const char kSettingGenerateDraftData[];