"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:
chrome
components/dom_distiller/core
printing
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
265
chrome/browser/ui/webui/print_preview/print_preview_distiller.cc
Normal file
265
chrome/browser/ui/webui/print_preview/print_preview_distiller.cc
Normal file
@ -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[];
|
||||
|
Reference in New Issue
Block a user