Print Preview: Make Scaling settings a dropdown

Makes scaling settings a dropdown with 3 options: Default, Custom, and
Fit to Page. Fit to Page option is only available for PDFs. Selecting
the custom option displays the current number input for scaling.

Bug: 930008
Change-Id: I77b3c356f0592f360c87f8d4c58fe5322719208d
Reviewed-on: https://chromium-review.googlesource.com/c/1460477
Reviewed-by: Demetrios Papadopoulos <dpapad@chromium.org>
Commit-Queue: Rebekah Potter <rbpotter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#631491}
diff --git a/chrome/app/printing_strings.grdp b/chrome/app/printing_strings.grdp
index 71329f5..be4b876 100644
--- a/chrome/app/printing_strings.grdp
+++ b/chrome/app/printing_strings.grdp
@@ -79,6 +79,12 @@
     <message name="IDS_PRINT_PREVIEW_SCALING_LABEL" desc="Scaling option label.">
       Scale
     </message>
+    <message name="IDS_PRINT_PREVIEW_OPTION_DEFAULT_SCALING" desc="Option to print with default (100%) scaling.">
+      Default
+    </message>
+    <message name="IDS_PRINT_PREVIEW_OPTION_CUSTOM_SCALING" desc="Option to print with custom scaling.">
+      Custom
+    </message>
     <message name="IDS_PRINT_PREVIEW_PAGES_PER_SHEET_LABEL" desc="Pages per sheet option label.">
       Pages per sheet
     </message>
diff --git a/chrome/browser/resources/print_preview/new/model.js b/chrome/browser/resources/print_preview/new/model.js
index 7497283..5c646ae 100644
--- a/chrome/browser/resources/print_preview/new/model.js
+++ b/chrome/browser/resources/print_preview/new/model.js
@@ -87,6 +87,7 @@
   'layout',
   'margins',
   'mediaSize',
+  'customScaling',
   'scaling',
   'fitToPage',
   'vendorItems',
@@ -208,6 +209,14 @@
             setByPolicy: false,
             key: 'scaling',
           },
+          customScaling: {
+            value: false,
+            unavailableValue: false,
+            valid: true,
+            available: true,
+            setByPolicy: false,
+            key: 'customScaling',
+          },
           duplex: {
             value: true,
             unavailableValue: false,
@@ -326,9 +335,10 @@
         'settings.collate.value, settings.layout.value, settings.color.value,' +
         'settings.mediaSize.value, settings.margins.value, ' +
         'settings.customMargins.value, settings.dpi.value, ' +
-        'settings.fitToPage.value, settings.scaling.value, ' +
-        'settings.duplex.value, settings.headerFooter.value, ' +
-        'settings.cssBackground.value, settings.vendorItems.value)',
+        'settings.fitToPage.value, settings.customScaling.value, ' +
+        'settings.scaling.value, settings.duplex.value, ' +
+        'settings.headerFooter.value, settings.cssBackground.value, ' +
+        'settings.vendorItems.value)',
   ],
 
   /** @private {boolean} */
@@ -711,6 +721,11 @@
         const value = this.stickySettings_[setting.key];
         if (value != undefined) {
           this.setSetting(settingName, value);
+        } else if (settingName === 'customScaling') {
+          // Use the stored scaling value instead of resetting users with an
+          // older set of sticky settings.
+          this.setSetting(
+              settingName, this.stickySettings_['scaling'] !== '100');
         }
       });
     }
@@ -820,7 +835,9 @@
       printWithPrivet: destination.isPrivet,
       printWithExtension: destination.isExtension,
       rasterizePDF: this.getSettingValue('rasterize'),
-      scaleFactor: parseInt(this.getSettingValue('scaling'), 10),
+      scaleFactor: this.getSettingValue('customScaling') ?
+          parseInt(this.getSettingValue('scaling'), 10) :
+          100,
       pagesPerSheet: this.getSettingValue('pagesPerSheet'),
       dpiHorizontal: (dpi && 'horizontal_dpi' in dpi) ? dpi.horizontal_dpi : 0,
       dpiVertical: (dpi && 'vertical_dpi' in dpi) ? dpi.vertical_dpi : 0,
diff --git a/chrome/browser/resources/print_preview/new/preview_area.js b/chrome/browser/resources/print_preview/new/preview_area.js
index c7d2c51..b13c40f 100644
--- a/chrome/browser/resources/print_preview/new/preview_area.js
+++ b/chrome/browser/resources/print_preview/new/preview_area.js
@@ -89,8 +89,8 @@
     'onSettingsChanged_(settings.color.value, settings.cssBackground.value, ' +
         'settings.fitToPage.value, settings.headerFooter.value, ' +
         'settings.layout.value, settings.ranges.value, ' +
-        'settings.selectionOnly.value, settings.scaling.value, ' +
-        'settings.rasterize.value, destination)',
+        'settings.selectionOnly.value, settings.customScaling.value, ' +
+        'settings.scaling.value, settings.rasterize.value, destination)',
     'onMarginsChanged_(settings.margins.value)',
     'onCustomMarginsChanged_(settings.customMargins.value)',
     'onMediaSizeChanged_(settings.mediaSize.value)',
