Home | History | Annotate | Download | only in fontSettings
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 'use strict';
      6 
      7 /**
      8  * @fileoverview The Advanced Font Settings Extension implementation.
      9  */
     10 
     11 function $(id) {
     12   return document.getElementById(id);
     13 }
     14 
     15 /**
     16  * @namespace
     17  */
     18 var advancedFonts = {};
     19 
     20 /**
     21  * The ICU script code for the Common, or global, script, which is used as the
     22  * fallback when the script is undeclared.
     23  * @const
     24  */
     25 advancedFonts.COMMON_SCRIPT = 'Zyyy';
     26 
     27 /**
     28  * The scripts supported by the Font Settings Extension API.
     29  * @const
     30  */
     31 advancedFonts.scripts = [
     32   { scriptCode: advancedFonts.COMMON_SCRIPT, scriptName: 'Default'},
     33   { scriptCode: 'Afak', scriptName: 'Afaka'},
     34   { scriptCode: 'Arab', scriptName: 'Arabic'},
     35   { scriptCode: 'Armi', scriptName: 'Imperial Aramaic'},
     36   { scriptCode: 'Armn', scriptName: 'Armenian'},
     37   { scriptCode: 'Avst', scriptName: 'Avestan'},
     38   { scriptCode: 'Bali', scriptName: 'Balinese'},
     39   { scriptCode: 'Bamu', scriptName: 'Bamum'},
     40   { scriptCode: 'Bass', scriptName: 'Bassa Vah'},
     41   { scriptCode: 'Batk', scriptName: 'Batak'},
     42   { scriptCode: 'Beng', scriptName: 'Bengali'},
     43   { scriptCode: 'Blis', scriptName: 'Blissymbols'},
     44   { scriptCode: 'Bopo', scriptName: 'Bopomofo'},
     45   { scriptCode: 'Brah', scriptName: 'Brahmi'},
     46   { scriptCode: 'Brai', scriptName: 'Braille'},
     47   { scriptCode: 'Bugi', scriptName: 'Buginese'},
     48   { scriptCode: 'Buhd', scriptName: 'Buhid'},
     49   { scriptCode: 'Cakm', scriptName: 'Chakma'},
     50   { scriptCode: 'Cans', scriptName: 'Unified Canadian Aboriginal Syllabics'},
     51   { scriptCode: 'Cari', scriptName: 'Carian'},
     52   { scriptCode: 'Cham', scriptName: 'Cham'},
     53   { scriptCode: 'Cher', scriptName: 'Cherokee'},
     54   { scriptCode: 'Cirt', scriptName: 'Cirth'},
     55   { scriptCode: 'Copt', scriptName: 'Coptic'},
     56   { scriptCode: 'Cprt', scriptName: 'Cypriot'},
     57   { scriptCode: 'Cyrl', scriptName: 'Cyrillic'},
     58   { scriptCode: 'Cyrs', scriptName: 'Old Church Slavonic Cyrillic'},
     59   { scriptCode: 'Deva', scriptName: 'Devanagari'},
     60   { scriptCode: 'Dsrt', scriptName: 'Deseret'},
     61   { scriptCode: 'Dupl', scriptName: 'Duployan shorthand'},
     62   { scriptCode: 'Egyd', scriptName: 'Egyptian demotic'},
     63   { scriptCode: 'Egyh', scriptName: 'Egyptian hieratic'},
     64   { scriptCode: 'Egyp', scriptName: 'Egyptian hieroglyphs'},
     65   { scriptCode: 'Elba', scriptName: 'Elbasan'},
     66   { scriptCode: 'Ethi', scriptName: 'Ethiopic'},
     67   { scriptCode: 'Geok', scriptName: 'Georgian Khutsuri'},
     68   { scriptCode: 'Geor', scriptName: 'Georgian'},
     69   { scriptCode: 'Glag', scriptName: 'Glagolitic'},
     70   { scriptCode: 'Goth', scriptName: 'Gothic'},
     71   { scriptCode: 'Gran', scriptName: 'Grantha'},
     72   { scriptCode: 'Grek', scriptName: 'Greek'},
     73   { scriptCode: 'Gujr', scriptName: 'Gujarati'},
     74   { scriptCode: 'Guru', scriptName: 'Gurmukhi'},
     75   { scriptCode: 'Hang', scriptName: 'Hangul'},
     76   { scriptCode: 'Hani', scriptName: 'Han'},
     77   { scriptCode: 'Hano', scriptName: 'Hanunoo'},
     78   { scriptCode: 'Hans', scriptName: 'Simplified Han'},
     79   { scriptCode: 'Hant', scriptName: 'Traditional Han'},
     80   { scriptCode: 'Hebr', scriptName: 'Hebrew'},
     81   { scriptCode: 'Hluw', scriptName: 'Anatolian Hieroglyphs'},
     82   { scriptCode: 'Hmng', scriptName: 'Pahawh Hmong'},
     83   { scriptCode: 'Hung', scriptName: 'Old Hungarian'},
     84   { scriptCode: 'Inds', scriptName: 'Indus'},
     85   { scriptCode: 'Ital', scriptName: 'Old Italic'},
     86   { scriptCode: 'Java', scriptName: 'Javanese'},
     87   { scriptCode: 'Jpan', scriptName: 'Japanese'},
     88   { scriptCode: 'Jurc', scriptName: 'Jurchen'},
     89   { scriptCode: 'Kali', scriptName: 'Kayah Li'},
     90   { scriptCode: 'Khar', scriptName: 'Kharoshthi'},
     91   { scriptCode: 'Khmr', scriptName: 'Khmer'},
     92   { scriptCode: 'Khoj', scriptName: 'Khojki'},
     93   { scriptCode: 'Knda', scriptName: 'Kannada'},
     94   { scriptCode: 'Kpel', scriptName: 'Kpelle'},
     95   { scriptCode: 'Kthi', scriptName: 'Kaithi'},
     96   { scriptCode: 'Lana', scriptName: 'Lanna'},
     97   { scriptCode: 'Laoo', scriptName: 'Lao'},
     98   { scriptCode: 'Latf', scriptName: 'Fraktur Latin'},
     99   { scriptCode: 'Latg', scriptName: 'Gaelic Latin'},
    100   { scriptCode: 'Latn', scriptName: 'Latin'},
    101   { scriptCode: 'Lepc', scriptName: 'Lepcha'},
    102   { scriptCode: 'Limb', scriptName: 'Limbu'},
    103   { scriptCode: 'Lina', scriptName: 'Linear A'},
    104   { scriptCode: 'Linb', scriptName: 'Linear B'},
    105   { scriptCode: 'Lisu', scriptName: 'Fraser'},
    106   { scriptCode: 'Loma', scriptName: 'Loma'},
    107   { scriptCode: 'Lyci', scriptName: 'Lycian'},
    108   { scriptCode: 'Lydi', scriptName: 'Lydian'},
    109   { scriptCode: 'Mand', scriptName: 'Mandaean'},
    110   { scriptCode: 'Mani', scriptName: 'Manichaean'},
    111   { scriptCode: 'Maya', scriptName: 'Mayan hieroglyphs'},
    112   { scriptCode: 'Mend', scriptName: 'Mende'},
    113   { scriptCode: 'Merc', scriptName: 'Meroitic Cursive'},
    114   { scriptCode: 'Mero', scriptName: 'Meroitic'},
    115   { scriptCode: 'Mlym', scriptName: 'Malayalam'},
    116   { scriptCode: 'Mong', scriptName: 'Mongolian'},
    117   { scriptCode: 'Moon', scriptName: 'Moon'},
    118   { scriptCode: 'Mroo', scriptName: 'Mro'},
    119   { scriptCode: 'Mtei', scriptName: 'Meitei Mayek'},
    120   { scriptCode: 'Mymr', scriptName: 'Myanmar'},
    121   { scriptCode: 'Narb', scriptName: 'Old North Arabian'},
    122   { scriptCode: 'Nbat', scriptName: 'Nabataean'},
    123   { scriptCode: 'Nkgb', scriptName: 'Naxi Geba'},
    124   { scriptCode: 'Nkoo', scriptName: 'NKo'},
    125   { scriptCode: 'Nshu', scriptName: 'Nshu'},
    126   { scriptCode: 'Ogam', scriptName: 'Ogham'},
    127   { scriptCode: 'Olck', scriptName: 'Ol Chiki'},
    128   { scriptCode: 'Orkh', scriptName: 'Orkhon'},
    129   { scriptCode: 'Orya', scriptName: 'Oriya'},
    130   { scriptCode: 'Osma', scriptName: 'Osmanya'},
    131   { scriptCode: 'Palm', scriptName: 'Palmyrene'},
    132   { scriptCode: 'Perm', scriptName: 'Old Permic'},
    133   { scriptCode: 'Phag', scriptName: 'Phags-pa'},
    134   { scriptCode: 'Phli', scriptName: 'Inscriptional Pahlavi'},
    135   { scriptCode: 'Phlp', scriptName: 'Psalter Pahlavi'},
    136   { scriptCode: 'Phlv', scriptName: 'Book Pahlavi'},
    137   { scriptCode: 'Phnx', scriptName: 'Phoenician'},
    138   { scriptCode: 'Plrd', scriptName: 'Pollard Phonetic'},
    139   { scriptCode: 'Prti', scriptName: 'Inscriptional Parthian'},
    140   { scriptCode: 'Rjng', scriptName: 'Rejang'},
    141   { scriptCode: 'Roro', scriptName: 'Rongorongo'},
    142   { scriptCode: 'Runr', scriptName: 'Runic'},
    143   { scriptCode: 'Samr', scriptName: 'Samaritan'},
    144   { scriptCode: 'Sara', scriptName: 'Sarati'},
    145   { scriptCode: 'Sarb', scriptName: 'Old South Arabian'},
    146   { scriptCode: 'Saur', scriptName: 'Saurashtra'},
    147   { scriptCode: 'Sgnw', scriptName: 'SignWriting'},
    148   { scriptCode: 'Shaw', scriptName: 'Shavian'},
    149   { scriptCode: 'Shrd', scriptName: 'Sharada'},
    150   { scriptCode: 'Sind', scriptName: 'Khudawadi'},
    151   { scriptCode: 'Sinh', scriptName: 'Sinhala'},
    152   { scriptCode: 'Sora', scriptName: 'Sora Sompeng'},
    153   { scriptCode: 'Sund', scriptName: 'Sundanese'},
    154   { scriptCode: 'Sylo', scriptName: 'Syloti Nagri'},
    155   { scriptCode: 'Syrc', scriptName: 'Syriac'},
    156   { scriptCode: 'Syre', scriptName: 'Estrangelo Syriac'},
    157   { scriptCode: 'Syrj', scriptName: 'Western Syriac'},
    158   { scriptCode: 'Syrn', scriptName: 'Eastern Syriac'},
    159   { scriptCode: 'Tagb', scriptName: 'Tagbanwa'},
    160   { scriptCode: 'Takr', scriptName: 'Takri'},
    161   { scriptCode: 'Tale', scriptName: 'Tai Le'},
    162   { scriptCode: 'Talu', scriptName: 'New Tai Lue'},
    163   { scriptCode: 'Taml', scriptName: 'Tamil'},
    164   { scriptCode: 'Tang', scriptName: 'Tangut'},
    165   { scriptCode: 'Tavt', scriptName: 'Tai Viet'},
    166   { scriptCode: 'Telu', scriptName: 'Telugu'},
    167   { scriptCode: 'Teng', scriptName: 'Tengwar'},
    168   { scriptCode: 'Tfng', scriptName: 'Tifinagh'},
    169   { scriptCode: 'Tglg', scriptName: 'Tagalog'},
    170   { scriptCode: 'Thaa', scriptName: 'Thaana'},
    171   { scriptCode: 'Thai', scriptName: 'Thai'},
    172   { scriptCode: 'Tibt', scriptName: 'Tibetan'},
    173   { scriptCode: 'Tirh', scriptName: 'Tirhuta'},
    174   { scriptCode: 'Ugar', scriptName: 'Ugaritic'},
    175   { scriptCode: 'Vaii', scriptName: 'Vai'},
    176   { scriptCode: 'Visp', scriptName: 'Visible Speech'},
    177   { scriptCode: 'Wara', scriptName: 'Varang Kshiti'},
    178   { scriptCode: 'Wole', scriptName: 'Woleai'},
    179   { scriptCode: 'Xpeo', scriptName: 'Old Persian'},
    180   { scriptCode: 'Xsux', scriptName: 'Sumero-Akkadian Cuneiform'},
    181   { scriptCode: 'Yiii', scriptName: 'Yi'},
    182   { scriptCode: 'Zmth', scriptName: 'Mathematical Notation'},
    183   { scriptCode: 'Zsym', scriptName: 'Symbols'}
    184 ];
    185 
    186 /**
    187  * The generic font families supported by the Font Settings Extension API.
    188  * @const
    189  */
    190 advancedFonts.FAMILIES =
    191     ['standard', 'sansserif', 'serif', 'fixed', 'cursive', 'fantasy'];
    192 
    193 /**
    194  * Sample texts.
    195  * @const
    196  */
    197 advancedFonts.SAMPLE_TEXTS = {
    198   // "Cyrllic script".
    199   Cyrl: '',
    200   Hang: '         .',
    201   Hans: '',
    202   Hant: '',
    203   Jpan: '',
    204   // "Khmer language".
    205   Khmr: '\u1797\u17B6\u179F\u17B6\u1781\u17D2\u1798\u17C2\u179A',
    206   Zyyy: 'The quick brown fox jumps over the lazy dog.'
    207 };
    208 
    209 /**
    210  * Controller of pending changes.
    211  * @const
    212  */
    213 advancedFonts.pendingChanges = new PendingChanges();
    214 
    215 /**
    216  * Map from |genericFamily| to UI controls and data for its font setting.
    217  */
    218 advancedFonts.fontSettings = null;
    219 
    220 /**
    221  * Map from |fontSizeKey| to UI contols and data for its font size setting.
    222  */
    223 advancedFonts.fontSizeSettings = null;
    224 
    225 /**
    226  * Gets the font size used for |fontSizeKey|, including pending changes. Calls
    227  * |callback| with the result.
    228  *
    229  * @param {string} fontSizeKey The font size setting key. See
    230  *     PendingChanges.getFontSize().
    231  * @param {function(number, boolean)} callback The callback of form
    232  *     function(size, controllable). |size| is the effective setting,
    233  *     |controllable| is whether the setting can be set.
    234  */
    235 advancedFonts.getEffectiveFontSize = function(fontSizeKey, callback) {
    236   advancedFonts.fontSizeSettings[fontSizeKey].getter({}, function(details) {
    237     var controllable = advancedFonts.isControllableLevel(
    238         details.levelOfControl);
    239     var size = details.pixelSize;
    240     var pendingFontSize = advancedFonts.pendingChanges.getFontSize(fontSizeKey);
    241     // If the setting is not controllable, we can have no pending change.
    242     if (!controllable) {
    243       if (pendingFontSize != null) {
    244         advancedFonts.pendingChanges.setFontSize(fontSizeKey, null);
    245         $('apply-settings').disabled = advancedFonts.pendingChanges.isEmpty();
    246         pendingFontSize = null;
    247       }
    248     }
    249 
    250     // If we have a pending change, it overrides the current setting.
    251     if (pendingFontSize != null)
    252       size = pendingFontSize;
    253     callback(size, controllable);
    254   });
    255 };
    256 
    257 /**
    258  * Gets the font used for |script| and |genericFamily|, including pending
    259  * changes. Calls |callback| with the result.
    260  *
    261  * @param {string} script The script code.
    262  * @param {string} genericFamily The generic family.
    263  * @param {function(string, boolean, string)} callback The callback of form
    264  *     function(font, controllable, effectiveFont). |font| is the setting
    265  *     (pending or not), |controllable| is whether the setting can be set,
    266  *     |effectiveFont| is the font used taking fallback into consideration.
    267  */
    268 advancedFonts.getEffectiveFont = function(script, genericFamily, callback) {
    269   var pendingChanges = advancedFonts.pendingChanges;
    270   var details = { script: script, genericFamily: genericFamily };
    271   chrome.fontSettings.getFont(details, function(result) {
    272     var setting = {};
    273     setting.font = result.fontId;
    274     setting.controllable =
    275         advancedFonts.isControllableLevel(result.levelOfControl);
    276     var pendingFont =
    277         pendingChanges.getFont(details.script, details.genericFamily);
    278     // If the setting is not controllable, we can have no pending change.
    279     if (!setting.controllable) {
    280       if (pendingFont != null) {
    281         pendingChanges.setFont(script, genericFamily, null);
    282         $('apply-settings').disabled = advancedFonts.pendingChanges.isEmpty();
    283         pendingFont = null;
    284       }
    285     }
    286 
    287     // If we have a pending change, it overrides the current setting.
    288     if (pendingFont != null)
    289       setting.font = pendingFont;
    290 
    291     // If we have a font, we're done.
    292     if (setting.font) {
    293       callback(setting.font, setting.controllable, setting.font);
    294       return;
    295     }
    296 
    297     // If we're still here, we have to fallback to common script, unless this
    298     // already is common script.
    299     if (script == advancedFonts.COMMON_SCRIPT) {
    300       callback('', setting.controllable, '');
    301       return;
    302     }
    303     advancedFonts.getEffectiveFont(
    304         advancedFonts.COMMON_SCRIPT,
    305         genericFamily,
    306         callback.bind(null, setting.font, setting.controllable));
    307   });
    308 };
    309 
    310 /**
    311  * Refreshes the UI controls related to a font setting.
    312  *
    313  * @param {{fontList: HTMLSelectElement, samples: Array.<HTMLElement>}}
    314  *     fontSetting The setting object (see advancedFonts.fontSettings).
    315  * @param {string} font The value of the font setting.
    316  * @param {boolean} controllable Whether the font setting can be controlled
    317  *     by this extension.
    318  * @param {string} effectiveFont The font used, including fallback to Common
    319  *     script.
    320  */
    321 advancedFonts.refreshFont = function(
    322     fontSetting, font, controllable, effectiveFont) {
    323   for (var i = 0; i < fontSetting.samples.length; ++i)
    324     fontSetting.samples[i].style.fontFamily = effectiveFont;
    325   advancedFonts.setSelectedFont(fontSetting.fontList, font);
    326   fontSetting.fontList.disabled = !controllable;
    327 };
    328 
    329 /**
    330  * Refreshes the UI controls related to a font size setting.
    331  *
    332  * @param {{label: HTMLElement, slider: Slider, samples: Array.<HTMLElement>}}
    333  *     fontSizeSetting The setting object (see advancedFonts.fontSizeSettings).
    334  * @param size The value of the font size setting.
    335  * @param controllable Whether the setting can be controlled by this extension.
    336  */
    337 advancedFonts.refreshFontSize = function(fontSizeSetting, size, controllable) {
    338   fontSizeSetting.label.textContent = 'Size: ' + size + 'px';
    339   advancedFonts.setFontSizeSlider(fontSizeSetting.slider, size, controllable);
    340   for (var i = 0; i < fontSizeSetting.samples.length; ++i)
    341     fontSizeSetting.samples[i].style.fontSize = size + 'px';
    342 };
    343 
    344 /**
    345  * Refreshes all UI controls to reflect the current settings, including pending
    346  * changes.
    347  */
    348 advancedFonts.refresh = function() {
    349   var script = advancedFonts.getSelectedScript();
    350   var sample;
    351   if (advancedFonts.SAMPLE_TEXTS[script])
    352     sample = advancedFonts.SAMPLE_TEXTS[script];
    353   else
    354     sample = advancedFonts.SAMPLE_TEXTS[advancedFonts.COMMON_SCRIPT];
    355   var sampleTexts = document.querySelectorAll('.sample-text-span');
    356   for (var i = 0; i < sampleTexts.length; i++)
    357     sampleTexts[i].textContent = sample;
    358 
    359   var setting;
    360   var callback;
    361   for (var genericFamily in advancedFonts.fontSettings) {
    362     setting = advancedFonts.fontSettings[genericFamily];
    363     callback = advancedFonts.refreshFont.bind(null, setting);
    364     advancedFonts.getEffectiveFont(script, genericFamily, callback);
    365   }
    366 
    367   for (var fontSizeKey in advancedFonts.fontSizeSettings) {
    368     setting = advancedFonts.fontSizeSettings[fontSizeKey];
    369     callback = advancedFonts.refreshFontSize.bind(null, setting);
    370     advancedFonts.getEffectiveFontSize(fontSizeKey, callback);
    371   }
    372 
    373   $('apply-settings').disabled = advancedFonts.pendingChanges.isEmpty();
    374 };
    375 
    376 /**
    377  * @return {string} The currently selected script code.
    378  */
    379 advancedFonts.getSelectedScript = function() {
    380   var scriptList = $('scriptList');
    381   return scriptList.options[scriptList.selectedIndex].value;
    382 };
    383 
    384 /**
    385  * @param {HTMLSelectElement} fontList The <select> containing a list of fonts.
    386  * @return {string} The currently selected value of |fontList|.
    387  */
    388 advancedFonts.getSelectedFont = function(fontList) {
    389   return fontList.options[fontList.selectedIndex].value;
    390 };
    391 
    392 /**
    393  * Populates the font lists.
    394  * @param {Array.<{fontId: string, displayName: string>} fonts The list of
    395  *     fonts on the system.
    396  */
    397 advancedFonts.populateFontLists = function(fonts) {
    398   for (var genericFamily in advancedFonts.fontSettings) {
    399     var list = advancedFonts.fontSettings[genericFamily].fontList;
    400 
    401     // Add a special item to indicate fallback to the non-per-script
    402     // font setting. The Font Settings API uses the empty string to indicate
    403     // fallback.
    404     var defaultItem = document.createElement('option');
    405     defaultItem.value = '';
    406     defaultItem.text = '(Use default)';
    407     list.add(defaultItem);
    408 
    409     for (var i = 0; i < fonts.length; ++i) {
    410       var item = document.createElement('option');
    411       item.value = fonts[i].fontId;
    412       item.text = fonts[i].displayName;
    413       list.add(item);
    414     }
    415   }
    416   advancedFonts.refresh();
    417 };
    418 
    419 /**
    420  * Handles change events on a <select> element for a font setting.
    421  * @param {string} genericFamily The generic family for the font setting.
    422  * @param {Event} event The change event.
    423  */
    424 advancedFonts.handleFontListChange = function(genericFamily, event) {
    425   var script = advancedFonts.getSelectedScript();
    426   var font = advancedFonts.getSelectedFont(event.target);
    427 
    428   advancedFonts.pendingChanges.setFont(script, genericFamily, font);
    429   advancedFonts.refresh();
    430 };
    431 
    432 /**
    433  * Sets the selected value of |fontList| to |fontId|.
    434  * @param {HTMLSelectElement} fontList The <select> containing a list of fonts.
    435  * @param {string} fontId The font to set |fontList|'s selection to.
    436  */
    437 advancedFonts.setSelectedFont = function(fontList, fontId) {
    438   var script = advancedFonts.getSelectedScript();
    439   var i;
    440   for (i = 0; i < fontList.length; i++) {
    441     if (fontId == fontList.options[i].value) {
    442       fontList.selectedIndex = i;
    443       break;
    444     }
    445   }
    446   if (i == fontList.length) {
    447     console.warn("font '" + fontId + "' for " + fontList.id + ' for ' +
    448         script + ' is not on the system');
    449   }
    450 };
    451 
    452 /**
    453  * Handles change events on a font size slider.
    454  * @param {string} fontSizeKey The key for the font size setting whose slider
    455  *     changed. See PendingChanges.getFont.
    456  * @param {string} value The new value of the slider.
    457  */
    458 advancedFonts.handleFontSizeSliderChange = function(fontSizeKey, value) {
    459   var pixelSize = parseInt(value);
    460   if (!isNaN(pixelSize)) {
    461     advancedFonts.pendingChanges.setFontSize(fontSizeKey, pixelSize);
    462     advancedFonts.refresh();
    463   }
    464 };
    465 
    466 /**
    467  * @param {string} levelOfControl The level of control string for a setting,
    468  *     as returned by the Font Settings Extension API.
    469  * @return {boolean} True if |levelOfControl| signifies that the extension can
    470  *     control the setting; otherwise, returns false.
    471  */
    472 advancedFonts.isControllableLevel = function(levelOfControl) {
    473   return levelOfControl == 'controllable_by_this_extension' ||
    474       levelOfControl == 'controlled_by_this_extension';
    475 };
    476 
    477 /*
    478  * Updates the specified font size slider's value and enabled property.
    479  * @param {Slider} slider The slider for a font size setting.
    480  * @param {number} size The value to set the slider to.
    481  * @param {boolean} enabled Whether to enable or disable the slider.
    482  */
    483 advancedFonts.setFontSizeSlider = function(slider, size, enabled) {
    484   if (slider.getValue() != size)
    485     slider.setValue(size);
    486   var inputElement = slider.getInput();
    487   if (enabled) {
    488     inputElement.parentNode.classList.remove('disabled');
    489     inputElement.disabled = false;
    490   } else {
    491     inputElement.parentNode.classList.add('disabled');
    492     inputElement.disabled = true;
    493   }
    494 };
    495 
    496 /**
    497  * Initializes the UI control elements related to the font size setting
    498  * |fontSizeKey| and registers listeners for the user adjusting its slider and
    499  * the setting changing on the browser-side.
    500  * @param {string} fontSizeKey The key for font size setting. See
    501  *     PendingChanges.getFont().
    502  */
    503 advancedFonts.initFontSizeSetting = function(fontSizeKey) {
    504   var fontSizeSettings = advancedFonts.fontSizeSettings;
    505   var setting = fontSizeSettings[fontSizeKey];
    506   var label = setting.label;
    507   var samples = setting.samples;
    508 
    509   setting.slider = new Slider(
    510       setting.sliderContainer,
    511       0,
    512       setting.minValue,
    513       setting.maxValue,
    514       advancedFonts.handleFontSizeSliderChange.bind(null, fontSizeKey)
    515   );
    516 
    517   var slider = setting.slider;
    518   setting.getter({}, function(details) {
    519     var size = details.pixelSize.toString();
    520     var controllable = advancedFonts.isControllableLevel(
    521         details.levelOfControl);
    522     advancedFonts.setFontSizeSlider(slider, size, controllable);
    523     for (var i = 0; i < samples.length; i++)
    524       samples[i].style.fontSize = size + 'px';
    525   });
    526   fontSizeSettings[fontSizeKey].onChanged.addListener(advancedFonts.refresh);
    527 };
    528 
    529 /**
    530  * Clears the font settings for the specified script.
    531  * @param {string} script The script code.
    532  */
    533 advancedFonts.clearSettingsForScript = function(script) {
    534   advancedFonts.pendingChanges.clearOneScript(script);
    535   for (var i = 0; i < advancedFonts.FAMILIES.length; i++) {
    536     chrome.fontSettings.clearFont({
    537       script: script,
    538       genericFamily: advancedFonts.FAMILIES[i]
    539     });
    540   }
    541 };
    542 
    543 /**
    544  * Clears all font and font size settings.
    545  */
    546 advancedFonts.clearAllSettings = function() {
    547   advancedFonts.pendingChanges.clear();
    548   for (var i = 0; i < advancedFonts.scripts.length; i++)
    549     advancedFonts.clearSettingsForScript(advancedFonts.scripts[i].scriptCode);
    550   chrome.fontSettings.clearDefaultFixedFontSize();
    551   chrome.fontSettings.clearDefaultFontSize();
    552   chrome.fontSettings.clearMinimumFontSize();
    553 };
    554 
    555 /**
    556  * Closes the overlay.
    557  */
    558 advancedFonts.closeOverlay = function() {
    559   $('overlay-container').hidden = true;
    560 };
    561 
    562 /**
    563  * Initializes apply and reset buttons.
    564  */
    565 advancedFonts.initApplyAndResetButtons = function() {
    566   var applyButton = $('apply-settings');
    567   applyButton.addEventListener('click', function() {
    568     advancedFonts.pendingChanges.apply();
    569     advancedFonts.refresh();
    570   });
    571 
    572   var overlay = $('overlay-container');
    573   cr.ui.overlay.globalInitialization();
    574   cr.ui.overlay.setupOverlay(overlay);
    575   overlay.addEventListener('cancelOverlay', advancedFonts.closeOverlay);
    576 
    577   $('reset-this-script-button').onclick = function(event) {
    578     var scriptList = $('scriptList');
    579     var scriptName = scriptList.options[scriptList.selectedIndex].text;
    580     $('reset-this-script-overlay-dialog-content').innerText =
    581         'Are you sure you want to reset settings for ' + scriptName +
    582         ' script?';
    583 
    584     $('overlay-container').hidden = false;
    585     $('reset-this-script-overlay-dialog').hidden = false;
    586     $('reset-all-scripts-overlay-dialog').hidden = true;
    587   };
    588   $('reset-this-script-ok').onclick = function(event) {
    589     advancedFonts.clearSettingsForScript(advancedFonts.getSelectedScript());
    590     advancedFonts.closeOverlay();
    591     advancedFonts.refresh();
    592   };
    593   $('reset-this-script-cancel').onclick = advancedFonts.closeOverlay;
    594 
    595   $('reset-all-button').onclick = function(event) {
    596     $('overlay-container').hidden = false;
    597     $('reset-all-scripts-overlay-dialog').hidden = false;
    598     $('reset-this-script-overlay-dialog').hidden = true;
    599   };
    600   $('reset-all-ok').onclick = function(event) {
    601     advancedFonts.clearAllSettings();
    602     advancedFonts.closeOverlay();
    603     advancedFonts.refresh();
    604   };
    605   $('reset-all-cancel').onclick = advancedFonts.closeOverlay;
    606 };
    607 
    608 /**
    609  * Best guess for system fonts, taken from the IDS_WEB_FONT_FAMILY strings in
    610  * Chrome.
    611  * TODO: The font should be localized like Chrome does.
    612  * @const
    613  */
    614 advancedFonts.systemFonts = {
    615   cros: 'Noto Sans UI, sans-serif',
    616   linux: 'Ubuntu, sans-serif',
    617   mac: 'Lucida Grande, sans-serif',
    618   win: 'Segoe UI, Tahoma, sans-serif',
    619   unknown: 'sans-serif'
    620 };
    621 
    622 /**
    623  * @return {string} The platform this extension is running on.
    624  */
    625 advancedFonts.getPlatform = function() {
    626   var ua = window.navigator.appVersion;
    627   if (ua.indexOf('Win') != -1) return 'win';
    628   if (ua.indexOf('Mac') != -1) return 'mac';
    629   if (ua.indexOf('Linux') != -1) return 'linux';
    630   if (ua.indexOf('CrOS') != -1) return 'cros';
    631   return 'unknown';
    632 };
    633 
    634 /**
    635  * Chrome settings tries to use the system font. So does this extension.
    636  */
    637 advancedFonts.useSystemFont = function() {
    638   document.body.style.fontFamily =
    639       advancedFonts.systemFonts[advancedFonts.getPlatform()];
    640 };
    641 
    642 /**
    643  * Sorts the list of script codes by scriptName. Someday this extension will
    644  * have localized script names, so the order will depend on locale.
    645  */
    646 advancedFonts.sortScripts = function() {
    647   var i;
    648   var scripts = advancedFonts.scripts;
    649   for (i = 0; i < scripts; ++i) {
    650     if (scripts[i].scriptCode == advancedFonts.COMMON_SCRIPT)
    651       break;
    652   }
    653   var defaultScript = scripts.splice(i, 1)[0];
    654 
    655   scripts.sort(function(a, b) {
    656     if (a.scriptName > b.scriptName)
    657       return 1;
    658     if (a.scriptName < b.scriptName)
    659       return -1;
    660     return 0;
    661   });
    662 
    663   scripts.unshift(defaultScript);
    664 };
    665 
    666 /**
    667  * Initializes UI controls for font settings.
    668  */
    669 advancedFonts.initFontControls = function() {
    670   advancedFonts.fontSettings = {
    671     standard: {
    672       fontList: $('standardFontList'),
    673       samples: [$('standardFontSample'), $('minFontSample')]
    674     },
    675     serif: {
    676       fontList: $('serifFontList'),
    677       samples: [$('serifFontSample')]
    678     },
    679     sansserif: {
    680       fontList: $('sansSerifFontList'),
    681       samples: [$('sansSerifFontSample')]
    682     },
    683     fixed: {
    684       fontList: $('fixedFontList'),
    685       samples: [$('fixedFontSample')]
    686     }
    687   };
    688 
    689   for (var genericFamily in advancedFonts.fontSettings) {
    690     var list = advancedFonts.fontSettings[genericFamily].fontList;
    691     list.addEventListener(
    692         'change', advancedFonts.handleFontListChange.bind(list, genericFamily));
    693   }
    694   chrome.fontSettings.onFontChanged.addListener(advancedFonts.refresh);
    695   chrome.fontSettings.getFontList(advancedFonts.populateFontLists);
    696 };
    697 
    698 /**
    699  * Initializes UI controls for font size settings.
    700  */
    701 advancedFonts.initFontSizeControls = function() {
    702   advancedFonts.fontSizeSettings = {
    703     defaultFontSize: {
    704       sliderContainer: $('defaultFontSizeSliderContainer'),
    705       minValue: 6,
    706       maxValue: 50,
    707       samples: [
    708         $('standardFontSample'), $('serifFontSample'), $('sansSerifFontSample')
    709       ],
    710       label: $('defaultFontSizeLabel'),
    711       getter: chrome.fontSettings.getDefaultFontSize,
    712       onChanged: chrome.fontSettings.onDefaultFontSizeChanged
    713     },
    714     defaultFixedFontSize: {
    715       sliderContainer: $('defaultFixedFontSizeSliderContainer'),
    716       minValue: 6,
    717       maxValue: 50,
    718       samples: [$('fixedFontSample')],
    719       label: $('fixedFontSizeLabel'),
    720       getter: chrome.fontSettings.getDefaultFixedFontSize,
    721       onChanged: chrome.fontSettings.onDefaultFixedFontSizeChanged
    722     },
    723     minFontSize: {
    724       sliderContainer: $('minFontSizeSliderContainer'),
    725       minValue: 6,
    726       maxValue: 24,
    727       samples: [$('minFontSample')],
    728       label: $('minFontSizeLabel'),
    729       getter: chrome.fontSettings.getMinimumFontSize,
    730       onChanged: chrome.fontSettings.onMinimumFontSizeChanged
    731     }
    732   };
    733 
    734   for (var fontSizeKey in advancedFonts.fontSizeSettings)
    735     advancedFonts.initFontSizeSetting(fontSizeKey);
    736 };
    737 
    738 /**
    739  * Initializes the list of scripts.
    740  */
    741 advancedFonts.initScriptList = function() {
    742   var scriptList = $('scriptList');
    743   advancedFonts.sortScripts();
    744   var scripts = advancedFonts.scripts;
    745   for (var i = 0; i < scripts.length; i++) {
    746     var script = document.createElement('option');
    747     script.value = scripts[i].scriptCode;
    748     script.text = scripts[i].scriptName;
    749     scriptList.add(script);
    750   }
    751   scriptList.selectedIndex = 0;
    752   scriptList.addEventListener('change', advancedFonts.refresh);
    753 };
    754 
    755 /**
    756  * Initializes the extension.
    757  */
    758 advancedFonts.init = function() {
    759   advancedFonts.useSystemFont();
    760 
    761   advancedFonts.initFontControls();
    762   advancedFonts.initFontSizeControls();
    763   advancedFonts.initScriptList();
    764 
    765   advancedFonts.initApplyAndResetButtons();
    766 };
    767 
    768 document.addEventListener('DOMContentLoaded', advancedFonts.init);
    769