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
11function $(id) {
12  return document.getElementById(id);
13}
14
15/**
16 * @namespace
17 */
18var 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 */
25advancedFonts.COMMON_SCRIPT = 'Zyyy';
26
27/**
28 * The scripts supported by the Font Settings Extension API.
29 * @const
30 */
31advancedFonts.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: 'N’Ko'},
125  { scriptCode: 'Nshu', scriptName: 'Nüshu'},
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 */
190advancedFonts.FAMILIES =
191    ['standard', 'sansserif', 'serif', 'fixed', 'cursive', 'fantasy'];
192
193/**
194 * Sample texts.
195 * @const
196 */
197advancedFonts.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 */
213advancedFonts.pendingChanges = new PendingChanges();
214
215/**
216 * Map from |genericFamily| to UI controls and data for its font setting.
217 */
218advancedFonts.fontSettings = null;
219
220/**
221 * Map from |fontSizeKey| to UI contols and data for its font size setting.
222 */
223advancedFonts.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 */
235advancedFonts.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 */
268advancedFonts.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 */
321advancedFonts.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 */
337advancedFonts.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 */
348advancedFonts.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 */
379advancedFonts.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 */
388advancedFonts.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 */
397advancedFonts.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 */
424advancedFonts.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 */
437advancedFonts.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 */
458advancedFonts.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 */
472advancedFonts.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 */
483advancedFonts.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 */
503advancedFonts.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 */
533advancedFonts.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 */
546advancedFonts.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 */
558advancedFonts.closeOverlay = function() {
559  $('overlay-container').hidden = true;
560};
561
562/**
563 * Initializes apply and reset buttons.
564 */
565advancedFonts.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 */
614advancedFonts.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 */
625advancedFonts.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 */
637advancedFonts.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 */
646advancedFonts.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 */
669advancedFonts.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 */
701advancedFonts.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 */
741advancedFonts.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 */
758advancedFonts.init = function() {
759  advancedFonts.useSystemFont();
760
761  advancedFonts.initFontControls();
762  advancedFonts.initFontSizeControls();
763  advancedFonts.initScriptList();
764
765  advancedFonts.initApplyAndResetButtons();
766};
767
768document.addEventListener('DOMContentLoaded', advancedFonts.init);
769