@@ -625,7 +625,9 @@
       requestID: this.inFlightRequestId_,
       previewModifiable: this.documentModifiable,
       fitToPageEnabled: this.getSettingValue('fitToPage'),
-      scaleFactor: parseInt(this.getSettingValue('scaling'), 10),
+      scaleFactor: this.getSettingValue('customScaling') ?
+          parseInt(this.getSettingValue('scaling'), 10) :
+          100,
       shouldPrintBackgrounds: this.getSettingValue('cssBackground'),
       shouldPrintSelectionOnly: this.getSettingValue('selectionOnly'),
       // NOTE: Even though the remaining fields don't directly relate to the
diff --git a/chrome/browser/resources/print_preview/new/scaling_settings.html b/chrome/browser/resources/print_preview/new/scaling_settings.html
index 29fac7a..3d1b792 100644
--- a/chrome/browser/resources/print_preview/new/scaling_settings.html
+++ b/chrome/browser/resources/print_preview/new/scaling_settings.html
@@ -1,31 +1,43 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
-<link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
+<link rel="import" href="chrome://resources/html/md_select_css.html">
 <link rel="import" href="number_settings_section.html">
 <link rel="import" href="print_preview_shared_css.html">
+<link rel="import" href="select_behavior.html">
 <link rel="import" href="settings_behavior.html">
 <link rel="import" href="settings_section.html">
 
 <dom-module id="print-preview-scaling-settings">
   <template>
-    <style include="print-preview-shared">
+    <style include="print-preview-shared md-select">
     </style>
-    <print-preview-settings-section hidden$="[[!settings.fitToPage.available]]">
-      <span slot="title">$i18n{scalingLabel}</span>
-      <div slot="controls" class="checkbox">
-        <cr-checkbox id="fit-to-page-checkbox" tabindex="1"
-            disabled$="[[getDisabled_(disabled, currentState_)]]"
-            on-change="onFitToPageChange_" aria-live="polite">
-          $i18n{optionFitToPage}
-        </cr-checkbox>
+    <print-preview-settings-section>
+      <span slot="title" id="scaling-label">$i18n{scalingLabel}</span>
+      <div slot="controls">
+        <select class="md-select" aria-labelledby="scaling-label"
+            disabled$="[[dropdownDisabled_(disabled, inputValid_)]]"
+            value="{{selectedValue::change}}">
+          <option value="[[scalingValueEnum_.DEFAULT]]">
+            $i18n{optionDefaultScaling}
+          </option>
+          <option value="[[scalingValueEnum_.FIT_TO_PAGE]]"
+              hidden$="[[!settings.fitToPage.available]]">
+            $i18n{optionFitToPage}
+          </option>
+          <option value="[[scalingValueEnum_.CUSTOM]]">
+            $i18n{optionCustomScaling}
+          </option>
+        </select>
       </div>
     </print-preview-settings-section>
-    <print-preview-number-settings-section max-value="200" min-value="10"
-        default-value="100" input-aria-label="$i18n{scalingLabel}"
-        input-label="[[getScalingInputLabel_(settings.fitToPage.available)]]"
-        disabled="[[disabled]]" current-value="{{currentValue_}}"
-        input-valid="{{inputValid_}}" hint-message="$i18n{scalingInstruction}">
-    </print-preview-number-settings-section>
+    <iron-collapse opened="[[customSelected_]]">
+      <print-preview-number-settings-section
+          max-value="200" min-value="10" default-value="100"
+          disabled$="[[inputDisabled_(disabled, inputValid_, customSelected_)]]"
+          current-value="{{currentValue_}}" input-valid="{{inputValid_}}"
+          hint-message="$i18n{scalingInstruction}">
+      </print-preview-number-settings-section>
+    </iron-collapse>
   </template>
   <script src="scaling_settings.js"></script>
 </dom-module>
diff --git a/chrome/browser/resources/print_preview/new/scaling_settings.js b/chrome/browser/resources/print_preview/new/scaling_settings.js
index b69e116..a891b32 100644
--- a/chrome/browser/resources/print_preview/new/scaling_settings.js
+++ b/chrome/browser/resources/print_preview/new/scaling_settings.js
@@ -5,11 +5,10 @@
 cr.exportPath('print_preview_new');
 
 /** @enum {number} */
