Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(223)

Side by Side Diff: chrome/browser/resources/options/language_list.js

Issue 7003007: Apply content-security-policy to the HTML options page. This is a (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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 cr.define('options', function() {
6 const ArrayDataModel = cr.ui.ArrayDataModel;
7 const DeletableItem = options.DeletableItem;
8 const DeletableItemList = options.DeletableItemList;
9 const List = cr.ui.List;
10 const ListItem = cr.ui.ListItem;
11 const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
12
13 /**
14 * Creates a new Language list item.
15 * @param {String} languageCode the languageCode.
16 * @constructor
17 * @extends {DeletableItem.ListItem}
18 */
19 function LanguageListItem(languageCode) {
20 var el = cr.doc.createElement('li');
21 el.__proto__ = LanguageListItem.prototype;
22 el.languageCode_ = languageCode;
23 el.decorate();
24 return el;
25 };
26
27 LanguageListItem.prototype = {
28 __proto__: DeletableItem.prototype,
29
30 /**
31 * The language code of this language.
32 * @type {String}
33 * @private
34 */
35 languageCode_: null,
36
37 /** @inheritDoc */
38 decorate: function() {
39 DeletableItem.prototype.decorate.call(this);
40
41 var languageCode = this.languageCode_;
42 var languageOptions = options.LanguageOptions.getInstance();
43 this.deletable = languageOptions.languageIsDeletable(languageCode);
44 this.languageCode = languageCode;
45 this.contentElement.textContent =
46 LanguageList.getDisplayNameFromLanguageCode(languageCode);
47 this.title =
48 LanguageList.getNativeDisplayNameFromLanguageCode(languageCode);
49 this.draggable = true;
50 },
51 };
52
53 /**
54 * Creates a new language list.
55 * @param {Object=} opt_propertyBag Optional properties.
56 * @constructor
57 * @extends {cr.ui.List}
58 */
59 var LanguageList = cr.ui.define('list');
60
61 /**
62 * Gets display name from the given language code.
63 * @param {string} languageCode Language code (ex. "fr").
64 */
65 LanguageList.getDisplayNameFromLanguageCode = function(languageCode) {
66 // Build the language code to display name dictionary at first time.
67 if (!this.languageCodeToDisplayName_) {
68 this.languageCodeToDisplayName_ = {};
69 var languageList = templateData.languageList;
70 for (var i = 0; i < languageList.length; i++) {
71 var language = languageList[i];
72 this.languageCodeToDisplayName_[language.code] = language.displayName;
73 }
74 }
75
76 return this.languageCodeToDisplayName_[languageCode];
77 }
78
79 /**
80 * Gets native display name from the given language code.
81 * @param {string} languageCode Language code (ex. "fr").
82 */
83 LanguageList.getNativeDisplayNameFromLanguageCode = function(languageCode) {
84 // Build the language code to display name dictionary at first time.
85 if (!this.languageCodeToNativeDisplayName_) {
86 this.languageCodeToNativeDisplayName_ = {};
87 var languageList = templateData.languageList;
88 for (var i = 0; i < languageList.length; i++) {
89 var language = languageList[i];
90 this.languageCodeToNativeDisplayName_[language.code] =
91 language.nativeDisplayName;
92 }
93 }
94
95 return this.languageCodeToNativeDisplayName_[languageCode];
96 }
97
98 /**
99 * Returns true if the given language code is valid.
100 * @param {string} languageCode Language code (ex. "fr").
101 */
102 LanguageList.isValidLanguageCode = function(languageCode) {
103 // Having the display name for the language code means that the
104 // language code is valid.
105 if (LanguageList.getDisplayNameFromLanguageCode(languageCode)) {
106 return true;
107 }
108 return false;
109 }
110
111 LanguageList.prototype = {
112 __proto__: DeletableItemList.prototype,
113
114 // The list item being dragged.
115 draggedItem: null,
116 // The drop position information: "below" or "above".
117 dropPos: null,
118 // The preference is a CSV string that describes preferred languages
119 // in Chrome OS. The language list is used for showing the language
120 // list in "Language and Input" options page.
121 preferredLanguagesPref: 'settings.language.preferred_languages',
122 // The preference is a CSV string that describes accept languages used
123 // for content negotiation. To be more precise, the list will be used
124 // in "Accept-Language" header in HTTP requests.
125 acceptLanguagesPref: 'intl.accept_languages',
126
127 /** @inheritDoc */
128 decorate: function() {
129 DeletableItemList.prototype.decorate.call(this);
130 this.selectionModel = new ListSingleSelectionModel;
131
132 // HACK(arv): http://crbug.com/40902
133 window.addEventListener('resize', this.redraw.bind(this));
134
135 // Listen to pref change.
136 if (cr.isChromeOS) {
137 Preferences.getInstance().addEventListener(this.preferredLanguagesPref,
138 this.handlePreferredLanguagesPrefChange_.bind(this));
139 } else {
140 Preferences.getInstance().addEventListener(this.acceptLanguagesPref,
141 this.handleAcceptLanguagesPrefChange_.bind(this));
142 }
143
144 // Listen to drag and drop events.
145 this.addEventListener('dragstart', this.handleDragStart_.bind(this));
146 this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
147 this.addEventListener('dragover', this.handleDragOver_.bind(this));
148 this.addEventListener('drop', this.handleDrop_.bind(this));
149 this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
150 },
151
152 createItem: function(languageCode) {
153 return new LanguageListItem(languageCode);
154 },
155
156 /*
157 * For each item, determines whether it's deletable.
158 */
159 updateDeletable: function() {
160 for (var i = 0; i < this.items.length; ++i) {
161 var item = this.getListItemByIndex(i);
162 var languageCode = item.languageCode;
163 var languageOptions = options.LanguageOptions.getInstance();
164 item.deletable = languageOptions.languageIsDeletable(languageCode);
165 }
166 },
167
168 /*
169 * Adds a language to the language list.
170 * @param {string} languageCode language code (ex. "fr").
171 */
172 addLanguage: function(languageCode) {
173 // It shouldn't happen but ignore the language code if it's
174 // null/undefined, or already present.
175 if (!languageCode || this.dataModel.indexOf(languageCode) >= 0) {
176 return;
177 }
178 this.dataModel.push(languageCode);
179 // Select the last item, which is the language added.
180 this.selectionModel.selectedIndex = this.dataModel.length - 1;
181
182 this.savePreference_();
183 },
184
185 /*
186 * Gets the language codes of the currently listed languages.
187 */
188 getLanguageCodes: function() {
189 return this.dataModel.slice();
190 },
191
192 /*
193 * Gets the language code of the selected language.
194 */
195 getSelectedLanguageCode: function() {
196 return this.selectedItem;
197 },
198
199 /*
200 * Selects the language by the given language code.
201 * @returns {boolean} True if the operation is successful.
202 */
203 selectLanguageByCode: function(languageCode) {
204 var index = this.dataModel.indexOf(languageCode);
205 if (index >= 0) {
206 this.selectionModel.selectedIndex = index;
207 return true;
208 }
209 return false;
210 },
211
212 /** @inheritDoc */
213 deleteItemAtIndex: function(index) {
214 if (index >= 0) {
215 this.dataModel.splice(index, 1);
216 // Once the selected item is removed, there will be no selected item.
217 // Select the item pointed by the lead index.
218 index = this.selectionModel.leadIndex;
219 this.savePreference_();
220 }
221 return index;
222 },
223
224 /*
225 * Computes the target item of drop event.
226 * @param {Event} e The drop or dragover event.
227 * @private
228 */
229 getTargetFromDropEvent_ : function(e) {
230 var target = e.target;
231 // e.target may be an inner element of the list item
232 while (target != null && !(target instanceof ListItem)) {
233 target = target.parentNode;
234 }
235 return target;
236 },
237
238 /*
239 * Handles the dragstart event.
240 * @param {Event} e The dragstart event.
241 * @private
242 */
243 handleDragStart_: function(e) {
244 var target = e.target;
245 // ListItem should be the only draggable element type in the page,
246 // but just in case.
247 if (target instanceof ListItem) {
248 this.draggedItem = target;
249 e.dataTransfer.effectAllowed = 'move';
250 // We need to put some kind of data in the drag or it will be
251 // ignored. Use the display name in case the user drags to a text
252 // field or the desktop.
253 e.dataTransfer.setData('text/plain', target.title);
254 }
255 },
256
257 /*
258 * Handles the dragenter event.
259 * @param {Event} e The dragenter event.
260 * @private
261 */
262 handleDragEnter_: function(e) {
263 e.preventDefault();
264 },
265
266 /*
267 * Handles the dragover event.
268 * @param {Event} e The dragover event.
269 * @private
270 */
271 handleDragOver_: function(e) {
272 var dropTarget = this.getTargetFromDropEvent_(e);
273 // Determines whether the drop target is to accept the drop.
274 // The drop is only successful on another ListItem.
275 if (!(dropTarget instanceof ListItem) ||
276 dropTarget == this.draggedItem) {
277 this.hideDropMarker_();
278 return;
279 }
280 // Compute the drop postion. Should we move the dragged item to
281 // below or above the drop target?
282 var rect = dropTarget.getBoundingClientRect();
283 var dy = e.clientY - rect.top;
284 var yRatio = dy / rect.height;
285 var dropPos = yRatio <= .5 ? 'above' : 'below';
286 this.dropPos = dropPos;
287 this.showDropMarker_(dropTarget, dropPos);
288 e.preventDefault();
289 },
290
291 /*
292 * Handles the drop event.
293 * @param {Event} e The drop event.
294 * @private
295 */
296 handleDrop_: function(e) {
297 var dropTarget = this.getTargetFromDropEvent_(e);
298 this.hideDropMarker_();
299
300 // Delete the language from the original position.
301 var languageCode = this.draggedItem.languageCode;
302 var originalIndex = this.dataModel.indexOf(languageCode);
303 this.dataModel.splice(originalIndex, 1);
304 // Insert the language to the new position.
305 var newIndex = this.dataModel.indexOf(dropTarget.languageCode);
306 if (this.dropPos == 'below')
307 newIndex += 1;
308 this.dataModel.splice(newIndex, 0, languageCode);
309 // The cursor should move to the moved item.
310 this.selectionModel.selectedIndex = newIndex;
311 // Save the preference.
312 this.savePreference_();
313 },
314
315 /*
316 * Handles the dragleave event.
317 * @param {Event} e The dragleave event
318 * @private
319 */
320 handleDragLeave_ : function(e) {
321 this.hideDropMarker_();
322 },
323
324 /*
325 * Shows and positions the marker to indicate the drop target.
326 * @param {HTMLElement} target The current target list item of drop
327 * @param {string} pos 'below' or 'above'
328 * @private
329 */
330 showDropMarker_ : function(target, pos) {
331 window.clearTimeout(this.hideDropMarkerTimer_);
332 var marker = $('language-options-list-dropmarker');
333 var rect = target.getBoundingClientRect();
334 var markerHeight = 8;
335 if (pos == 'above') {
336 marker.style.top = (rect.top - markerHeight/2) + 'px';
337 } else {
338 marker.style.top = (rect.bottom - markerHeight/2) + 'px';
339 }
340 marker.style.width = rect.width + 'px';
341 marker.style.left = rect.left + 'px';
342 marker.style.display = 'block';
343 },
344
345 /*
346 * Hides the drop marker.
347 * @private
348 */
349 hideDropMarker_ : function() {
350 // Hide the marker in a timeout to reduce flickering as we move between
351 // valid drop targets.
352 window.clearTimeout(this.hideDropMarkerTimer_);
353 this.hideDropMarkerTimer_ = window.setTimeout(function() {
354 $('language-options-list-dropmarker').style.display = '';
355 }, 100);
356 },
357
358 /**
359 * Handles preferred languages pref change.
360 * @param {Event} e The change event object.
361 * @private
362 */
363 handlePreferredLanguagesPrefChange_: function(e) {
364 var languageCodesInCsv = e.value.value;
365 var languageCodes = languageCodesInCsv.split(',');
366
367 // Add the UI language to the initial list of languages. This is to avoid
368 // a bug where the UI language would be removed from the preferred
369 // language list by sync on first login.
370 // See: crosbug.com/14283
371 languageCodes.push(navigator.language);
372 languageCodes = this.filterBadLanguageCodes_(languageCodes);
373 this.load_(languageCodes);
374 },
375
376 /**
377 * Handles accept languages pref change.
378 * @param {Event} e The change event object.
379 * @private
380 */
381 handleAcceptLanguagesPrefChange_: function(e) {
382 var languageCodesInCsv = e.value.value;
383 var languageCodes = this.filterBadLanguageCodes_(
384 languageCodesInCsv.split(','));
385 this.load_(languageCodes);
386 },
387
388 /**
389 * Loads given language list.
390 * @param {Array} languageCodes List of language codes.
391 * @private
392 */
393 load_: function(languageCodes) {
394 // Preserve the original selected index. See comments below.
395 var originalSelectedIndex = (this.selectionModel ?
396 this.selectionModel.selectedIndex : -1);
397 this.dataModel = new ArrayDataModel(languageCodes);
398 if (originalSelectedIndex >= 0 &&
399 originalSelectedIndex < this.dataModel.length) {
400 // Restore the original selected index if the selected index is
401 // valid after the data model is loaded. This is neeeded to keep
402 // the selected language after the languge is added or removed.
403 this.selectionModel.selectedIndex = originalSelectedIndex;
404 // The lead index should be updated too.
405 this.selectionModel.leadIndex = originalSelectedIndex;
406 } else if (this.dataModel.length > 0){
407 // Otherwise, select the first item if it's not empty.
408 // Note that ListSingleSelectionModel won't select an item
409 // automatically, hence we manually select the first item here.
410 this.selectionModel.selectedIndex = 0;
411 }
412 },
413
414 /**
415 * Saves the preference.
416 */
417 savePreference_: function() {
418 // Encode the language codes into a CSV string.
419 if (cr.isChromeOS)
420 Preferences.setStringPref(this.preferredLanguagesPref,
421 this.dataModel.slice().join(','));
422 // Save the same language list as accept languages preference as
423 // well, but we need to expand the language list, to make it more
424 // acceptable. For instance, some web sites don't understand 'en-US'
425 // but 'en'. See crosbug.com/9884.
426 var acceptLanguages = this.expandLanguageCodes(this.dataModel.slice());
427 Preferences.setStringPref(this.acceptLanguagesPref,
428 acceptLanguages.join(','));
429 cr.dispatchSimpleEvent(this, 'save');
430 },
431
432 /**
433 * Expands language codes to make these more suitable for Accept-Language.
434 * Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
435 * 'en' won't appear twice as this function eliminates duplicates.
436 * @param {Array} languageCodes List of language codes.
437 * @private
438 */
439 expandLanguageCodes: function(languageCodes) {
440 var expandedLanguageCodes = [];
441 var seen = {}; // Used to eliminiate duplicates.
442 for (var i = 0; i < languageCodes.length; i++) {
443 var languageCode = languageCodes[i];
444 if (!(languageCode in seen)) {
445 expandedLanguageCodes.push(languageCode);
446 seen[languageCode] = true;
447 }
448 var parts = languageCode.split('-');
449 if (!(parts[0] in seen)) {
450 expandedLanguageCodes.push(parts[0]);
451 seen[parts[0]] = true;
452 }
453 }
454 return expandedLanguageCodes;
455 },
456
457 /**
458 * Filters bad language codes in case bad language codes are
459 * stored in the preference. Removes duplicates as well.
460 * @param {Array} languageCodes List of language codes.
461 * @private
462 */
463 filterBadLanguageCodes_: function(languageCodes) {
464 var filteredLanguageCodes = [];
465 var seen = {};
466 for (var i = 0; i < languageCodes.length; i++) {
467 // Check if the the language code is valid, and not
468 // duplicate. Otherwise, skip it.
469 if (LanguageList.isValidLanguageCode(languageCodes[i]) &&
470 !(languageCodes[i] in seen)) {
471 filteredLanguageCodes.push(languageCodes[i]);
472 seen[languageCodes[i]] = true;
473 }
474 }
475 return filteredLanguageCodes;
476 },
477 };
478
479 return {
480 LanguageList: LanguageList,
481 LanguageListItem: LanguageListItem
482 };
483 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698