-print_preview_new.ScalingState = {
-  INIT: 0,
-  VALID: 1,
-  INVALID: 2,
-  FIT_TO_PAGE: 3,
+const ScalingValue = {
+  DEFAULT: 0,
+  FIT_TO_PAGE: 1,
+  CUSTOM: 2,
 };
 
 /*
@@ -23,13 +22,10 @@
 Polymer({
   is: 'print-preview-scaling-settings',
 
-  behaviors: [SettingsBehavior],
+  behaviors: [SettingsBehavior, print_preview_new.SelectBehavior],
 
   properties: {
-    fitToPageScaling: {
-      type: Number,
-      observer: 'onFitToPageScalingSet_',
-    },
+    disabled: Boolean,
 
     /** @private {string} */
     currentValue_: {
@@ -38,83 +34,84 @@
     },
 
     /** @private {boolean} */
-    inputValid_: Boolean,
+    customSelected_: {
+      type: Boolean,
+      computed: 'computeCustomSelected_(selectedValue)',
+    },
 
     /** @private {boolean} */
-    hideInput_: Boolean,
+    inputValid_: Boolean,
 
-    disabled: Boolean,
-
-    /** @private {!print_preview_new.ScalingState} */
-    currentState_: {
-      type: Number,
-      value: print_preview_new.ScalingState.INIT,
-      observer: 'onStateChange_',
+    /**
+     * Mirroring the enum so that it can be used from HTML bindings.
+     * @private
+     */
+    scalingValueEnum_: {
+      type: Object,
+      value: ScalingValue,
     },
   },
 
   observers: [
     'onFitToPageSettingChange_(settings.fitToPage.value)',
     'onScalingSettingChanged_(settings.scaling.value)',
-    'onScalingValidChanged_(settings.scaling.valid)',
+    'onCustomScalingSettingChanged_(settings.customScaling.value)',
   ],
 
-  /**
-   * Timeout used to delay processing of the checkbox input.
-   * @private {?number}
-   */
-  fitToPageTimeout_: null,
-
-  /** @private {boolean} */
-  ignoreFtp_: false,
-
-  /** @private {boolean} */
-  ignoreValid_: false,
-
-  /** @private {boolean} */
-  ignoreValue_: false,
-
   /** @private {string} */
-  lastValidScaling_: '100',
+  lastValidScaling_: '',
 
-  /** @private {?boolean} */
-  lastFitToPageValue_: null,
+  onProcessSelectChange: function(value) {
+    if (value === ScalingValue.FIT_TO_PAGE.toString()) {
+      this.setSetting('fitToPage', true);
+      return;
+    }
+
+    const fitToPageAvailable = this.getSetting('fitToPage').available;
+    if (fitToPageAvailable) {
+      this.setSetting('fitToPage', false);
+    }
+    const isCustom = value === ScalingValue.CUSTOM.toString();
+    this.setSetting('customScaling', isCustom);
+    if (isCustom) {
+      this.setSetting('scaling', this.currentValue_);
+    }
+  },
+
+  /** @private */
+  updateScalingToValid_: function() {
+    if (!this.getSetting('scaling').valid) {
+      this.currentValue_ = this.lastValidScaling_;
+    } else {
+      this.lastValidScaling_ = this.currentValue_;
+    }
+  },
 
   /** @private */
   onFitToPageSettingChange_: function() {
-    if (this.ignoreFtp_ || !this.getSetting('fitToPage').available) {
+    if (!this.getSettingValue('fitToPage') ||
+        !this.getSetting('fitToPage').available) {
       return;
     }
 
-    const fitToPage = this.getSetting('fitToPage').value;
-
-    if (fitToPage) {
-      this.currentState_ = print_preview_new.ScalingState.FIT_TO_PAGE;
-      return;
-    }
-
-    this.currentState_ = this.getSetting('scaling').valid ?
-        print_preview_new.ScalingState.VALID :
-        print_preview_new.ScalingState.INVALID;
-  },
-
-  /**
-   * @return {string} The value to display for fit to page scling.
-   * @private
-   */
-  getFitToPageScalingDisplayValue_: function() {
-    return this.fitToPageScaling > 0 ? this.fitToPageScaling.toString() : '';
+    this.updateScalingToValid_();
+    this.selectedValue = ScalingValue.FIT_TO_PAGE.toString();
   },
 
   /** @private */
-  onFitToPageScalingSet_: function() {
-    if (this.currentState_ != print_preview_new.ScalingState.FIT_TO_PAGE) {
+  onCustomScalingSettingChanged_: function() {
+    if (this.getSettingValue('fitToPage') &&
+        this.getSetting('fitToPage').available) {
       return;
     }
 
-    this.ignoreValue_ = true;
-    this.currentValue_ = this.getFitToPageScalingDisplayValue_();
-    this.ignoreValue_ = false;
+    const isCustom =
+        /** @type {boolean} */ (this.getSetting('customScaling').value);
+    if (!isCustom) {
+      this.updateScalingToValid_();
+    }
+    this.selectedValue = isCustom ? ScalingValue.CUSTOM.toString() :
+                                    ScalingValue.DEFAULT.toString();
   },
 
   /**
@@ -122,25 +119,9 @@
    * @private
    */
   onScalingSettingChanged_: function() {
-    // Update last valid scaling and ensure input string matches.
-    this.lastValidScaling_ =
-        /** @type {string} */ (this.getSetting('scaling').value);
-    this.currentValue_ = this.lastValidScaling_;
-    this.currentState_ = print_preview_new.ScalingState.VALID;
-  },
-
-  /**
-   * Updates the state of the UI when scaling validity is set.
-   * @private
-   */
-  onScalingValidChanged_: function() {
-    if (this.ignoreValid_) {
-      return;
-    }
-
-    this.currentState_ = this.getSetting('scaling').valid ?
-        print_preview_new.ScalingState.VALID :
-        print_preview_new.ScalingState.INVALID;
+    const value = /** @type {string} */ (this.getSetting('scaling').value);
+    this.lastValidScaling_ = value;
+    this.currentValue_ = value;
   },
 
   /**
@@ -149,10 +130,6 @@
    * @private
    */
   onInputChanged_: function() {
-    if (this.ignoreValue_) {
-      return;
-    }
-
     if (this.currentValue_ !== '') {
       this.setSettingValid('scaling', this.inputValid_);
     }
@@ -162,84 +139,27 @@
     }
   },
 
-  /** @private */
-  onFitToPageChange_: function() {
-    const newValue = this.$$('#fit-to-page-checkbox').checked;
-
-    if (this.fitToPageTimeout_ !== null) {
-      clearTimeout(this.fitToPageTimeout_);
-    }
-
-    this.fitToPageTimeout_ = setTimeout(() => {
-      this.fitToPageTimeout_ = null;
-
-      if (newValue === this.lastFitToPageValue_) {
-        return;
-      }
-
-      this.lastFitToPageValue_ = newValue;
-      this.setSetting('fitToPage', newValue);
-
-      if (newValue == false) {
-        this.currentValue_ = this.lastValidScaling_;
-      } else {
-        this.currentState_ = print_preview_new.ScalingState.FIT_TO_PAGE;
-      }
-
-      // For tests only
-      this.fire('update-checkbox-setting', 'fitToPage');
-    }, 200);
+  /**
+   * @return {boolean} Whether the dropdown should be disabled.
+   * @private
+   */
+  dropdownDisabled_: function() {
+    return this.disabled && this.inputValid_;
   },
 
   /**
    * @return {boolean} Whether the input should be disabled.
    * @private
    */
-  getDisabled_: function() {
-    return this.disabled &&
-        this.currentState_ !== print_preview_new.ScalingState.INVALID;
+  inputDisabled_: function() {
+    return !this.customSelected_ || (this.disabled && this.inputValid_);
   },
 
   /**
-   * @param {!print_preview_new.ScalingState} current
-   * @param {!print_preview_new.ScalingState} previous
+   * @return {boolean} Whether the custom scaling option is selected.
    * @private
    */
-  onStateChange_: function(current, previous) {
-    if (previous == print_preview_new.ScalingState.FIT_TO_PAGE) {
-      this.ignoreFtp_ = true;
-      this.$$('#fit-to-page-checkbox').checked = false;
-      this.lastFitToPageValue_ = false;
-      if (current == print_preview_new.ScalingState.VALID) {
-        this.setSetting('fitToPage', false);
-      }
-      this.ignoreFtp_ = false;
-    }
-    if (current == print_preview_new.ScalingState.FIT_TO_PAGE) {
-      if (previous == print_preview_new.ScalingState.INVALID) {
-        this.ignoreValid_ = true;
-        this.setSettingValid('scaling', true);
-        this.ignoreValid_ = false;
-      }
-      this.$$('#fit-to-page-checkbox').checked = true;
-      this.ignoreValue_ = true;
-      this.currentValue_ = this.getFitToPageScalingDisplayValue_();
-      this.ignoreValue_ = false;
-    }
-    if (current == print_preview_new.ScalingState.VALID &&
-        previous == print_preview_new.ScalingState.INVALID &&
-        this.getSetting('fitToPage').available) {
-      this.setSetting('fitToPage', false);
-    }
-  },
-
-  /**
-   * @return {string} The label to use on the scaling input.
-   * @private
-   */
-  getScalingInputLabel_: function() {
-    return this.getSetting('fitToPage').available ?
-        '' :
-        loadTimeData.getString('scalingLabel');
+  computeCustomSelected_: function() {
+    return this.selectedValue === ScalingValue.CUSTOM.toString();
   },
 });
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
index b32db77a..30af140 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
@@ -217,6 +217,8 @@
     {"optionCollate", IDS_PRINT_PREVIEW_OPTION_COLLATE},
     {"optionColor", IDS_PRINT_PREVIEW_OPTION_COLOR},
     {"optionCustomPages", IDS_PRINT_PREVIEW_OPTION_CUSTOM_PAGES},
+    {"optionCustomScaling", IDS_PRINT_PREVIEW_OPTION_CUSTOM_SCALING},
+    {"optionDefaultScaling", IDS_PRINT_PREVIEW_OPTION_DEFAULT_SCALING},
     {"optionFitToPage", IDS_PRINT_PREVIEW_OPTION_FIT_TO_PAGE},
     {"optionHeaderFooter", IDS_PRINT_PREVIEW_OPTION_HEADER_FOOTER},
     {"optionLandscape", IDS_PRINT_PREVIEW_OPTION_LANDSCAPE},
diff --git a/chrome/test/data/webui/print_preview/invalid_settings_browsertest.js b/chrome/test/data/webui/print_preview/invalid_settings_browsertest.js
index cd854ac..46b056f 100644
--- a/chrome/test/data/webui/print_preview/invalid_settings_browsertest.js
+++ b/chrome/test/data/webui/print_preview/invalid_settings_browsertest.js
@@ -257,6 +257,9 @@
 
       return nativeLayer.whenCalled('getInitialSettings')
           .then(function() {
+            // Set this to enable the scaling input.
+            page.setSetting('customScaling', true);
+
             page.destinationStore_.startLoadCloudDestinations();
 
             // FooDevice will be selected since it is the most recently used
diff --git a/chrome/test/data/webui/print_preview/model_test.js b/chrome/test/data/webui/print_preview/model_test.js
index 7f90b01..9a4b65e 100644
--- a/chrome/test/data/webui/print_preview/model_test.js
+++ b/chrome/test/data/webui/print_preview/model_test.js
@@ -35,6 +35,7 @@
         dpi: {},
         mediaSize: {width_microns: 215900, height_microns: 279400},
         marginsType: 0, /* default */
+        customScaling: false,
         scaling: '100',
         isHeaderFooterEnabled: true,
         isCssBackgroundEnabled: false,
@@ -53,6 +54,7 @@
         dpi: {horizontal_dpi: 1000, vertical_dpi: 500},
         mediaSize: {width_microns: 43180, height_microns: 21590},
         marginsType: 2, /* none */
+        customScaling: true,
         scaling: '85',
         isHeaderFooterEnabled: false,
         isCssBackgroundEnabled: true,
@@ -112,6 +114,7 @@
           .then(() => testStickySetting('layout', 'isLandscapeEnabled'))
           .then(() => testStickySetting('margins', 'marginsType'))
           .then(() => testStickySetting('mediaSize', 'mediaSize'))
+          .then(() => testStickySetting('customScaling', 'customScaling'))
           .then(() => testStickySetting('scaling', 'scaling'))
           .then(() => testStickySetting('fitToPage', 'isFitToPageEnabled'))
           .then(() => testStickySetting('vendorItems', 'vendorOptions'));
@@ -170,6 +173,7 @@
           vertical_dpi: 100,
         },
         fitToPage: true,
+        customScaling: true,
         scaling: '90',
         duplex: false,
         cssBackground: true,
diff --git a/chrome/test/data/webui/print_preview/preview_generation_test.js b/chrome/test/data/webui/print_preview/preview_generation_test.js
index c20103e..c310bb75 100644
--- a/chrome/test/data/webui/print_preview/preview_generation_test.js
+++ b/chrome/test/data/webui/print_preview/preview_generation_test.js
@@ -277,7 +277,58 @@
 
     /** Validate changing the scaling updates the preview. */
     test(assert(TestNames.Scaling), function() {
-      return testSimpleSetting('scaling', '100', '90', 'scaleFactor', 100, 90);
+      return initialize()
+          .then(function(args) {
+            const ticket = JSON.parse(args.printTicket);
+            assertEquals(0, ticket.requestID);
+            assertEquals(100, ticket.scaleFactor);
+            nativeLayer.resetResolver('getPreview');
+            assertEquals('100', page.getSettingValue('scaling'));
+            assertEquals(false, page.getSettingValue('customScaling'));
+            page.setSetting('customScaling', true);
+            return nativeLayer.whenCalled('getPreview');
+          })
+          .then(function(args) {
+            const ticket = JSON.parse(args.printTicket);
+            // No change since custom value is 100 by default.
+            assertEquals(100, ticket.scaleFactor);
+            assertEquals(1, ticket.requestID);
+            nativeLayer.resetResolver('getPreview');
+            assertEquals('100', page.getSettingValue('scaling'));
+            assertEquals(true, page.getSettingValue('customScaling'));
+            page.setSetting('scaling', '90');
+            return nativeLayer.whenCalled('getPreview');
+          })
+          .then(function(args) {
+            const ticket = JSON.parse(args.printTicket);
+            // Ticket updates with new custom value.
+            assertEquals(90, ticket.scaleFactor);
+            assertEquals(2, ticket.requestID);
+            nativeLayer.resetResolver('getPreview');
+            assertEquals('90', page.getSettingValue('scaling'));
+            assertEquals(true, page.getSettingValue('customScaling'));
+            page.setSetting('customScaling', false);
+            return nativeLayer.whenCalled('getPreview');
+          })
+          .then(function(args) {
+            const ticket = JSON.parse(args.printTicket);
+            // Back to 100 for default
+            assertEquals(100, ticket.scaleFactor);
+            assertEquals(3, ticket.requestID);
+            nativeLayer.resetResolver('getPreview');
+            assertEquals('90', page.getSettingValue('scaling'));
+            assertEquals(false, page.getSettingValue('customScaling'));
+            page.setSetting('customScaling', true);
+            return nativeLayer.whenCalled('getPreview');
+          })
+          .then(function(args) {
+            const ticket = JSON.parse(args.printTicket);
+            // Back to 90, since custom scaling value is now 90.
+            assertEquals(90, ticket.scaleFactor);
+            assertEquals(4, ticket.requestID);
+            assertEquals('90', page.getSettingValue('scaling'));
+            assertEquals(true, page.getSettingValue('customScaling'));
+          });
     });
 
     /**
diff --git a/chrome/test/data/webui/print_preview/restore_state_test.js b/chrome/test/data/webui/print_preview/restore_state_test.js
index 4d0cce8..652fbdf 100644
--- a/chrome/test/data/webui/print_preview/restore_state_test.js
+++ b/chrome/test/data/webui/print_preview/restore_state_test.js
@@ -51,11 +51,15 @@
           stickySettings.vendorOptions.printArea,
           page.settings.vendorItems.value.printArea);
 
-      [['margins', 'marginsType'], ['color', 'isColorEnabled'],
+      [['margins', 'marginsType'],
+       ['color', 'isColorEnabled'],
        ['headerFooter', 'isHeaderFooterEnabled'],
-       ['layout', 'isLandscapeEnabled'], ['collate', 'isCollateEnabled'],
+       ['layout', 'isLandscapeEnabled'],
+       ['collate', 'isCollateEnabled'],
        ['fitToPage', 'isFitToPageEnabled'],
-       ['cssBackground', 'isCssBackgroundEnabled'], ['scaling', 'scaling'],
+       ['cssBackground', 'isCssBackgroundEnabled'],
+       ['scaling', 'scaling'],
+       ['customScaling', 'customScaling'],
       ].forEach(keys => {
         assertEquals(stickySettings[keys[1]], page.settings[keys[0]].value);
       });
@@ -111,6 +115,7 @@
           printArea: 6,
         },
         marginsType: 3, /* custom */
+        customScaling: true,
         scaling: '90',
         isHeaderFooterEnabled: true,
         isCssBackgroundEnabled: true,
@@ -145,6 +150,7 @@
           printArea: 4,
         },
         marginsType: 0, /* default */
+        customScaling: false,
         scaling: '120',
         isHeaderFooterEnabled: false,
         isCssBackgroundEnabled: false,
@@ -209,6 +215,12 @@
         },
         {
           section: 'print-preview-scaling-settings',
+          settingName: 'customScaling',
+          key: 'customScaling',
+          value: true,
+        },
+        {
+          section: 'print-preview-scaling-settings',
           settingName: 'scaling',
           key: 'scaling',
           value: '85',
diff --git a/chrome/test/data/webui/print_preview/settings_section_test.js b/chrome/test/data/webui/print_preview/settings_section_test.js
index d8c3235..0a9c454 100644
--- a/chrome/test/data/webui/print_preview/settings_section_test.js
+++ b/chrome/test/data/webui/print_preview/settings_section_test.js
@@ -377,23 +377,25 @@
       toggleMoreSettings();
       assertFalse(scalingElement.hidden);
 
-      // HTML to non-PDF destination -> only input shown
+      // HTML to non-PDF destination -> No fit to page option.
       initDocumentInfo(false, false);
-      const fitToPageSection =
-          scalingElement.$$('print-preview-settings-section');
-      const scalingInputWrapper =
-          scalingElement.$$('print-preview-number-settings-section')
-              .$$('.input-wrapper');
       assertFalse(scalingElement.hidden);
-      assertTrue(fitToPageSection.hidden);
-      assertFalse(scalingInputWrapper.hidden);
+      const fitToPageOption = scalingElement.$$(
+          `[value="${scalingElement.scalingValueEnum_.FIT_TO_PAGE}"]`);
+      const defaultOption = scalingElement.$$(
+          `[value="${scalingElement.scalingValueEnum_.DEFAULT}"]`);
+      const customOption = scalingElement.$$(
+          `[value="${scalingElement.scalingValueEnum_.CUSTOM}"]`);
+      assertTrue(fitToPageOption.hidden);
+      assertFalse(defaultOption.hidden);
+      assertFalse(customOption.hidden);
 
-      // PDF to non-PDF destination -> checkbox and input shown. Check that if
-      // more settings is collapsed the section is hidden.
+      // PDF to non-PDF destination -> All 3 options.
       initDocumentInfo(true, false);
       assertFalse(scalingElement.hidden);
-      assertFalse(fitToPageSection.hidden);
-      assertFalse(scalingInputWrapper.hidden);
+      assertFalse(fitToPageOption.hidden);
+      assertFalse(defaultOption.hidden);
+      assertFalse(customOption.hidden);
 
       // PDF to PDF destination -> section disappears.
       setPdfDestination();
@@ -907,22 +909,39 @@
       const scalingInput =
           scalingElement.$$('print-preview-number-settings-section')
               .$.userValue.inputElement;
-      const fitToPageCheckbox = scalingElement.$$('#fit-to-page-checkbox');
+      const collapse = scalingElement.$$('iron-collapse');
+      const scalingDropdown = scalingElement.$$('.md-select');
 
+      /**
+       * @param {boolean} isCustom Whether custom scaling is selected.
+       * @param {string} scalingValue The value of the scaling setting.
+       * @param {string} scalingDisplayValue The value displayed in the scaling
+       *     input.
+       * @param {boolean} scalingValid Whether the scaling setting is valid.
+       * @param {boolean} fitToPage Whether fit to page is selected.
+       */
       const validateScalingState =
-          (scalingValue, scalingValid, fitToPage, fitToPageDisplay) => {
-            // Invalid scalings are always set directly in the input, so no need
-            // to verify that the input matches them.
-            if (scalingValid) {
-              const scalingDisplay = fitToPage ?
-                  page.documentSettings_.fitToPageScaling.toString() :
-                  scalingValue;
-              assertEquals(scalingDisplay, scalingInput.value);
+          (isCustom, scalingValue, scalingDisplayValue, scalingValid,
+           fitToPage) => {
+            if (fitToPage) {
+              assertEquals(
+                  scalingElement.scalingValueEnum_.FIT_TO_PAGE.toString(),
+                  scalingDropdown.value);
+            } else if (isCustom) {
+              assertEquals(
+                  scalingElement.scalingValueEnum_.CUSTOM.toString(),
+                  scalingDropdown.value);
+            } else {
+              assertEquals(
+                  scalingElement.scalingValueEnum_.DEFAULT.toString(),
+                  scalingDropdown.value);
             }
+            assertEquals(isCustom && !fitToPage, collapse.opened);
+            assertEquals(scalingDisplayValue, scalingInput.value);
             assertEquals(scalingValue, page.settings.scaling.value);
             assertEquals(scalingValid, page.settings.scaling.valid);
-            assertEquals(fitToPageDisplay, fitToPageCheckbox.checked);
             assertEquals(fitToPage, page.settings.fitToPage.value);
+            assertEquals(isCustom, page.settings.customScaling.value);
           };
 
       // Set PDF so both scaling and fit to page are active.
@@ -930,32 +949,41 @@
       assertFalse(scalingElement.hidden);
 
       // Default is 100
-      validateScalingState('100', true, false, false);
+      validateScalingState(false, '100', '100', true, false);
 
-      // Change to 105
-      print_preview_test_utils.triggerInputEvent(scalingInput, '105');
-      return test_util.eventToPromise('input-change', scalingElement)
+      // Select custom
+      scalingDropdown.value =
+          scalingElement.scalingValueEnum_.CUSTOM.toString();
+      scalingDropdown.dispatchEvent(new CustomEvent('change'));
+      return test_util.eventToPromise('process-select-change', scalingElement)
           .then(function() {
-            validateScalingState('105', true, false, false);
+            validateScalingState(true, '100', '100', true, false);
 
-            // Change to fit to page. Should display fit to page scaling but not
-            // alter the scaling setting.
-            fitToPageCheckbox.checked = true;
-            fitToPageCheckbox.dispatchEvent(new CustomEvent('change'));
-            return test_util.eventToPromise(
-                'update-checkbox-setting', scalingElement);
-          })
-          .then(function(event) {
-            assertEquals('fitToPage', event.detail);
-            validateScalingState('105', true, true, true);
-
-            // Set scaling. Should uncheck fit to page and set the settings for
-            // scaling and fit to page.
-            print_preview_test_utils.triggerInputEvent(scalingInput, '95');
+            print_preview_test_utils.triggerInputEvent(scalingInput, '105');
             return test_util.eventToPromise('input-change', scalingElement);
           })
           .then(function() {
-            validateScalingState('95', true, false, false);
+            validateScalingState(true, '105', '105', true, false);
+
+            // Change to fit to page.
+            scalingDropdown.value =
+                scalingElement.scalingValueEnum_.FIT_TO_PAGE.toString();
+            scalingDropdown.dispatchEvent(new CustomEvent('change'));
+            return test_util.eventToPromise(
+                'process-select-change', scalingElement);
+          })
+          .then(function(event) {
+            validateScalingState(true, '105', '105', true, true);
+
+            // Go back to custom. Restores 105 value.
+            scalingDropdown.value =
+                scalingElement.scalingValueEnum_.CUSTOM.toString();
+            scalingDropdown.dispatchEvent(new CustomEvent('change'));
+            return test_util.eventToPromise(
+                'process-select-change', scalingElement);
+          })
+          .then(function() {
+            validateScalingState(true, '105', '105', true, false);
 
             // Set scaling to something invalid. Should change setting validity
             // but not value.
@@ -963,48 +991,55 @@
             return test_util.eventToPromise('input-change', scalingElement);
           })
           .then(function() {
-            validateScalingState('95', false, false, false);
+            validateScalingState(true, '105', '5', false, false);
 
-            // Check fit to page. Should set scaling valid.
-            fitToPageCheckbox.checked = true;
-            fitToPageCheckbox.dispatchEvent(new CustomEvent('change'));
+            // Select fit to page. Should clear the invalid value.
+            scalingDropdown.value =
+                scalingElement.scalingValueEnum_.FIT_TO_PAGE.toString();
+            scalingDropdown.dispatchEvent(new CustomEvent('change'));
             return test_util.eventToPromise(
-                'update-checkbox-setting', scalingElement);
+                'process-select-change', scalingElement);
           })
           .then(function(event) {
-            assertEquals('fitToPage', event.detail);
-            validateScalingState('95', true, true, true);
+            validateScalingState(true, '105', '105', true, true);
 
-            // Uncheck fit to page. Should reset scaling to last valid.
-            fitToPageCheckbox.checked = false;
-            fitToPageCheckbox.dispatchEvent(new CustomEvent('change'));
+            // Custom scaling should set to last valid.
+            scalingDropdown.value =
+                scalingElement.scalingValueEnum_.CUSTOM.toString();
+            scalingDropdown.dispatchEvent(new CustomEvent('change'));
             return test_util.eventToPromise(
-                'update-checkbox-setting', scalingElement);
+                'process-select-change', scalingElement);
           })
           .then(function(event) {
-            assertEquals('fitToPage', event.detail);
-            validateScalingState('95', true, false, false);
+            validateScalingState(true, '105', '105', true, false);
 
-            // Change to fit to page. Should display fit to page scaling but not
-            // alter the scaling setting.
-            fitToPageCheckbox.checked = true;
-            fitToPageCheckbox.dispatchEvent(new CustomEvent('change'));
-            return test_util.eventToPromise(
-                'update-checkbox-setting', scalingElement);
-          })
-          .then(function(event) {
-            assertEquals('fitToPage', event.detail);
-            validateScalingState('95', true, true, true);
-
-            // Enter something invalid in the scaling field. This should not
-            // change the stored value of scaling or fit to page, to avoid an
-            // unnecessary preview regeneration, but should display fit to page
-            // as unchecked.
-            print_preview_test_utils.triggerInputEvent(scalingInput, '9');
+            // Set scaling to something invalid. Should change setting validity
+            // but not value.
+            print_preview_test_utils.triggerInputEvent(scalingInput, '500');
             return test_util.eventToPromise('input-change', scalingElement);
           })
           .then(function() {
-            validateScalingState('95', false, true, false);
+            validateScalingState(true, '105', '500', false, false);
+
+            // Pick default scaling. This should clear the error.
+            scalingDropdown.value =
+                scalingElement.scalingValueEnum_.DEFAULT.toString();
+            scalingDropdown.dispatchEvent(new CustomEvent('change'));
+            return test_util.eventToPromise(
+                'process-select-change', scalingElement);
+          })
+          .then(function(event) {
+            validateScalingState(false, '105', '105', true, false);
+
+            // Custom scaling should set to last valid.
+            scalingDropdown.value =
+                scalingElement.scalingValueEnum_.CUSTOM.toString();
+            scalingDropdown.dispatchEvent(new CustomEvent('change'));
+            return test_util.eventToPromise(
+                'process-select-change', scalingElement);
+          })
+          .then(function() {
+            validateScalingState(true, '105', '105', true, false);
 
             // Enter a blank value in the scaling field. This should not
             // change the stored value of scaling or fit to page, to avoid an
@@ -1013,15 +1048,7 @@
             return test_util.eventToPromise('input-change', scalingElement);
           })
           .then(function() {
-            validateScalingState('95', false, true, false);
-
-            // Entering something valid unsets fit to page and sets scaling
-            // valid to true.
-            print_preview_test_utils.triggerInputEvent(scalingInput, '90');
-            return test_util.eventToPromise('input-change', scalingElement);
-          })
-          .then(function() {
-            validateScalingState('90', true, false, false);
+            validateScalingState(true, '105', '', true, false);
           });
     });
 
diff --git a/chrome/test/data/webui/print_preview/system_dialog_browsertest.js b/chrome/test/data/webui/print_preview/system_dialog_browsertest.js
index bf376b5..237ebba 100644
--- a/chrome/test/data/webui/print_preview/system_dialog_browsertest.js
+++ b/chrome/test/data/webui/print_preview/system_dialog_browsertest.js
@@ -83,21 +83,29 @@
       assertFalse(scalingSettings.hidden);
       nativeLayer.resetResolver('getPreview');
 
-      // Set scaling settings to a bad value
-      const scalingSettingsInput =
-          scalingSettings.$$('print-preview-number-settings-section')
-              .$.userValue.inputElement;
-      scalingSettingsInput.value = '0';
-      scalingSettingsInput.dispatchEvent(
-          new CustomEvent('input', {composed: true, bubbles: true}));
+      // Set scaling settings to custom.
+      scalingSettings.$$('.md-select').value =
+          scalingSettings.scalingValueEnum_.CUSTOM;
+      scalingSettings.$$('.md-select').dispatchEvent(new CustomEvent('change'));
+      return nativeLayer.whenCalled('getPreview')
+          .then(() => {
+            nativeLayer.resetResolver('getPreview');
+            // Set an invalid input.
+            const scalingSettingsInput =
+                scalingSettings.$$('print-preview-number-settings-section')
+                    .$.userValue.inputElement;
+            scalingSettingsInput.value = '0';
+            scalingSettingsInput.dispatchEvent(
+                new CustomEvent('input', {composed: true, bubbles: true}));
 
-      // No new preview
-      nativeLayer.whenCalled('getPreview').then(function() {
-        assertTrue(false);
-      });
+            // No new preview
+            nativeLayer.whenCalled('getPreview').then(function() {
+              assertTrue(false);
+            });
 
-      return test_util.eventToPromise('input-change', scalingSettings)
-          .then(function() {
+            return test_util.eventToPromise('input-change', scalingSettings);
+          })
+          .then(() => {
             // Expect disabled print button
             const header = page.$$('print-preview-header');
             const printButton = header.$$('.action-button');