// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This file exists to aggregate all of the javascript used by the
// settings page into a single file which will be flattened and served
// as a single resource.
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/////////////////////////////////////////////////////////////////////////////
// Preferences class:
/**
* Preferences class manages access to Chrome profile preferences.
* @constructor
*/
function Preferences() {
}
cr.addSingletonGetter(Preferences);
/**
* Sets value of a boolean preference.
* and signals its changed value.
* @param {string} name Preference name.
* @param {boolean} value New preference value.
* @param {string} metric User metrics identifier.
*/
Preferences.setBooleanPref = function(name, value, metric) {
var argumentList = [name, Boolean(value)];
if (metric != undefined) argumentList.push(metric);
chrome.send('setBooleanPref', argumentList);
};
/**
* Sets value of an integer preference.
* and signals its changed value.
* @param {string} name Preference name.
* @param {number} value New preference value.
* @param {string} metric User metrics identifier.
*/
Preferences.setIntegerPref = function(name, value, metric) {
var argumentList = [name, Number(value)];
if (metric != undefined) argumentList.push(metric);
chrome.send('setIntegerPref', argumentList);
};
/**
* Sets value of a double-valued preference.
* and signals its changed value.
* @param {string} name Preference name.
* @param {number} value New preference value.
* @param {string} metric User metrics identifier.
*/
Preferences.setDoublePref = function(name, value, metric) {
var argumentList = [name, Number(value)];
if (metric != undefined) argumentList.push(metric);
chrome.send('setDoublePref', argumentList);
};
/**
* Sets value of a string preference.
* and signals its changed value.
* @param {string} name Preference name.
* @param {string} value New preference value.
* @param {string} metric User metrics identifier.
*/
Preferences.setStringPref = function(name, value, metric) {
var argumentList = [name, String(value)];
if (metric != undefined) argumentList.push(metric);
chrome.send('setStringPref', argumentList);
};
/**
* Sets value of a string preference that represents a URL
* and signals its changed value. The value will be fixed to be a valid URL.
* @param {string} name Preference name.
* @param {string} value New preference value.
* @param {string} metric User metrics identifier.
*/
Preferences.setURLPref = function(name, value, metric) {
var argumentList = [name, String(value)];
if (metric != undefined) argumentList.push(metric);
chrome.send('setURLPref', argumentList);
};
/**
* Sets value of a JSON list preference.
* and signals its changed value.
* @param {string} name Preference name.
* @param {Array} value New preference value.
* @param {string} metric User metrics identifier.
*/
Preferences.setListPref = function(name, value, metric) {
var argumentList = [name, JSON.stringify(value)];
if (metric != undefined) argumentList.push(metric);
chrome.send('setListPref', argumentList);
};
/**
* Clears value of a JSON preference.
* @param {string} name Preference name.
* @param {string} metric User metrics identifier.
*/
Preferences.clearPref = function(name, metric) {
var argumentList = [name];
if (metric != undefined) argumentList.push(metric);
chrome.send('clearPref', argumentList);
};
Preferences.prototype = {
__proto__: cr.EventTarget.prototype,
// Map of registered preferences.
registeredPreferences_: {},
/**
* Adds an event listener to the target.
* @param {string} type The name of the event.
* @param {!Function|{handleEvent:Function}} handler The handler for the
* event. This is called when the event is dispatched.
*/
addEventListener: function(type, handler) {
cr.EventTarget.prototype.addEventListener.call(this, type, handler);
this.registeredPreferences_[type] = true;
},
/**
* Initializes preference reading and change notifications.
*/
initialize: function() {
var params1 = ['Preferences.prefsFetchedCallback'];
var params2 = ['Preferences.prefsChangedCallback'];
for (var prefName in this.registeredPreferences_) {
params1.push(prefName);
params2.push(prefName);
}
chrome.send('fetchPrefs', params1);
chrome.send('observePrefs', params2);
},
/**
* Helper function for flattening of dictionary passed via fetchPrefs
* callback.
* @param {string} prefix Preference name prefix.
* @param {object} dict Map with preference values.
*/
flattenMapAndDispatchEvent_: function(prefix, dict) {
for (var prefName in dict) {
if (typeof dict[prefName] == 'object' &&
!this.registeredPreferences_[prefix + prefName]) {
this.flattenMapAndDispatchEvent_(prefix + prefName + '.',
dict[prefName]);
} else {
var event = new cr.Event(prefix + prefName);
event.value = dict[prefName];
this.dispatchEvent(event);
}
}
}
};
/**
* Callback for fetchPrefs method.
* @param {object} dict Map of fetched property values.
*/
Preferences.prefsFetchedCallback = function(dict) {
Preferences.getInstance().flattenMapAndDispatchEvent_('', dict);
};
/**
* Callback for observePrefs method.
* @param {array} notification An array defining changed preference values.
* notification[0] contains name of the change preference while its new value
* is stored in notification[1].
*/
Preferences.prefsChangedCallback = function(notification) {
var event = new cr.Event(notification[0]);
event.value = notification[1];
Preferences.getInstance().dispatchEvent(event);
};
// Export
return {
Preferences: Preferences
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var Preferences = options.Preferences;
/**
* Allows an element to be disabled for several reasons.
* The element is disabled if at least one reason is true, and the reasons
* can be set separately.
* @private
* @param {!HTMLElement} el The element to update.
* @param {string} reason The reason for disabling the element.
* @param {boolean} disabled Whether the element should be disabled or enabled
* for the given |reason|.
*/
function updateDisabledState_(el, reason, disabled) {
if (!el.disabledReasons)
el.disabledReasons = {};
if (el.disabled && (Object.keys(el.disabledReasons).length == 0)) {
// The element has been previously disabled without a reason, so we add
// one to keep it disabled.
el.disabledReasons['other'] = true;
}
if (!el.disabled) {
// If the element is not disabled, there should be no reason, except for
// 'other'.
delete el.disabledReasons['other'];
if (Object.keys(el.disabledReasons).length > 0)
console.error("Element is not disabled but should be");
}
if (disabled) {
el.disabledReasons[reason] = true;
} else {
delete el.disabledReasons[reason];
}
el.disabled = Object.keys(el.disabledReasons).length > 0;
}
/**
* Helper function to update element's state from pref change event.
* @private
* @param {!HTMLElement} el The element to update.
* @param {!Event} event The pref change event.
*/
function updateElementState_(el, event) {
el.controlledBy = null;
if (!event.value)
return;
updateDisabledState_(el, 'notUserModifiable', event.value.disabled);
el.controlledBy = event.value['controlledBy'];
OptionsPage.updateManagedBannerVisibility();
}
/////////////////////////////////////////////////////////////////////////////
// PrefCheckbox class:
// TODO(jhawkins): Refactor all this copy-pasted code!
// Define a constructor that uses an input element as its underlying element.
var PrefCheckbox = cr.ui.define('input');
PrefCheckbox.prototype = {
// Set up the prototype chain
__proto__: HTMLInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
this.type = 'checkbox';
var self = this;
self.initializeValueType(self.getAttribute('value-type'));
// Listen to pref changes.
Preferences.getInstance().addEventListener(
this.pref,
function(event) {
var value = event.value && event.value['value'] != undefined ?
event.value['value'] : event.value;
// Invert pref value if inverted_pref == true.
if (self.inverted_pref)
self.checked = !Boolean(value);
else
self.checked = Boolean(value);
updateElementState_(self, event);
});
// Listen to user events.
this.addEventListener(
'change',
function(e) {
if (self.customChangeHandler(e))
return;
var value = self.inverted_pref ? !self.checked : self.checked;
switch(self.valueType) {
case 'number':
Preferences.setIntegerPref(self.pref,
Number(value), self.metric);
break;
case 'boolean':
Preferences.setBooleanPref(self.pref,
value, self.metric);
break;
}
});
},
/**
* Sets up options in checkbox element.
* @param {String} valueType The preference type for this checkbox.
*/
initializeValueType: function(valueType) {
this.valueType = valueType || 'boolean';
},
/**
* See |updateDisabledState_| above.
*/
setDisabled: function(reason, disabled) {
updateDisabledState_(this, reason, disabled);
},
/**
* This method is called first while processing an onchange event. If it
* returns false, regular onchange processing continues (setting the
* associated pref, etc). If it returns true, the rest of the onchange is
* not performed. I.e., this works like stopPropagation or cancelBubble.
* @param {Event} event Change event.
*/
customChangeHandler: function(event) {
return false;
},
};
/**
* The preference name.
* @type {string}
*/
cr.defineProperty(PrefCheckbox, 'pref', cr.PropertyKind.ATTR);
/**
* Whether the preference is controlled by something else than the user's
* settings (either 'policy' or 'extension').
* @type {string}
*/
cr.defineProperty(PrefCheckbox, 'controlledBy', cr.PropertyKind.ATTR);
/**
* The user metric string.
* @type {string}
*/
cr.defineProperty(PrefCheckbox, 'metric', cr.PropertyKind.ATTR);
/**
* Whether to use inverted pref value.
* @type {boolean}
*/
cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);
/////////////////////////////////////////////////////////////////////////////
// PrefRadio class:
//Define a constructor that uses an input element as its underlying element.
var PrefRadio = cr.ui.define('input');
PrefRadio.prototype = {
// Set up the prototype chain
__proto__: HTMLInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
this.type = 'radio';
var self = this;
// Listen to pref changes.
Preferences.getInstance().addEventListener(this.pref,
function(event) {
var value = event.value && event.value['value'] != undefined ?
event.value['value'] : event.value;
self.checked = String(value) == self.value;
updateElementState_(self, event);
});
// Listen to user events.
this.addEventListener('change',
function(e) {
if(self.value == 'true' || self.value == 'false') {
Preferences.setBooleanPref(self.pref,
self.value == 'true', self.metric);
} else {
Preferences.setIntegerPref(self.pref,
parseInt(self.value, 10), self.metric);
}
});
},
/**
* See |updateDisabledState_| above.
*/
setDisabled: function(reason, disabled) {
updateDisabledState_(this, reason, disabled);
},
};
/**
* The preference name.
* @type {string}
*/
cr.defineProperty(PrefRadio, 'pref', cr.PropertyKind.ATTR);
/**
* Whether the preference is controlled by something else than the user's
* settings (either 'policy' or 'extension').
* @type {string}
*/
cr.defineProperty(PrefRadio, 'controlledBy', cr.PropertyKind.ATTR);
/**
* The user metric string.
* @type {string}
*/
cr.defineProperty(PrefRadio, 'metric', cr.PropertyKind.ATTR);
/////////////////////////////////////////////////////////////////////////////
// PrefNumeric class:
// Define a constructor that uses an input element as its underlying element.
var PrefNumeric = function() {};
PrefNumeric.prototype = {
// Set up the prototype chain
__proto__: HTMLInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
var self = this;
// Listen to pref changes.
Preferences.getInstance().addEventListener(this.pref,
function(event) {
self.value = event.value && event.value['value'] != undefined ?
event.value['value'] : event.value;
updateElementState_(self, event);
});
// Listen to user events.
this.addEventListener('change',
function(e) {
if (this.validity.valid) {
Preferences.setIntegerPref(self.pref, self.value, self.metric);
}
});
},
/**
* See |updateDisabledState_| above.
*/
setDisabled: function(reason, disabled) {
updateDisabledState_(this, reason, disabled);
},
};
/**
* The preference name.
* @type {string}
*/
cr.defineProperty(PrefNumeric, 'pref', cr.PropertyKind.ATTR);
/**
* Whether the preference is controlled by something else than the user's
* settings (either 'policy' or 'extension').
* @type {string}
*/
cr.defineProperty(PrefNumeric, 'controlledBy', cr.PropertyKind.ATTR);
/**
* The user metric string.
* @type {string}
*/
cr.defineProperty(PrefNumeric, 'metric', cr.PropertyKind.ATTR);
/////////////////////////////////////////////////////////////////////////////
// PrefNumber class:
// Define a constructor that uses an input element as its underlying element.
var PrefNumber = cr.ui.define('input');
PrefNumber.prototype = {
// Set up the prototype chain
__proto__: PrefNumeric.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
this.type = 'number';
PrefNumeric.prototype.decorate.call(this);
// Listen to user events.
this.addEventListener('input',
function(e) {
if (this.validity.valid) {
Preferences.setIntegerPref(self.pref, self.value, self.metric);
}
});
},
/**
* See |updateDisabledState_| above.
*/
setDisabled: function(reason, disabled) {
updateDisabledState_(this, reason, disabled);
},
};
/////////////////////////////////////////////////////////////////////////////
// PrefRange class:
// Define a constructor that uses an input element as its underlying element.
var PrefRange = cr.ui.define('input');
PrefRange.prototype = {
// Set up the prototype chain
__proto__: HTMLInputElement.prototype,
/**
* The map from input range value to the corresponding preference value.
*/
valueMap: undefined,
/**
* If true, the associated pref will be modified on each onchange event;
* otherwise, the pref will only be modified on the onmouseup event after
* the drag.
*/
continuous: true,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
this.type = 'range';
// Update the UI when the pref changes.
Preferences.getInstance().addEventListener(
this.pref, this.onPrefChange_.bind(this));
// Listen to user events.
// TODO(jhawkins): Add onmousewheel handling once the associated WK bug is
// fixed.
// https://bugs.webkit.org/show_bug.cgi?id=52256
this.onchange = this.onChange_.bind(this);
this.onkeyup = this.onmouseup = this.onInputUp_.bind(this);
},
/**
* Event listener that updates the UI when the underlying pref changes.
* @param {Event} event The event that details the pref change.
* @private
*/
onPrefChange_: function(event) {
var value = event.value && event.value['value'] != undefined ?
event.value['value'] : event.value;
if (value != undefined)
this.value = this.valueMap ? this.valueMap.indexOf(value) : value;
},
/**
* onchange handler that sets the pref when the user changes the value of
* the input element.
* @private
*/
onChange_: function(event) {
if (this.continuous)
this.setRangePref_();
if (this.notifyChange)
this.notifyChange(this, this.mapValueToRange_(this.value));
},
/**
* Sets the integer value of |pref| to the value of this element.
* @private
*/
setRangePref_: function() {
Preferences.setIntegerPref(
this.pref, this.mapValueToRange_(this.value), this.metric);
if (this.notifyPrefChange)
this.notifyPrefChange(this, this.mapValueToRange_(this.value));
},
/**
* onkeyup/onmouseup handler that modifies the pref if |continuous| is
* false.
* @private
*/
onInputUp_: function(event) {
if (!this.continuous)
this.setRangePref_();
},
/**
* Maps the value of this element into the range provided by the client,
* represented by |valueMap|.
* @param {number} value The value to map.
* @private
*/
mapValueToRange_: function(value) {
return this.valueMap ? this.valueMap[value] : value;
},
/**
* Called when the client has specified non-continuous mode and the value of
* the range control changes.
* @param {Element} el This element.
* @param {number} value The value of this element.
*/
notifyChange: function(el, value) {
},
/**
* See |updateDisabledState_| above.
*/
setDisabled: function(reason, disabled) {
updateDisabledState_(this, reason, disabled);
},
};
/**
* The preference name.
* @type {string}
*/
cr.defineProperty(PrefRange, 'pref', cr.PropertyKind.ATTR);
/**
* Whether the preference is controlled by something else than the user's
* settings (either 'policy' or 'extension').
* @type {string}
*/
cr.defineProperty(PrefRange, 'controlledBy', cr.PropertyKind.ATTR);
/**
* The user metric string.
* @type {string}
*/
cr.defineProperty(PrefRange, 'metric', cr.PropertyKind.ATTR);
/////////////////////////////////////////////////////////////////////////////
// PrefSelect class:
// Define a constructor that uses a select element as its underlying element.
var PrefSelect = cr.ui.define('select');
PrefSelect.prototype = {
// Set up the prototype chain
__proto__: HTMLSelectElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
var self = this;
// Listen to pref changes.
Preferences.getInstance().addEventListener(this.pref,
function(event) {
var value = event.value && event.value['value'] != undefined ?
event.value['value'] : event.value;
// Make sure |value| is a string, because the value is stored as a
// string in the HTMLOptionElement.
value = value.toString();
updateElementState_(self, event);
var found = false;
for (var i = 0; i < self.options.length; i++) {
if (self.options[i].value == value) {
self.selectedIndex = i;
found = true;
}
}
// Item not found, select first item.
if (!found)
self.selectedIndex = 0;
if (self.onchange != undefined)
self.onchange(event);
});
// Listen to user events.
this.addEventListener('change',
function(e) {
if (!self.dataType) {
console.error('undefined data type for <select> pref');
return;
}
switch(self.dataType) {
case 'number':
Preferences.setIntegerPref(self.pref,
self.options[self.selectedIndex].value, self.metric);
break;
case 'double':
Preferences.setDoublePref(self.pref,
self.options[self.selectedIndex].value, self.metric);
break;
case 'boolean':
var option = self.options[self.selectedIndex];
var value = (option.value == 'true') ? true : false;
Preferences.setBooleanPref(self.pref, value, self.metric);
break;
case 'string':
Preferences.setStringPref(self.pref,
self.options[self.selectedIndex].value, self.metric);
break;
default:
console.error('unknown data type for <select> pref: ' +
self.dataType);
}
});
},
/**
* See |updateDisabledState_| above.
*/
setDisabled: function(reason, disabled) {
updateDisabledState_(this, reason, disabled);
},
};
/**
* The preference name.
* @type {string}
*/
cr.defineProperty(PrefSelect, 'pref', cr.PropertyKind.ATTR);
/**
* Whether the preference is controlled by something else than the user's
* settings (either 'policy' or 'extension').
* @type {string}
*/
cr.defineProperty(PrefSelect, 'controlledBy', cr.PropertyKind.ATTR);
/**
* The user metric string.
* @type {string}
*/
cr.defineProperty(PrefSelect, 'metric', cr.PropertyKind.ATTR);
/**
* The data type for the preference options.
* @type {string}
*/
cr.defineProperty(PrefSelect, 'dataType', cr.PropertyKind.ATTR);
/////////////////////////////////////////////////////////////////////////////
// PrefTextField class:
// Define a constructor that uses an input element as its underlying element.
var PrefTextField = cr.ui.define('input');
PrefTextField.prototype = {
// Set up the prototype chain
__proto__: HTMLInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
var self = this;
// Listen to pref changes.
Preferences.getInstance().addEventListener(this.pref,
function(event) {
self.value = event.value && event.value['value'] != undefined ?
event.value['value'] : event.value;
updateElementState_(self, event);
});
// Listen to user events.
this.addEventListener('change',
function(e) {
switch(self.dataType) {
case 'number':
Preferences.setIntegerPref(self.pref, self.value, self.metric);
break;
case 'double':
Preferences.setDoublePref(self.pref, self.value, self.metric);
break;
case 'url':
Preferences.setURLPref(self.pref, self.value, self.metric);
break;
default:
Preferences.setStringPref(self.pref, self.value, self.metric);
break;
}
});
window.addEventListener('unload',
function() {
if (document.activeElement == self)
self.blur();
});
},
/**
* See |updateDisabledState_| above.
*/
setDisabled: function(reason, disabled) {
updateDisabledState_(this, reason, disabled);
},
};
/**
* The preference name.
* @type {string}
*/
cr.defineProperty(PrefTextField, 'pref', cr.PropertyKind.ATTR);
/**
* Whether the preference is controlled by something else than the user's
* settings (either 'policy' or 'extension').
* @type {string}
*/
cr.defineProperty(PrefTextField, 'controlledBy', cr.PropertyKind.ATTR);
/**
* The user metric string.
* @type {string}
*/
cr.defineProperty(PrefTextField, 'metric', cr.PropertyKind.ATTR);
/**
* The data type for the preference options.
* @type {string}
*/
cr.defineProperty(PrefTextField, 'dataType', cr.PropertyKind.ATTR);
/////////////////////////////////////////////////////////////////////////////
// PrefButton class:
// Define a constructor that uses a button element as its underlying element.
var PrefButton = cr.ui.define('button');
PrefButton.prototype = {
// Set up the prototype chain
__proto__: HTMLButtonElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
var self = this;
// Listen to pref changes. This element behaves like a normal button and
// doesn't affect the underlying preference; it just becomes disabled
// when the preference is managed, and its value is false.
// This is useful for buttons that should be disabled when the underlying
// boolean preference is set to false by a policy or extension.
Preferences.getInstance().addEventListener(this.pref,
function(event) {
var e = {
value: {
'disabled': event.value['disabled'] && !event.value['value'],
'controlledBy': event.value['controlledBy']
}
};
updateElementState_(self, e);
});
},
/**
* See |updateDisabledState_| above.
*/
setDisabled: function(reason, disabled) {
updateDisabledState_(this, reason, disabled);
},
};
/**
* The preference name.
* @type {string}
*/
cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR);
/**
* Whether the preference is controlled by something else than the user's
* settings (either 'policy' or 'extension').
* @type {string}
*/
cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR);
// Export
return {
PrefCheckbox: PrefCheckbox,
PrefNumber: PrefNumber,
PrefNumeric: PrefNumeric,
PrefRadio: PrefRadio,
PrefRange: PrefRange,
PrefSelect: PrefSelect,
PrefTextField: PrefTextField,
PrefButton: PrefButton
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const List = cr.ui.List;
const ListItem = cr.ui.ListItem;
/**
* Creates a deletable list item, which has a button that will trigger a call
* to deleteItemAtIndex(index) in the list.
*/
var DeletableItem = cr.ui.define('li');
DeletableItem.prototype = {
__proto__: ListItem.prototype,
/**
* The element subclasses should populate with content.
* @type {HTMLElement}
* @private
*/
contentElement_: null,
/**
* The close button element.
* @type {HTMLElement}
* @private
*/
closeButtonElement_: null,
/**
* Whether or not this item can be deleted.
* @type {boolean}
* @private
*/
deletable_: true,
/** @inheritDoc */
decorate: function() {
ListItem.prototype.decorate.call(this);
this.classList.add('deletable-item');
this.contentElement_ = this.ownerDocument.createElement('div');
this.appendChild(this.contentElement_);
this.closeButtonElement_ = this.ownerDocument.createElement('button');
this.closeButtonElement_.className =
'raw-button close-button custom-appearance';
this.closeButtonElement_.addEventListener('mousedown',
this.handleMouseDownUpOnClose_);
this.closeButtonElement_.addEventListener('mouseup',
this.handleMouseDownUpOnClose_);
this.closeButtonElement_.addEventListener('focus',
this.handleFocus_.bind(this));
this.appendChild(this.closeButtonElement_);
},
/**
* Returns the element subclasses should add content to.
* @return {HTMLElement} The element subclasses should popuplate.
*/
get contentElement() {
return this.contentElement_;
},
/* Gets/sets the deletable property. An item that is not deletable doesn't
* show the delete button (although space is still reserved for it).
*/
get deletable() {
return this.deletable_;
},
set deletable(value) {
this.deletable_ = value;
this.closeButtonElement_.disabled = !value;
},
/**
* Called when a focusable child element receives focus. Selects this item
* in the list selection model.
* @private
*/
handleFocus_: function() {
var list = this.parentNode;
var index = list.getIndexOfListItem(this);
list.selectionModel.selectedIndex = index;
list.selectionModel.anchorIndex = index;
},
/**
* Don't let the list have a crack at the event. We don't want clicking the
* close button to change the selection of the list.
* @param {Event} e The mouse down/up event object.
* @private
*/
handleMouseDownUpOnClose_: function(e) {
if (!e.target.disabled)
e.stopPropagation();
},
};
var DeletableItemList = cr.ui.define('list');
DeletableItemList.prototype = {
__proto__: List.prototype,
/** @inheritDoc */
decorate: function() {
List.prototype.decorate.call(this);
this.addEventListener('click', this.handleClick_);
this.addEventListener('keydown', this.handleKeyDown_);
},
/**
* Callback for onclick events.
* @param {Event} e The click event object.
* @private
*/
handleClick_: function(e) {
if (this.disabled)
return;
var target = e.target;
if (target.classList.contains('close-button')) {
var listItem = this.getListItemAncestor(target);
var selected = this.selectionModel.selectedIndexes;
// Check if the list item that contains the close button being clicked
// is not in the list of selected items. Only delete this item in that
// case.
var idx = this.getIndexOfListItem(listItem);
if (selected.indexOf(idx) == -1) {
this.deleteItemAtIndex(idx);
} else {
this.deleteSelectedItems_();
}
}
},
/**
* Callback for keydown events.
* @param {Event} e The keydown event object.
* @private
*/
handleKeyDown_: function(e) {
// Map delete (and backspace on Mac) to item deletion (unless focus is
// in an input field, where it's intended for text editing).
if ((e.keyCode == 46 || (e.keyCode == 8 && cr.isMac)) &&
e.target.tagName != 'INPUT') {
this.deleteSelectedItems_();
// Prevent the browser from going back.
e.preventDefault();
}
},
/**
* Deletes all the currently selected items that are deletable.
* @private
*/
deleteSelectedItems_: function() {
var selected = this.selectionModel.selectedIndexes;
// Reverse through the list of selected indexes to maintain the
// correct index values after deletion.
for (var j = selected.length - 1; j >= 0; j--) {
var index = selected[j];
if (this.getListItemByIndex(index).deletable)
this.deleteItemAtIndex(index);
}
},
/**
* Called when an item should be deleted; subclasses are responsible for
* implementing.
* @param {number} index The index of the item that is being deleted.
*/
deleteItemAtIndex: function(index) {
},
};
return {
DeletableItemList: DeletableItemList,
DeletableItem: DeletableItem,
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const DeletableItem = options.DeletableItem;
const DeletableItemList = options.DeletableItemList;
/**
* Creates a new list item with support for inline editing.
* @constructor
* @extends {options.DeletableListItem}
*/
function InlineEditableItem() {
var el = cr.doc.createElement('div');
InlineEditableItem.decorate(el);
return el;
}
/**
* Decorates an element as a inline-editable list item. Note that this is
* a subclass of DeletableItem.
* @param {!HTMLElement} el The element to decorate.
*/
InlineEditableItem.decorate = function(el) {
el.__proto__ = InlineEditableItem.prototype;
el.decorate();
};
InlineEditableItem.prototype = {
__proto__: DeletableItem.prototype,
/**
* Whether or not this item can be edited.
* @type {boolean}
* @private
*/
editable_: true,
/**
* Whether or not this is a placeholder for adding a new item.
* @type {boolean}
* @private
*/
isPlaceholder_: false,
/**
* Fields associated with edit mode.
* @type {array}
* @private
*/
editFields_: null,
/**
* Whether or not the current edit should be considered cancelled, rather
* than committed, when editing ends.
* @type {boolean}
* @private
*/
editCancelled_: true,
/**
* The editable item corresponding to the last click, if any. Used to decide
* initial focus when entering edit mode.
* @type {HTMLElement}
* @private
*/
editClickTarget_: null,
/** @inheritDoc */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
this.editFields_ = [];
this.addEventListener('mousedown', this.handleMouseDown_);
this.addEventListener('keydown', this.handleKeyDown_);
this.addEventListener('leadChange', this.handleLeadChange_);
},
/** @inheritDoc */
selectionChanged: function() {
this.updateEditState();
},
/**
* Called when this element gains or loses 'lead' status. Updates editing
* mode accordingly.
* @private
*/
handleLeadChange_: function() {
this.updateEditState();
},
/**
* Updates the edit state based on the current selected and lead states.
*/
updateEditState: function() {
if (this.editable)
this.editing = this.selected && this.lead;
},
/**
* Whether the user is currently editing the list item.
* @type {boolean}
*/
get editing() {
return this.hasAttribute('editing');
},
set editing(editing) {
if (this.editing == editing)
return;
if (editing)
this.setAttribute('editing', '');
else
this.removeAttribute('editing');
if (editing) {
this.editCancelled_ = false;
cr.dispatchSimpleEvent(this, 'edit', true);
var focusElement = this.editClickTarget_ || this.initialFocusElement;
this.editClickTarget_ = null;
// When this is called in response to the selectedChange event,
// the list grabs focus immediately afterwards. Thus we must delay
// our focus grab.
var self = this;
if (focusElement) {
window.setTimeout(function() {
// Make sure we are still in edit mode by the time we execute.
if (self.editing) {
focusElement.focus();
focusElement.select();
}
}, 50);
}
} else {
if (!this.editCancelled_ && this.hasBeenEdited &&
this.currentInputIsValid) {
if (this.isPlaceholder)
this.parentNode.focusPlaceholder = true;
this.updateStaticValues_();
cr.dispatchSimpleEvent(this, 'commitedit', true);
} else {
this.resetEditableValues_();
cr.dispatchSimpleEvent(this, 'canceledit', true);
}
}
},
/**
* Whether the item is editable.
* @type {boolean}
*/
get editable() {
return this.editable_;
},
set editable(editable) {
this.editable_ = editable;
if (!editable)
this.editing = false;
},
/**
* Whether the item is a new item placeholder.
* @type {boolean}
*/
get isPlaceholder() {
return this.isPlaceholder_;
},
set isPlaceholder(isPlaceholder) {
this.isPlaceholder_ = isPlaceholder;
if (isPlaceholder)
this.deletable = false;
},
/**
* The HTML element that should have focus initially when editing starts,
* if a specific element wasn't clicked.
* Defaults to the first <input> element; can be overriden by subclasses if
* a different element should be focused.
* @type {HTMLElement}
*/
get initialFocusElement() {
return this.contentElement.querySelector('input');
},
/**
* Whether the input in currently valid to submit. If this returns false
* when editing would be submitted, either editing will not be ended,
* or it will be cancelled, depending on the context.
* Can be overrided by subclasses to perform input validation.
* @type {boolean}
*/
get currentInputIsValid() {
return true;
},
/**
* Returns true if the item has been changed by an edit.
* Can be overrided by subclasses to return false when nothing has changed
* to avoid unnecessary commits.
* @type {boolean}
*/
get hasBeenEdited() {
return true;
},
/**
* Returns a div containing an <input>, as well as static text if
* isPlaceholder is not true.
* @param {string} text The text of the cell.
* @return {HTMLElement} The HTML element for the cell.
* @private
*/
createEditableTextCell: function(text) {
var container = this.ownerDocument.createElement('div');
if (!this.isPlaceholder) {
var textEl = this.ownerDocument.createElement('div');
textEl.className = 'static-text';
textEl.textContent = text;
textEl.setAttribute('displaymode', 'static');
container.appendChild(textEl);
}
var inputEl = this.ownerDocument.createElement('input');
inputEl.type = 'text';
inputEl.value = text;
if (!this.isPlaceholder) {
inputEl.setAttribute('displaymode', 'edit');
inputEl.staticVersion = textEl;
} else {
// At this point |this| is not attached to the parent list yet, so give
// a short timeout in order for the attachment to occur.
var self = this;
window.setTimeout(function() {
var list = self.parentNode;
if (list && list.focusPlaceholder) {
list.focusPlaceholder = false;
if (list.shouldFocusPlaceholder())
inputEl.focus();
}
}, 50);
}
inputEl.addEventListener('focus', this.handleFocus_.bind(this));
container.appendChild(inputEl);
this.editFields_.push(inputEl);
return container;
},
/**
* Resets the editable version of any controls created by createEditable*
* to match the static text.
* @private
*/
resetEditableValues_: function() {
var editFields = this.editFields_;
for (var i = 0; i < editFields.length; i++) {
var staticLabel = editFields[i].staticVersion;
if (!staticLabel && !this.isPlaceholder)
continue;
if (editFields[i].tagName == 'INPUT') {
editFields[i].value =
this.isPlaceholder ? '' : staticLabel.textContent;
}
// Add more tag types here as new createEditable* methods are added.
editFields[i].setCustomValidity('');
}
},
/**
* Sets the static version of any controls created by createEditable*
* to match the current value of the editable version. Called on commit so
* that there's no flicker of the old value before the model updates.
* @private
*/
updateStaticValues_: function() {
var editFields = this.editFields_;
for (var i = 0; i < editFields.length; i++) {
var staticLabel = editFields[i].staticVersion;
if (!staticLabel)
continue;
if (editFields[i].tagName == 'INPUT')
staticLabel.textContent = editFields[i].value;
// Add more tag types here as new createEditable* methods are added.
}
},
/**
* Called a key is pressed. Handles committing and cancelling edits.
* @param {Event} e The key down event.
* @private
*/
handleKeyDown_: function(e) {
if (!this.editing)
return;
var endEdit = false;
switch (e.keyIdentifier) {
case 'U+001B': // Esc
this.editCancelled_ = true;
endEdit = true;
break;
case 'Enter':
if (this.currentInputIsValid)
endEdit = true;
break;
}
if (endEdit) {
// Blurring will trigger the edit to end; see InlineEditableItemList.
this.ownerDocument.activeElement.blur();
// Make sure that handled keys aren't passed on and double-handled.
// (e.g., esc shouldn't both cancel an edit and close a subpage)
e.stopPropagation();
}
},
/**
* Called when the list item is clicked. If the click target corresponds to
* an editable item, stores that item to focus when edit mode is started.
* @param {Event} e The mouse down event.
* @private
*/
handleMouseDown_: function(e) {
if (!this.editable || this.editing)
return;
var clickTarget = e.target;
var editFields = this.editFields_;
for (var i = 0; i < editFields.length; i++) {
if (editFields[i] == clickTarget ||
editFields[i].staticVersion == clickTarget) {
this.editClickTarget_ = editFields[i];
return;
}
}
},
};
/**
* Takes care of committing changes to inline editable list items when the
* window loses focus.
*/
function handleWindowBlurs() {
window.addEventListener('blur', function(e) {
var itemAncestor = findAncestor(document.activeElement, function(node) {
return node instanceof InlineEditableItem;
});
if (itemAncestor);
document.activeElement.blur();
});
}
handleWindowBlurs();
var InlineEditableItemList = cr.ui.define('list');
InlineEditableItemList.prototype = {
__proto__: DeletableItemList.prototype,
/**
* Focuses the input element of the placeholder if true.
* @type {boolean}
*/
focusPlaceholder: false,
/** @inheritDoc */
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
this.setAttribute('inlineeditable', '');
this.addEventListener('hasElementFocusChange',
this.handleListFocusChange_);
},
/**
* Called when the list hierarchy as a whole loses or gains focus; starts
* or ends editing for the lead item if necessary.
* @param {Event} e The change event.
* @private
*/
handleListFocusChange_: function(e) {
var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex);
if (leadItem) {
if (e.newValue)
leadItem.updateEditState();
else
leadItem.editing = false;
}
},
/**
* May be overridden by subclasses to disable focusing the placeholder.
* @return true if the placeholder element should be focused on edit commit.
*/
shouldFocusPlaceholder: function() {
return true;
},
};
// Export
return {
InlineEditableItem: InlineEditableItem,
InlineEditableItemList: InlineEditableItemList,
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var Preferences = options.Preferences;
/**
* A controlled setting indicator that can be placed on a setting as an
* indicator that the value is controlled by some external entity such as
* policy or an extension.
* @constructor
* @extends {HTMLSpanElement}
*/
var ControlledSettingIndicator = cr.ui.define('span');
ControlledSettingIndicator.prototype = {
__proto__: HTMLSpanElement.prototype,
/**
* Decorates the base element to show the proper icon.
*/
decorate: function() {
var self = this;
var doc = self.ownerDocument;
// Create the details and summary elements.
var detailsContainer = doc.createElement('details');
detailsContainer.appendChild(doc.createElement('summary'));
// This should really create a div element, but that breaks :hover. See
// https://bugs.webkit.org/show_bug.cgi?id=72957
var bubbleContainer = doc.createElement('p');
bubbleContainer.className = 'controlled-setting-bubble';
detailsContainer.appendChild(bubbleContainer);
self.appendChild(detailsContainer);
// If there is a pref, track its controlledBy property in order to be able
// to bring up the correct bubble.
if (this.hasAttribute('pref')) {
Preferences.getInstance().addEventListener(
this.getAttribute('pref'),
function(event) {
if (event.value) {
var controlledBy = event.value['controlledBy'];
self.controlledBy = controlledBy ? controlledBy : null;
}
});
}
self.addEventListener('click', self.show_);
},
/**
* Closes the bubble.
*/
close: function() {
this.querySelector('details').removeAttribute('open');
this.ownerDocument.removeEventListener('click', this.closeHandler_, true);
},
/**
* Constructs the bubble DOM tree and shows it.
* @private
*/
show_: function() {
var self = this;
var doc = self.ownerDocument;
// Clear out the old bubble contents.
var bubbleContainer = this.querySelector('.controlled-setting-bubble');
if (bubbleContainer) {
while (bubbleContainer.hasChildNodes())
bubbleContainer.removeChild(bubbleContainer.lastChild);
}
// Work out the bubble text.
defaultStrings = {
'policy' : localStrings.getString('controlledSettingPolicy'),
'extension' : localStrings.getString('controlledSettingExtension'),
'recommended' : localStrings.getString('controlledSettingRecommended'),
};
// No controller, no bubble.
if (!self.controlledBy || !self.controlledBy in defaultStrings)
return;
var text = defaultStrings[self.controlledBy];
// Apply text overrides.
if (self.hasAttribute('text' + self.controlledBy))
text = self.getAttribute('text' + self.controlledBy);
// Create the DOM tree.
var bubbleText = doc.createElement('p');
bubbleText.className = 'controlled-setting-bubble-text';
bubbleText.textContent = text;
var pref = self.getAttribute('pref');
if (self.controlledBy == 'recommended' && pref) {
var container = doc.createElement('div');
var action = doc.createElement('button');
action.classList.add('link-button');
action.classList.add('controlled-setting-bubble-action');
action.textContent =
localStrings.getString('controlledSettingApplyRecommendation');
action.addEventListener(
'click',
function(e) {
Preferences.clearPref(pref);
});
container.appendChild(action);
bubbleText.appendChild(container);
}
bubbleContainer.appendChild(bubbleText);
// One-time bubble-closing event handler.
self.closeHandler_ = this.close.bind(this);
doc.addEventListener('click', self.closeHandler_, true);
}
};
/**
* The controlling entity of the setting. Can take the values "policy",
* "extension", "recommended" or be unset.
*/
cr.defineProperty(ControlledSettingIndicator, 'controlledBy',
cr.PropertyKind.ATTR,
ControlledSettingIndicator.prototype.close);
// Export.
return {
ControlledSettingIndicator : ControlledSettingIndicator
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
/////////////////////////////////////////////////////////////////////////////
// OptionsPage class:
/**
* Base class for options page.
* @constructor
* @param {string} name Options page name, also defines id of the div element
* containing the options view and the name of options page navigation bar
* item as name+'PageNav'.
* @param {string} title Options page title, used for navigation bar
* @extends {EventTarget}
*/
function OptionsPage(name, title, pageDivName) {
this.name = name;
this.title = title;
this.pageDivName = pageDivName;
this.pageDiv = $(this.pageDivName);
this.tab = null;
}
const SUBPAGE_SHEET_COUNT = 2;
/**
* Main level option pages. Maps lower-case page names to the respective page
* object.
* @protected
*/
OptionsPage.registeredPages = {};
/**
* Pages which are meant to behave like modal dialogs. Maps lower-case overlay
* names to the respective overlay object.
* @protected
*/
OptionsPage.registeredOverlayPages = {};
/**
* Whether or not |initialize| has been called.
* @private
*/
OptionsPage.initialized_ = false;
/**
* Gets the default page (to be shown on initial load).
*/
OptionsPage.getDefaultPage = function() {
return BrowserOptions.getInstance();
};
/**
* Shows the default page.
*/
OptionsPage.showDefaultPage = function() {
this.navigateToPage(this.getDefaultPage().name);
};
/**
* "Navigates" to a page, meaning that the page will be shown and the
* appropriate entry is placed in the history.
* @param {string} pageName Page name.
*/
OptionsPage.navigateToPage = function(pageName) {
this.showPageByName(pageName, true);
};
/**
* Shows a registered page. This handles both top-level pages and sub-pages.
* @param {string} pageName Page name.
* @param {boolean} updateHistory True if we should update the history after
* showing the page.
* @private
*/
OptionsPage.showPageByName = function(pageName, updateHistory) {
// Find the currently visible root-level page.
var rootPage = null;
for (var name in this.registeredPages) {
var page = this.registeredPages[name];
if (page.visible && !page.parentPage) {
rootPage = page;
break;
}
}
// Find the target page.
var targetPage = this.registeredPages[pageName.toLowerCase()];
if (!targetPage || !targetPage.canShowPage()) {
// If it's not a page, try it as an overlay.
if (!targetPage && this.showOverlay_(pageName, rootPage)) {
if (updateHistory)
this.updateHistoryState_();
return;
} else {
targetPage = this.getDefaultPage();
}
}
pageName = targetPage.name.toLowerCase();
var targetPageWasVisible = targetPage.visible;
// Determine if the root page is 'sticky', meaning that it
// shouldn't change when showing a sub-page. This can happen for special
// pages like Search.
var isRootPageLocked =
rootPage && rootPage.sticky && targetPage.parentPage;
// Notify pages if they will be hidden.
for (var name in this.registeredPages) {
var page = this.registeredPages[name];
if (!page.parentPage && isRootPageLocked)
continue;
if (page.willHidePage && name != pageName &&
!page.isAncestorOfPage(targetPage))
page.willHidePage();
}
// Update visibilities to show only the hierarchy of the target page.
for (var name in this.registeredPages) {
var page = this.registeredPages[name];
if (!page.parentPage && isRootPageLocked)
continue;
page.visible = name == pageName ||
(!document.documentElement.classList.contains('hide-menu') &&
page.isAncestorOfPage(targetPage));
}
// Update the history and current location.
if (updateHistory)
this.updateHistoryState_();
// Always update the page title.
document.title = targetPage.title;
// Notify pages if they were shown.
for (var name in this.registeredPages) {
var page = this.registeredPages[name];
if (!page.parentPage && isRootPageLocked)
continue;
if (!targetPageWasVisible && page.didShowPage && (name == pageName ||
page.isAncestorOfPage(targetPage)))
page.didShowPage();
}
};
/**
* Updates the visibility and stacking order of the subpage backdrop
* according to which subpage is topmost and visible.
* @private
*/
OptionsPage.updateSubpageBackdrop_ = function () {
var topmostPage = this.getTopmostVisibleNonOverlayPage_();
var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0;
var subpageBackdrop = $('subpage-backdrop');
if (nestingLevel > 0) {
var container = $('subpage-sheet-container-' + nestingLevel);
subpageBackdrop.style.zIndex =
parseInt(window.getComputedStyle(container).zIndex) - 1;
subpageBackdrop.hidden = false;
} else {
subpageBackdrop.hidden = true;
}
};
/**
* Scrolls the page to the correct position (the top when opening a subpage,
* or the old scroll position a previously hidden subpage becomes visible).
* @private
*/
OptionsPage.updateScrollPosition_ = function () {
var topmostPage = this.getTopmostVisibleNonOverlayPage_();
var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0;
var container = (nestingLevel > 0) ?
$('subpage-sheet-container-' + nestingLevel) : $('page-container');
var scrollTop = container.oldScrollTop || 0;
container.oldScrollTop = undefined;
window.scroll(document.body.scrollLeft, scrollTop);
};
/**
* Pushes the current page onto the history stack, overriding the last page
* if it is the generic chrome://settings/.
* @private
*/
OptionsPage.updateHistoryState_ = function() {
var page = this.getTopmostVisiblePage();
var path = location.pathname;
if (path)
path = path.slice(1).replace(/\/$/, ''); // Remove trailing slash.
// The page is already in history (the user may have clicked the same link
// twice). Do nothing.
if (path == page.name)
return;
// If there is no path, the current location is chrome://settings/.
// Override this with the new page.
var historyFunction = path ? window.history.pushState :
window.history.replaceState;
historyFunction.call(window.history,
{pageName: page.name},
page.title,
'/' + page.name);
// Update tab title.
document.title = page.title;
};
/**
* Shows a registered Overlay page. Does not update history.
* @param {string} overlayName Page name.
* @param {OptionPage} rootPage The currently visible root-level page.
* @return {boolean} whether we showed an overlay.
*/
OptionsPage.showOverlay_ = function(overlayName, rootPage) {
var overlay = this.registeredOverlayPages[overlayName.toLowerCase()];
if (!overlay || !overlay.canShowPage())
return false;
if ((!rootPage || !rootPage.sticky) && overlay.parentPage)
this.showPageByName(overlay.parentPage.name, false);
if (!overlay.visible) {
overlay.visible = true;
if (overlay.didShowPage) overlay.didShowPage();
}
return true;
};
/**
* Returns whether or not an overlay is visible.
* @return {boolean} True if an overlay is visible.
* @private
*/
OptionsPage.isOverlayVisible_ = function() {
return this.getVisibleOverlay_() != null;
};
/**
* Returns the currently visible overlay, or null if no page is visible.
* @return {OptionPage} The visible overlay.
*/
OptionsPage.getVisibleOverlay_ = function() {
for (var name in this.registeredOverlayPages) {
var page = this.registeredOverlayPages[name];
if (page.visible)
return page;
}
return null;
};
/**
* Closes the visible overlay. Updates the history state after closing the
* overlay.
*/
OptionsPage.closeOverlay = function() {
var overlay = this.getVisibleOverlay_();
if (!overlay)
return;
overlay.visible = false;
if (overlay.didClosePage) overlay.didClosePage();
this.updateHistoryState_();
};
/**
* Hides the visible overlay. Does not affect the history state.
* @private
*/
OptionsPage.hideOverlay_ = function() {
var overlay = this.getVisibleOverlay_();
if (overlay)
overlay.visible = false;
};
/**
* Returns the topmost visible page (overlays excluded).
* @return {OptionPage} The topmost visible page aside any overlay.
* @private
*/
OptionsPage.getTopmostVisibleNonOverlayPage_ = function() {
var topPage = null;
for (var name in this.registeredPages) {
var page = this.registeredPages[name];
if (page.visible &&
(!topPage || page.nestingLevel > topPage.nestingLevel))
topPage = page;
}
return topPage;
};
/**
* Returns the topmost visible page, or null if no page is visible.
* @return {OptionPage} The topmost visible page.
*/
OptionsPage.getTopmostVisiblePage = function() {
// Check overlays first since they're top-most if visible.
return this.getVisibleOverlay_() || this.getTopmostVisibleNonOverlayPage_();
};
/**
* Closes the topmost open subpage, if any.
* @private
*/
OptionsPage.closeTopSubPage_ = function() {
var topPage = this.getTopmostVisiblePage();
if (topPage && !topPage.isOverlay && topPage.parentPage) {
if (topPage.willHidePage)
topPage.willHidePage();
topPage.visible = false;
}
this.updateHistoryState_();
};
/**
* Closes all subpages below the given level.
* @param {number} level The nesting level to close below.
*/
OptionsPage.closeSubPagesToLevel = function(level) {
var topPage = this.getTopmostVisiblePage();
while (topPage && topPage.nestingLevel > level) {
if (topPage.willHidePage)
topPage.willHidePage();
topPage.visible = false;
topPage = topPage.parentPage;
}
this.updateHistoryState_();
};
/**
* Updates managed banner visibility state based on the topmost page.
*/
OptionsPage.updateManagedBannerVisibility = function() {
var topPage = this.getTopmostVisiblePage();
if (topPage)
topPage.updateManagedBannerVisibility();
};
/**
* Shows the tab contents for the given navigation tab.
* @param {!Element} tab The tab that the user clicked.
*/
OptionsPage.showTab = function(tab) {
// Search parents until we find a tab, or the nav bar itself. This allows
// tabs to have child nodes, e.g. labels in separately-styled spans.
while (tab && !tab.classList.contains('subpages-nav-tabs') &&
!tab.classList.contains('tab')) {
tab = tab.parentNode;
}
if (!tab || !tab.classList.contains('tab'))
return;
// Find tab bar of the tab.
var tabBar = tab;
while (tabBar && !tabBar.classList.contains('subpages-nav-tabs')) {
tabBar = tabBar.parentNode;
}
if (!tabBar)
return;
if (tabBar.activeNavTab != null) {
tabBar.activeNavTab.classList.remove('active-tab');
$(tabBar.activeNavTab.getAttribute('tab-contents')).classList.
remove('active-tab-contents');
}
tab.classList.add('active-tab');
$(tab.getAttribute('tab-contents')).classList.add('active-tab-contents');
tabBar.activeNavTab = tab;
};
/**
* Registers new options page.
* @param {OptionsPage} page Page to register.
*/
OptionsPage.register = function(page) {
this.registeredPages[page.name.toLowerCase()] = page;
// Create and add new page <li> element to navbar.
var pageNav = document.createElement('li');
pageNav.id = page.name + 'PageNav';
pageNav.className = 'navbar-item';
pageNav.setAttribute('pageName', page.name);
pageNav.setAttribute('role', 'tab');
pageNav.textContent = page.pageDiv.querySelector('h1').textContent;
pageNav.tabIndex = -1;
pageNav.onclick = function(event) {
OptionsPage.navigateToPage(this.getAttribute('pageName'));
};
pageNav.onkeydown = function(event) {
if ((event.keyCode == 37 || event.keyCode==38) &&
this.previousSibling && this.previousSibling.onkeydown) {
// Left and up arrow moves back one tab.
OptionsPage.navigateToPage(
this.previousSibling.getAttribute('pageName'));
this.previousSibling.focus();
} else if ((event.keyCode == 39 || event.keyCode == 40) &&
this.nextSibling) {
// Right and down arrows move forward one tab.
OptionsPage.navigateToPage(this.nextSibling.getAttribute('pageName'));
this.nextSibling.focus();
}
};
pageNav.onkeypress = function(event) {
// Enter or space
if (event.keyCode == 13 || event.keyCode == 32) {
OptionsPage.navigateToPage(this.getAttribute('pageName'));
}
};
var navbar = $('navbar');
navbar.appendChild(pageNav);
page.tab = pageNav;
page.initializePage();
};
/**
* Find an enclosing section for an element if it exists.
* @param {Element} element Element to search.
* @return {OptionPage} The section element, or null.
* @private
*/
OptionsPage.findSectionForNode_ = function(node) {
while (node = node.parentNode) {
if (node.nodeName == 'SECTION')
return node;
}
return null;
};
/**
* Registers a new Sub-page.
* @param {OptionsPage} subPage Sub-page to register.
* @param {OptionsPage} parentPage Associated parent page for this page.
* @param {Array} associatedControls Array of control elements that lead to
* this sub-page. The first item is typically a button in a root-level
* page. There may be additional buttons for nested sub-pages.
*/
OptionsPage.registerSubPage = function(subPage,
parentPage,
associatedControls) {
this.registeredPages[subPage.name.toLowerCase()] = subPage;
subPage.parentPage = parentPage;
if (associatedControls) {
subPage.associatedControls = associatedControls;
if (associatedControls.length) {
subPage.associatedSection =
this.findSectionForNode_(associatedControls[0]);
}
}
subPage.tab = undefined;
subPage.initializePage();
};
/**
* Registers a new Overlay page.
* @param {OptionsPage} overlay Overlay to register.
* @param {OptionsPage} parentPage Associated parent page for this overlay.
* @param {Array} associatedControls Array of control elements associated with
* this page.
*/
OptionsPage.registerOverlay = function(overlay,
parentPage,
associatedControls) {
this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay;
overlay.parentPage = parentPage;
if (associatedControls) {
overlay.associatedControls = associatedControls;
if (associatedControls.length) {
overlay.associatedSection =
this.findSectionForNode_(associatedControls[0]);
}
}
// Reverse the button strip for views. See the documentation of
// reverseButtonStrip_() for an explanation of why this is necessary.
if (cr.isViews)
this.reverseButtonStrip_(overlay);
overlay.tab = undefined;
overlay.isOverlay = true;
overlay.initializePage();
};
/**
* Reverses the child elements of a button strip. This is necessary because
* WebKit does not alter the tab order for elements that are visually reversed
* using -webkit-box-direction: reverse, and the button order is reversed for
* views. See https://bugs.webkit.org/show_bug.cgi?id=62664 for more
* information.
* @param {Object} overlay The overlay containing the button strip to reverse.
* @private
*/
OptionsPage.reverseButtonStrip_ = function(overlay) {
var buttonStrips = overlay.pageDiv.querySelectorAll('.button-strip');
// Reverse all button-strips in the overlay.
for (var j = 0; j < buttonStrips.length; j++) {
var buttonStrip = buttonStrips[j];
var childNodes = buttonStrip.childNodes;
for (var i = childNodes.length - 1; i >= 0; i--)
buttonStrip.appendChild(childNodes[i]);
}
};
/**
* Callback for window.onpopstate.
* @param {Object} data State data pushed into history.
*/
OptionsPage.setState = function(data) {
if (data && data.pageName) {
// It's possible an overlay may be the last top-level page shown.
if (this.isOverlayVisible_() &&
!this.registeredOverlayPages[data.pageName.toLowerCase()]) {
this.hideOverlay_();
}
this.showPageByName(data.pageName, false);
}
};
/**
* Callback for window.onbeforeunload. Used to notify overlays that they will
* be closed.
*/
OptionsPage.willClose = function() {
var overlay = this.getVisibleOverlay_();
if (overlay && overlay.didClosePage)
overlay.didClosePage();
};
/**
* Freezes/unfreezes the scroll position of given level's page container.
* @param {boolean} freeze Whether the page should be frozen.
* @param {number} level The level to freeze/unfreeze.
* @private
*/
OptionsPage.setPageFrozenAtLevel_ = function(freeze, level) {
var container = level == 0 ? $('page-container')
: $('subpage-sheet-container-' + level);
if (container.classList.contains('frozen') == freeze)
return;
if (freeze) {
// Lock the width, since auto width computation may change.
container.style.width = window.getComputedStyle(container).width;
container.oldScrollTop = document.body.scrollTop;
container.classList.add('frozen');
var verticalPosition =
container.getBoundingClientRect().top - container.oldScrollTop;
container.style.top = verticalPosition + 'px';
this.updateFrozenElementHorizontalPosition_(container);
} else {
container.classList.remove('frozen');
container.style.top = '';
container.style.left = '';
container.style.right = '';
container.style.width = '';
}
};
/**
* Freezes/unfreezes the scroll position of visible pages based on the current
* page stack.
*/
OptionsPage.updatePageFreezeStates = function() {
var topPage = OptionsPage.getTopmostVisiblePage();
if (!topPage)
return;
var nestingLevel = topPage.isOverlay ? 100 : topPage.nestingLevel;
for (var i = 0; i <= SUBPAGE_SHEET_COUNT; i++) {
this.setPageFrozenAtLevel_(i < nestingLevel, i);
}
};
/**
* Initializes the complete options page. This will cause all C++ handlers to
* be invoked to do final setup.
*/
OptionsPage.initialize = function() {
chrome.send('coreOptionsInitialize');
this.initialized_ = true;
document.addEventListener('scroll', this.handleScroll_.bind(this));
window.addEventListener('resize', this.handleResize_.bind(this));
if (!document.documentElement.classList.contains('hide-menu')) {
// Close subpages if the user clicks on the html body. Listen in the
// capturing phase so that we can stop the click from doing anything.
document.body.addEventListener('click',
this.bodyMouseEventHandler_.bind(this),
true);
// We also need to cancel mousedowns on non-subpage content.
document.body.addEventListener('mousedown',
this.bodyMouseEventHandler_.bind(this),
true);
var self = this;
// Hook up the close buttons.
subpageCloseButtons = document.querySelectorAll('.close-subpage');
for (var i = 0; i < subpageCloseButtons.length; i++) {
subpageCloseButtons[i].onclick = function() {
self.closeTopSubPage_();
};
};
// Install handler for key presses.
document.addEventListener('keydown',
this.keyDownEventHandler_.bind(this));
document.addEventListener('focus', this.manageFocusChange_.bind(this),
true);
}
// Calculate and store the horizontal locations of elements that may be
// frozen later.
var sidebarWidth =
parseInt(window.getComputedStyle($('mainview')).webkitPaddingStart, 10);
$('page-container').horizontalOffset = sidebarWidth +
parseInt(window.getComputedStyle(
$('mainview-content')).webkitPaddingStart, 10);
for (var level = 1; level <= SUBPAGE_SHEET_COUNT; level++) {
var containerId = 'subpage-sheet-container-' + level;
$(containerId).horizontalOffset = sidebarWidth;
}
$('subpage-backdrop').horizontalOffset = sidebarWidth;
// Trigger the resize handler manually to set the initial state.
this.handleResize_(null);
};
/**
* Does a bounds check for the element on the given x, y client coordinates.
* @param {Element} e The DOM element.
* @param {number} x The client X to check.
* @param {number} y The client Y to check.
* @return {boolean} True if the point falls within the element's bounds.
* @private
*/
OptionsPage.elementContainsPoint_ = function(e, x, y) {
var clientRect = e.getBoundingClientRect();
return x >= clientRect.left && x <= clientRect.right &&
y >= clientRect.top && y <= clientRect.bottom;
};
/**
* Called when focus changes; ensures that focus doesn't move outside
* the topmost subpage/overlay.
* @param {Event} e The focus change event.
* @private
*/
OptionsPage.manageFocusChange_ = function(e) {
var focusableItemsRoot;
var topPage = this.getTopmostVisiblePage();
if (!topPage)
return;
if (topPage.isOverlay) {
// If an overlay is visible, that defines the tab loop.
focusableItemsRoot = topPage.pageDiv;
} else {
// If a subpage is visible, use its parent as the tab loop constraint.
// (The parent is used because it contains the close button.)
if (topPage.nestingLevel > 0)
focusableItemsRoot = topPage.pageDiv.parentNode;
}
if (focusableItemsRoot && !focusableItemsRoot.contains(e.target))
topPage.focusFirstElement();
};
/**
* Called when the page is scrolled; moves elements that are position:fixed
* but should only behave as if they are fixed for vertical scrolling.
* @param {Event} e The scroll event.
* @private
*/
OptionsPage.handleScroll_ = function(e) {
var scrollHorizontalOffset = document.body.scrollLeft;
// position:fixed doesn't seem to work for horizontal scrolling in RTL mode,
// so only adjust in LTR mode (where scroll values will be positive).
if (scrollHorizontalOffset >= 0) {
$('navbar-container').style.left = -scrollHorizontalOffset + 'px';
var subpageBackdrop = $('subpage-backdrop');
subpageBackdrop.style.left = subpageBackdrop.horizontalOffset -
scrollHorizontalOffset + 'px';
this.updateAllFrozenElementPositions_();
}
};
/**
* Updates all frozen pages to match the horizontal scroll position.
* @private
*/
OptionsPage.updateAllFrozenElementPositions_ = function() {
var frozenElements = document.querySelectorAll('.frozen');
for (var i = 0; i < frozenElements.length; i++) {
this.updateFrozenElementHorizontalPosition_(frozenElements[i]);
}
};
/**
* Updates the given frozen element to match the horizontal scroll position.
* @param {HTMLElement} e The frozen element to update
* @private
*/
OptionsPage.updateFrozenElementHorizontalPosition_ = function(e) {
if (document.documentElement.dir == 'rtl')
e.style.right = e.horizontalOffset + 'px';
else
e.style.left = e.horizontalOffset - document.body.scrollLeft + 'px';
};
/**
* Called when the page is resized; adjusts the size of elements that depend
* on the veiwport.
* @param {Event} e The resize event.
* @private
*/
OptionsPage.handleResize_ = function(e) {
// Set an explicit height equal to the viewport on all the subpage
// containers shorter than the viewport. This is used instead of
// min-height: 100% so that there is an explicit height for the subpages'
// min-height: 100%.
var viewportHeight = document.documentElement.clientHeight;
var subpageContainers =
document.querySelectorAll('.subpage-sheet-container');
for (var i = 0; i < subpageContainers.length; i++) {
if (subpageContainers[i].scrollHeight > viewportHeight)
subpageContainers[i].style.removeProperty('height');
else
subpageContainers[i].style.height = viewportHeight + 'px';
}
};
/**
* A function to handle mouse events (mousedown or click) on the html body by
* closing subpages and/or stopping event propagation.
* @return {Event} a mousedown or click event.
* @private
*/
OptionsPage.bodyMouseEventHandler_ = function(event) {
// Do nothing if a subpage isn't showing.
var topPage = this.getTopmostVisiblePage();
if (!topPage || topPage.isOverlay || !topPage.parentPage)
return;
// Don't close subpages if a user is clicking in a select element.
// This is necessary because WebKit sends click events with strange
// coordinates when a user selects a new entry in a select element.
// See: http://crbug.com/87199
if (event.srcElement.nodeName == 'SELECT')
return;
// Do nothing if the client coordinates are not within the source element.
// This occurs if the user toggles a checkbox by pressing spacebar.
// This is a workaround to prevent keyboard events from closing the window.
// See: crosbug.com/15678
if (event.clientX == -document.body.scrollLeft &&
event.clientY == -document.body.scrollTop) {
return;
}
// Don't interfere with navbar clicks.
if ($('navbar').contains(event.target))
return;
// Figure out which page the click happened in.
for (var level = topPage.nestingLevel; level >= 0; level--) {
var clickIsWithinLevel = level == 0 ? true :
OptionsPage.elementContainsPoint_(
$('subpage-sheet-' + level), event.clientX, event.clientY);
if (!clickIsWithinLevel)
continue;
// Event was within the topmost page; do nothing.
if (topPage.nestingLevel == level)
return;
// Block propgation of both clicks and mousedowns, but only close subpages
// on click.
if (event.type == 'click')
this.closeSubPagesToLevel(level);
event.stopPropagation();
event.preventDefault();
return;
}
};
/**
* A function to handle key press events.
* @return {Event} a keydown event.
* @private
*/
OptionsPage.keyDownEventHandler_ = function(event) {
// Close the top overlay or sub-page on esc.
if (event.keyCode == 27) { // Esc
if (this.isOverlayVisible_())
this.closeOverlay();
else
this.closeTopSubPage_();
}
};
OptionsPage.setClearPluginLSODataEnabled = function(enabled) {
if (enabled) {
document.documentElement.setAttribute(
'flashPluginSupportsClearSiteData', '');
} else {
document.documentElement.removeAttribute(
'flashPluginSupportsClearSiteData');
}
};
/**
* Re-initializes the C++ handlers if necessary. This is called if the
* handlers are torn down and recreated but the DOM may not have been (in
* which case |initialize| won't be called again). If |initialize| hasn't been
* called, this does nothing (since it will be later, once the DOM has
* finished loading).
*/
OptionsPage.reinitializeCore = function() {
if (this.initialized_)
chrome.send('coreOptionsInitialize');
}
OptionsPage.prototype = {
__proto__: cr.EventTarget.prototype,
/**
* The parent page of this option page, or null for top-level pages.
* @type {OptionsPage}
*/
parentPage: null,
/**
* The section on the parent page that is associated with this page.
* Can be null.
* @type {Element}
*/
associatedSection: null,
/**
* An array of controls that are associated with this page. The first
* control should be located on a top-level page.
* @type {OptionsPage}
*/
associatedControls: null,
/**
* Initializes page content.
*/
initializePage: function() {},
/**
* Updates managed banner visibility state. This function iterates over
* all input fields of a window and if any of these is marked as managed
* it triggers the managed banner to be visible. The banner can be enforced
* being on through the managed flag of this class but it can not be forced
* being off if managed items exist.
*/
updateManagedBannerVisibility: function() {
var bannerDiv = $('managed-prefs-banner');
var controlledByPolicy = false;
var controlledByExtension = false;
var inputElements = this.pageDiv.querySelectorAll('input[controlled-by]');
for (var i = 0, len = inputElements.length; i < len; i++) {
if (inputElements[i].controlledBy == 'policy')
controlledByPolicy = true;
else if (inputElements[i].controlledBy == 'extension')
controlledByExtension = true;
}
if (!controlledByPolicy && !controlledByExtension) {
bannerDiv.hidden = true;
} else {
bannerDiv.hidden = false;
var height = window.getComputedStyle(bannerDiv).height;
if (controlledByPolicy && !controlledByExtension) {
$('managed-prefs-text').textContent =
templateData.policyManagedPrefsBannerText;
} else if (!controlledByPolicy && controlledByExtension) {
$('managed-prefs-text').textContent =
templateData.extensionManagedPrefsBannerText;
} else if (controlledByPolicy && controlledByExtension) {
$('managed-prefs-text').textContent =
templateData.policyAndExtensionManagedPrefsBannerText;
}
}
},
/**
* Gets page visibility state.
*/
get visible() {
return !this.pageDiv.hidden;
},
/**
* Sets page visibility.
*/
set visible(visible) {
if ((this.visible && visible) || (!this.visible && !visible))
return;
this.setContainerVisibility_(visible);
if (visible) {
this.pageDiv.hidden = false;
if (this.tab) {
this.tab.classList.add('navbar-item-selected');
this.tab.setAttribute('aria-selected', 'true');
this.tab.tabIndex = 0;
}
} else {
this.pageDiv.hidden = true;
if (this.tab) {
this.tab.classList.remove('navbar-item-selected');
this.tab.setAttribute('aria-selected', 'false');
this.tab.tabIndex = -1;
}
}
OptionsPage.updatePageFreezeStates();
// The managed prefs banner is global, so after any visibility change
// update it based on the topmost page, not necessarily this page
// (e.g., if an ancestor is made visible after a child).
OptionsPage.updateManagedBannerVisibility();
// A subpage was shown or hidden.
if (!this.isOverlay && this.nestingLevel > 0) {
OptionsPage.updateSubpageBackdrop_();
OptionsPage.updateScrollPosition_();
}
cr.dispatchPropertyChange(this, 'visible', visible, !visible);
},
/**
* Shows or hides this page's container.
* @param {boolean} visible Whether the container should be visible or not.
* @private
*/
setContainerVisibility_: function(visible) {
var container = null;
if (this.isOverlay) {
container = $('overlay');
} else {
var nestingLevel = this.nestingLevel;
if (nestingLevel > 0)
container = $('subpage-sheet-container-' + nestingLevel);
}
var isSubpage = !this.isOverlay;
if (!container)
return;
if (container.hidden != visible) {
if (visible) {
// If the container is set hidden and then immediately set visible
// again, the fadeCompleted_ callback would cause it to be erroneously
// hidden again. Removing the transparent tag avoids that.
container.classList.remove('transparent');
}
return;
}
if (visible) {
container.hidden = false;
if (isSubpage) {
var computedStyle = window.getComputedStyle(container);
container.style.WebkitPaddingStart =
parseInt(computedStyle.WebkitPaddingStart, 10) + 100 + 'px';
}
// Separate animating changes from the removal of display:none.
window.setTimeout(function() {
container.classList.remove('transparent');
if (isSubpage)
container.style.WebkitPaddingStart = '';
});
} else {
var self = this;
container.addEventListener('webkitTransitionEnd', function f(e) {
if (e.propertyName != 'opacity')
return;
container.removeEventListener('webkitTransitionEnd', f);
self.fadeCompleted_(container);
});
container.classList.add('transparent');
}
},
/**
* Called when a container opacity transition finishes.
* @param {HTMLElement} container The container element.
* @private
*/
fadeCompleted_: function(container) {
if (container.classList.contains('transparent'))
container.hidden = true;
},
/**
* Focuses the first control on the page.
*/
focusFirstElement: function() {
// Sets focus on the first interactive element in the page.
var focusElement =
this.pageDiv.querySelector('button, input, list, select');
if (focusElement)
focusElement.focus();
},
/**
* The nesting level of this page.
* @type {number} The nesting level of this page (0 for top-level page)
*/
get nestingLevel() {
var level = 0;
var parent = this.parentPage;
while (parent) {
level++;
parent = parent.parentPage;
}
return level;
},
/**
* Whether the page is considered 'sticky', such that it will
* remain a top-level page even if sub-pages change.
* @type {boolean} True if this page is sticky.
*/
get sticky() {
return false;
},
/**
* Checks whether this page is an ancestor of the given page in terms of
* subpage nesting.
* @param {OptionsPage} page
* @return {boolean} True if this page is nested under |page|
*/
isAncestorOfPage: function(page) {
var parent = page.parentPage;
while (parent) {
if (parent == this)
return true;
parent = parent.parentPage;
}
return false;
},
/**
* Whether it should be possible to show the page.
* @return {boolean} True if the page should be shown
*/
canShowPage: function() {
return true;
},
};
// Export
return {
OptionsPage: OptionsPage
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const Tree = cr.ui.Tree;
const TreeItem = cr.ui.TreeItem;
/**
* Creates a new tree item for certificate data.
* @param {Object=} data Data used to create a certificate tree item.
* @constructor
* @extends {TreeItem}
*/
function CertificateTreeItem(data) {
// TODO(mattm): other columns
var treeItem = new TreeItem({
label: data.name,
data: data
});
treeItem.__proto__ = CertificateTreeItem.prototype;
if (data.icon) {
treeItem.icon = data.icon;
}
if (data.untrusted) {
var badge = document.createElement('span');
badge.setAttribute('class', 'certUntrusted');
badge.textContent = localStrings.getString("badgeCertUntrusted");
treeItem.labelElement.insertBefore(
badge, treeItem.labelElement.firstChild);
}
return treeItem;
}
CertificateTreeItem.prototype = {
__proto__: TreeItem.prototype,
/**
* The tree path id/.
* @type {string}
*/
get pathId() {
var parent = this.parentItem;
if (parent && parent instanceof CertificateTreeItem) {
return parent.pathId + ',' + this.data.id;
} else {
return this.data.id;
}
}
};
/**
* Creates a new cookies tree.
* @param {Object=} opt_propertyBag Optional properties.
* @constructor
* @extends {Tree}
*/
var CertificatesTree = cr.ui.define('tree');
CertificatesTree.prototype = {
__proto__: Tree.prototype,
/** @inheritDoc */
decorate: function() {
Tree.prototype.decorate.call(this);
this.treeLookup_ = {};
},
/** @inheritDoc */
addAt: function(child, index) {
Tree.prototype.addAt.call(this, child, index);
if (child.data && child.data.id)
this.treeLookup_[child.data.id] = child;
},
/** @inheritDoc */
remove: function(child) {
Tree.prototype.remove.call(this, child);
if (child.data && child.data.id)
delete this.treeLookup_[child.data.id];
},
/**
* Clears the tree.
*/
clear: function() {
// Remove all fields without recreating the object since other code
// references it.
for (var id in this.treeLookup_){
delete this.treeLookup_[id];
}
this.textContent = '';
},
/**
* Populate the tree.
* @param {Array} nodesData Nodes data array.
*/
populate: function(nodesData) {
this.clear();
for (var i = 0; i < nodesData.length; ++i) {
var subnodes = nodesData[i]['subnodes'];
delete nodesData[i]['subnodes'];
var item = new CertificateTreeItem(nodesData[i]);
this.addAt(item, i);
for (var j = 0; j < subnodes.length; ++j) {
var subitem = new CertificateTreeItem(subnodes[j]);
item.addAt(subitem, j);
}
// Make tree expanded by default.
item.expanded = true;
}
cr.dispatchSimpleEvent(this, 'change');
},
};
return {
CertificatesTree: CertificatesTree
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/////////////////////////////////////////////////////////////////////////////
// CertificateManagerTab class:
/**
* blah
* @param {!string} id The id of this tab.
*/
function CertificateManagerTab(id) {
this.tree = $(id + '-tree');
options.CertificatesTree.decorate(this.tree);
this.tree.addEventListener('change',
this.handleCertificatesTreeChange_.bind(this));
var tree = this.tree;
this.viewButton = $(id + '-view');
this.viewButton.onclick = function(e) {
var selected = tree.selectedItem;
chrome.send('viewCertificate', [selected.data.id]);
}
this.editButton = $(id + '-edit');
if (this.editButton !== null) {
if (id == 'serverCertsTab') {
this.editButton.onclick = function(e) {
var selected = tree.selectedItem;
chrome.send('editServerCertificate', [selected.data.id]);
}
} else if (id == 'caCertsTab') {
this.editButton.onclick = function(e) {
var data = tree.selectedItem.data;
CertificateEditCaTrustOverlay.show(data.id, data.name);
}
}
}
this.backupButton = $(id + '-backup');
if (this.backupButton !== null) {
this.backupButton.onclick = function(e) {
var selected = tree.selectedItem;
chrome.send('exportPersonalCertificate', [selected.data.id]);
}
}
this.backupAllButton = $(id + '-backup-all');
if (this.backupAllButton !== null) {
this.backupAllButton.onclick = function(e) {
chrome.send('exportAllPersonalCertificates', []);
}
}
this.importButton = $(id + '-import');
if (this.importButton !== null) {
if (id == 'personalCertsTab') {
this.importButton.onclick = function(e) {
chrome.send('importPersonalCertificate', [false]);
}
} else if (id == 'serverCertsTab') {
this.importButton.onclick = function(e) {
chrome.send('importServerCertificate', []);
}
} else if (id == 'caCertsTab') {
this.importButton.onclick = function(e) {
chrome.send('importCaCertificate', []);
}
}
}
this.importAndBindButton = $(id + '-import-and-bind');
if (this.importAndBindButton !== null) {
if (id == 'personalCertsTab') {
this.importAndBindButton.onclick = function(e) {
chrome.send('importPersonalCertificate', [true]);
}
}
}
this.exportButton = $(id + '-export');
if (this.exportButton !== null) {
this.exportButton.onclick = function(e) {
var selected = tree.selectedItem;
chrome.send('exportCertificate', [selected.data.id]);
}
}
this.deleteButton = $(id + '-delete');
this.deleteButton.onclick = function(e) {
var data = tree.selectedItem.data;
AlertOverlay.show(
localStrings.getStringF(id + 'DeleteConfirm', data.name),
localStrings.getString(id + 'DeleteImpact'),
localStrings.getString('ok'),
localStrings.getString('cancel'),
function() {
tree.selectedItem = null;
chrome.send('deleteCertificate', [data.id]);
});
}
}
CertificateManagerTab.prototype = {
/**
* Update button state.
* @private
* @param {!Object} data The data of the selected item.
*/
updateButtonState: function(data) {
var isCert = !!data && data.id.substr(0, 5) == 'cert-';
var readOnly = !!data && data.readonly;
var hasChildren = this.tree.items.length > 0;
this.viewButton.disabled = !isCert;
if (this.editButton !== null)
this.editButton.disabled = !isCert;
if (this.backupButton !== null)
this.backupButton.disabled = !isCert;
if (this.backupAllButton !== null)
this.backupAllButton.disabled = !hasChildren;
if (this.exportButton !== null)
this.exportButton.disabled = !isCert;
this.deleteButton.disabled = !isCert || readOnly;
},
/**
* Handles certificate tree selection change.
* @private
* @param {!Event} e The change event object.
*/
handleCertificatesTreeChange_: function(e) {
var data = null;
if (this.tree.selectedItem) {
data = this.tree.selectedItem.data;
}
this.updateButtonState(data);
},
}
// TODO(xiyuan): Use notification from backend instead of polling.
// TPM token check polling timer.
var tpmPollingTimer;
// Initiate tpm token check if needed.
function checkTpmToken() {
var importAndBindButton = $('personalCertsTab-import-and-bind');
if (importAndBindButton && importAndBindButton.disabled)
chrome.send('checkTpmTokenReady');
}
// Stop tpm polling timer.
function stopTpmTokenCheckPolling() {
if (tpmPollingTimer) {
window.clearTimeout(tpmPollingTimer);
tpmPollingTimer = undefined;
}
}
/////////////////////////////////////////////////////////////////////////////
// CertificateManager class:
/**
* Encapsulated handling of ChromeOS accounts options page.
* @constructor
*/
function CertificateManager(model) {
OptionsPage.call(this, 'certificates',
templateData.certificateManagerPageTabTitle,
'certificateManagerPage');
}
cr.addSingletonGetter(CertificateManager);
CertificateManager.prototype = {
__proto__: OptionsPage.prototype,
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
this.personalTab = new CertificateManagerTab('personalCertsTab');
this.serverTab = new CertificateManagerTab('serverCertsTab');
this.caTab = new CertificateManagerTab('caCertsTab');
this.otherTab = new CertificateManagerTab('otherCertsTab');
this.addEventListener('visibleChange', this.handleVisibleChange_);
},
initalized_: false,
/**
* Handler for OptionsPage's visible property change event.
* @private
* @param {Event} e Property change event.
*/
handleVisibleChange_: function(e) {
if (!this.initalized_ && this.visible) {
this.initalized_ = true;
OptionsPage.showTab($('personal-certs-nav-tab'));
chrome.send('populateCertificateManager');
}
if (cr.isChromeOS) {
// Ensure TPM token check on visible and stop polling when hidden.
if (this.visible)
checkTpmToken();
else
stopTpmTokenCheckPolling();
}
}
};
// CertificateManagerHandler callbacks.
CertificateManager.onPopulateTree = function(args) {
$(args[0]).populate(args[1]);
};
CertificateManager.exportPersonalAskPassword = function(args) {
CertificateBackupOverlay.show();
};
CertificateManager.importPersonalAskPassword = function(args) {
CertificateRestoreOverlay.show();
};
CertificateManager.onCheckTpmTokenReady = function(ready) {
var importAndBindButton = $('personalCertsTab-import-and-bind');
if (importAndBindButton) {
importAndBindButton.disabled = !ready;
// Check again after 5 seconds if Tpm is not ready and certificate manager
// is still visible.
if (!ready && CertificateManager.getInstance().visible)
tpmPollingTimer = window.setTimeout(checkTpmToken, 5000);
}
};
// Export
return {
CertificateManagerTab: CertificateManagerTab,
CertificateManager: CertificateManager
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
/**
* CertificateRestoreOverlay class
* Encapsulated handling of the 'enter restore password' overlay page.
* @class
*/
function CertificateRestoreOverlay() {
OptionsPage.call(this, 'certificateRestore',
'',
'certificateRestoreOverlay');
}
cr.addSingletonGetter(CertificateRestoreOverlay);
CertificateRestoreOverlay.prototype = {
__proto__: OptionsPage.prototype,
/**
* Initializes the page.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var self = this;
$('certificateRestoreCancelButton').onclick = function(event) {
self.cancelRestore_();
}
$('certificateRestoreOkButton').onclick = function(event) {
self.finishRestore_();
}
self.clearInputFields_();
},
/**
* Clears any uncommitted input, and dismisses the overlay.
* @private
*/
dismissOverlay_: function() {
this.clearInputFields_();
OptionsPage.closeOverlay();
},
/**
* Attempt the restore operation.
* The overlay will be left up with inputs disabled until the backend
* finishes and dismisses it.
* @private
*/
finishRestore_: function() {
chrome.send('importPersonalCertificatePasswordSelected',
[$('certificateRestorePassword').value]);
$('certificateRestoreCancelButton').disabled = true;
$('certificateRestoreOkButton').disabled = true;
},
/**
* Cancel the restore operation.
* @private
*/
cancelRestore_: function() {
chrome.send('cancelImportExportCertificate');
this.dismissOverlay_();
},
/**
* Clears the value of each input field.
* @private
*/
clearInputFields_: function() {
$('certificateRestorePassword').value = '';
$('certificateRestoreCancelButton').disabled = false;
$('certificateRestoreOkButton').disabled = false;
},
};
CertificateRestoreOverlay.show = function() {
CertificateRestoreOverlay.getInstance().clearInputFields_();
OptionsPage.navigateToPage('certificateRestore');
};
CertificateRestoreOverlay.dismiss = function() {
CertificateRestoreOverlay.getInstance().dismissOverlay_();
};
// Export
return {
CertificateRestoreOverlay: CertificateRestoreOverlay
};
});
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
/**
* CertificateBackupOverlay class
* Encapsulated handling of the 'enter backup password' overlay page.
* @class
*/
function CertificateBackupOverlay() {
OptionsPage.call(this, 'certificateBackupOverlay',
'',
'certificateBackupOverlay');
}
cr.addSingletonGetter(CertificateBackupOverlay);
CertificateBackupOverlay.prototype = {
__proto__: OptionsPage.prototype,
/**
* Initializes the page.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var self = this;
$('certificateBackupCancelButton').onclick = function(event) {
self.cancelBackup_();
}
$('certificateBackupOkButton').onclick = function(event) {
self.finishBackup_();
}
$('certificateBackupPassword').oninput =
$('certificateBackupPassword2').oninput = function(event) {
self.comparePasswords_();
}
self.clearInputFields_();
},
/**
* Clears any uncommitted input, and dismisses the overlay.
* @private
*/
dismissOverlay_: function() {
this.clearInputFields_();
OptionsPage.closeOverlay();
},
/**
* Attempt the Backup operation.
* The overlay will be left up with inputs disabled until the backend
* finishes and dismisses it.
* @private
*/
finishBackup_: function() {
chrome.send('exportPersonalCertificatePasswordSelected',
[$('certificateBackupPassword').value]);
$('certificateBackupCancelButton').disabled = true;
$('certificateBackupOkButton').disabled = true;
$('certificateBackupPassword').disabled = true;
$('certificateBackupPassword2').disabled = true;
},
/**
* Cancel the Backup operation.
* @private
*/
cancelBackup_: function() {
chrome.send('cancelImportExportCertificate');
this.dismissOverlay_();
},
/**
* Compares the password fields and sets the button state appropriately.
* @private
*/
comparePasswords_: function() {
var password1 = $('certificateBackupPassword').value;
var password2 = $('certificateBackupPassword2').value;
$('certificateBackupOkButton').disabled =
!password1 || password1 != password2;
},
/**
* Clears the value of each input field.
* @private
*/
clearInputFields_: function() {
$('certificateBackupPassword').value = '';
$('certificateBackupPassword2').value = '';
$('certificateBackupPassword').disabled = false;
$('certificateBackupPassword2').disabled = false;
$('certificateBackupCancelButton').disabled = false;
$('certificateBackupOkButton').disabled = true;
},
};
CertificateBackupOverlay.show = function() {
CertificateBackupOverlay.getInstance().clearInputFields_();
OptionsPage.navigateToPage('certificateBackupOverlay');
};
CertificateBackupOverlay.dismiss = function() {
CertificateBackupOverlay.getInstance().dismissOverlay_();
};
// Export
return {
CertificateBackupOverlay: CertificateBackupOverlay
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
/**
* CertificateEditCaTrustOverlay class
* Encapsulated handling of the 'edit ca trust' and 'import ca' overlay pages.
* @class
*/
function CertificateEditCaTrustOverlay() {
OptionsPage.call(this, 'certificateEditCaTrustOverlay',
'',
'certificateEditCaTrustOverlay');
}
cr.addSingletonGetter(CertificateEditCaTrustOverlay);
CertificateEditCaTrustOverlay.prototype = {
__proto__: OptionsPage.prototype,
/**
* Dismisses the overlay.
* @private
*/
dismissOverlay_: function() {
OptionsPage.closeOverlay();
},
/**
* Enables or disables input fields.
* @private
*/
enableInputs_: function(enabled) {
$('certificateCaTrustSSLCheckbox').disabled =
$('certificateCaTrustEmailCheckbox').disabled =
$('certificateCaTrustObjSignCheckbox').disabled =
$('certificateEditCaTrustCancelButton').disabled =
$('certificateEditCaTrustOkButton').disabled = !enabled;
},
/**
* Attempt the Edit operation.
* The overlay will be left up with inputs disabled until the backend
* finishes and dismisses it.
* @private
*/
finishEdit_: function() {
// TODO(mattm): Send checked values as booleans. For now send them as
// strings, since WebUIBindings::send does not support any other types :(
chrome.send('editCaCertificateTrust',
[this.certId,
$('certificateCaTrustSSLCheckbox').checked.toString(),
$('certificateCaTrustEmailCheckbox').checked.toString(),
$('certificateCaTrustObjSignCheckbox').checked.toString()]);
this.enableInputs_(false);
},
/**
* Cancel the Edit operation.
* @private
*/
cancelEdit_: function() {
this.dismissOverlay_();
},
/**
* Attempt the Import operation.
* The overlay will be left up with inputs disabled until the backend
* finishes and dismisses it.
* @private
*/
finishImport_: function() {
// TODO(mattm): Send checked values as booleans. For now send them as
// strings, since WebUIBindings::send does not support any other types :(
chrome.send('importCaCertificateTrustSelected',
[$('certificateCaTrustSSLCheckbox').checked.toString(),
$('certificateCaTrustEmailCheckbox').checked.toString(),
$('certificateCaTrustObjSignCheckbox').checked.toString()]);
this.enableInputs_(false);
},
/**
* Cancel the Import operation.
* @private
*/
cancelImport_: function() {
chrome.send('cancelImportExportCertificate');
this.dismissOverlay_();
},
};
/**
* Callback from CertificateManagerHandler with the trust values.
* @param {boolean} trustSSL The initial value of SSL trust checkbox.
* @param {boolean} trustEmail The initial value of Email trust checkbox.
* @param {boolean} trustObjSign The initial value of Object Signing trust
*/
CertificateEditCaTrustOverlay.populateTrust = function(
trustSSL, trustEmail, trustObjSign) {
$('certificateCaTrustSSLCheckbox').checked = trustSSL;
$('certificateCaTrustEmailCheckbox').checked = trustEmail;
$('certificateCaTrustObjSignCheckbox').checked = trustObjSign;
CertificateEditCaTrustOverlay.getInstance().enableInputs_(true);
}
/**
* Show the Edit CA Trust overlay.
* @param {string} certId The id of the certificate to be passed to the
* certificate manager model.
* @param {string} certName The display name of the certificate.
* checkbox.
*/
CertificateEditCaTrustOverlay.show = function(certId, certName) {
var self = CertificateEditCaTrustOverlay.getInstance();
self.certId = certId;
$('certificateEditCaTrustCancelButton').onclick = function(event) {
self.cancelEdit_();
}
$('certificateEditCaTrustOkButton').onclick = function(event) {
self.finishEdit_();
}
$('certificateEditCaTrustDescription').textContent =
localStrings.getStringF('certificateEditCaTrustDescriptionFormat',
certName);
self.enableInputs_(false);
OptionsPage.navigateToPage('certificateEditCaTrustOverlay');
chrome.send('getCaCertificateTrust', [certId]);
}
/**
* Show the Import CA overlay.
* @param {string} certId The id of the certificate to be passed to the
* certificate manager model.
* @param {string} certName The display name of the certificate.
* checkbox.
*/
CertificateEditCaTrustOverlay.showImport = function(certName) {
var self = CertificateEditCaTrustOverlay.getInstance();
// TODO(mattm): do we want a view certificate button here like firefox has?
$('certificateEditCaTrustCancelButton').onclick = function(event) {
self.cancelImport_();
}
$('certificateEditCaTrustOkButton').onclick = function(event) {
self.finishImport_();
}
$('certificateEditCaTrustDescription').textContent =
localStrings.getStringF('certificateImportCaDescriptionFormat',
certName);
CertificateEditCaTrustOverlay.populateTrust(false, false, false);
OptionsPage.navigateToPage('certificateEditCaTrustOverlay');
}
CertificateEditCaTrustOverlay.dismiss = function() {
CertificateEditCaTrustOverlay.getInstance().dismissOverlay_();
};
// Export
return {
CertificateEditCaTrustOverlay: CertificateEditCaTrustOverlay
};
});
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/**
* CertificateImportErrorOverlay class
* Displays a list of certificates and errors.
* @class
*/
function CertificateImportErrorOverlay() {
OptionsPage.call(this, 'certificateImportErrorOverlay', '',
'certificateImportErrorOverlay');
}
cr.addSingletonGetter(CertificateImportErrorOverlay);
CertificateImportErrorOverlay.prototype = {
// Inherit CertificateImportErrorOverlay from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to start preference initialization.
OptionsPage.prototype.initializePage.call(this);
$('certificateImportErrorOverlayOk').onclick = function(event) {
OptionsPage.closeOverlay();
};
},
};
/**
* Show an alert overlay with the given message, button titles, and
* callbacks.
* @param {string} title The alert title to display to the user.
* @param {string} message The alert message to display to the user.
* @param {Array} certErrors The list of cert errors. Each error should have
* a .name and .error attribute.
*/
CertificateImportErrorOverlay.show = function(title, message, certErrors) {
$('certificateImportErrorOverlayTitle').textContent = title;
$('certificateImportErrorOverlayMessage').textContent = message;
ul = $('certificateImportErrorOverlayCertErrors');
ul.innerHTML = '';
for (var i = 0; i < certErrors.length; ++i) {
li = document.createElement("li");
li.textContent = localStrings.getStringF('certificateImportErrorFormat',
certErrors[i].name,
certErrors[i].error);
ul.appendChild(li);
}
OptionsPage.navigateToPage('certificateImportErrorOverlay');
}
// Export
return {
CertificateImportErrorOverlay: CertificateImportErrorOverlay
};
});
var CertificateManager = options.CertificateManager;
var CertificateRestoreOverlay = options.CertificateRestoreOverlay;
var CertificateBackupOverlay = options.CertificateBackupOverlay;
var CertificateEditCaTrustOverlay = options.CertificateEditCaTrustOverlay;
var CertificateImportErrorOverlay = options.CertificateImportErrorOverlay;
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
//
// AdvancedOptions class
// Encapsulated handling of advanced options page.
//
function AdvancedOptions() {
OptionsPage.call(this, 'advanced', templateData.advancedPageTabTitle,
'advancedPage');
}
cr.addSingletonGetter(AdvancedOptions);
AdvancedOptions.prototype = {
// Inherit AdvancedOptions from OptionsPage.
__proto__: options.OptionsPage.prototype,
/**
* Initializes the page.
*/
initializePage: function() {
// Call base class implementation to starts preference initialization.
OptionsPage.prototype.initializePage.call(this);
// Set up click handlers for buttons.
$('privacyContentSettingsButton').onclick = function(event) {
OptionsPage.navigateToPage('content');
OptionsPage.showTab($('cookies-nav-tab'));
chrome.send('coreOptionsUserMetricsAction',
['Options_ContentSettings']);
};
$('privacyClearDataButton').onclick = function(event) {
OptionsPage.navigateToPage('clearBrowserData');
chrome.send('coreOptionsUserMetricsAction', ['Options_ClearData']);
};
// 'metricsReportingEnabled' element is only present on Chrome branded
// builds.
if ($('metricsReportingEnabled')) {
$('metricsReportingEnabled').onclick = function(event) {
chrome.send('metricsReportingCheckboxAction',
[String(event.target.checked)]);
};
}
if (!cr.isChromeOS) {
$('autoOpenFileTypesResetToDefault').onclick = function(event) {
chrome.send('autoOpenFileTypesAction');
};
}
$('fontSettingsCustomizeFontsButton').onclick = function(event) {
OptionsPage.navigateToPage('fonts');
chrome.send('coreOptionsUserMetricsAction', ['Options_FontSettings']);
};
$('defaultFontSize').onchange = function(event) {
chrome.send('defaultFontSizeAction',
[String(event.target.options[event.target.selectedIndex].value)]);
};
$('defaultZoomFactor').onchange = function(event) {
chrome.send('defaultZoomFactorAction',
[String(event.target.options[event.target.selectedIndex].value)]);
};
$('language-button').onclick = function(event) {
OptionsPage.navigateToPage('languages');
chrome.send('coreOptionsUserMetricsAction',
['Options_LanuageAndSpellCheckSettings']);
};
if (cr.isWindows || cr.isMac) {
$('certificatesManageButton').onclick = function(event) {
chrome.send('showManageSSLCertificates');
};
} else {
$('certificatesManageButton').onclick = function(event) {
OptionsPage.navigateToPage('certificates');
chrome.send('coreOptionsUserMetricsAction',
['Options_ManageSSLCertificates']);
};
}
if (!cr.isChromeOS) {
$('proxiesConfigureButton').onclick = function(event) {
chrome.send('showNetworkProxySettings');
};
$('downloadLocationChangeButton').onclick = function(event) {
chrome.send('selectDownloadLocation');
};
// This text field is always disabled. Setting ".disabled = true" isn't
// enough, since a policy can disable it but shouldn't re-enable when
// it is removed.
$('downloadLocationPath').setDisabled('readonly', true);
}
$('sslCheckRevocation').onclick = function(event) {
chrome.send('checkRevocationCheckboxAction',
[String($('sslCheckRevocation').checked)]);
};
if ($('backgroundModeCheckbox')) {
$('backgroundModeCheckbox').onclick = function(event) {
chrome.send('backgroundModeAction',
[String($('backgroundModeCheckbox').checked)]);
};
}
// 'cloudPrintProxyEnabled' is true for Chrome branded builds on
// certain platforms, or could be enabled by a lab.
if (!cr.isChromeOS) {
$('cloudPrintProxySetupButton').onclick = function(event) {
if ($('cloudPrintProxyManageButton').style.display == 'none') {
// Disable the button, set it's text to the intermediate state.
$('cloudPrintProxySetupButton').textContent =
localStrings.getString('cloudPrintProxyEnablingButton');
$('cloudPrintProxySetupButton').disabled = true;
chrome.send('showCloudPrintSetupDialog');
} else {
chrome.send('disableCloudPrintProxy');
}
};
}
$('cloudPrintProxyManageButton').onclick = function(event) {
chrome.send('showCloudPrintManagePage');
};
}
};
//
// Chrome callbacks
//
// Set the checked state of the metrics reporting checkbox.
AdvancedOptions.SetMetricsReportingCheckboxState = function(
checked, disabled) {
$('metricsReportingEnabled').checked = checked;
$('metricsReportingEnabled').disabled = disabled;
if (disabled)
$('metricsReportingEnabledText').className = 'disable-services-span';
}
AdvancedOptions.SetMetricsReportingSettingVisibility = function(visible) {
if (visible) {
$('metricsReportingSetting').style.display = 'block';
} else {
$('metricsReportingSetting').style.display = 'none';
}
}
// Set the font size selected item.
AdvancedOptions.SetFontSize = function(font_size_value) {
var selectCtl = $('defaultFontSize');
for (var i = 0; i < selectCtl.options.length; i++) {
if (selectCtl.options[i].value == font_size_value) {
selectCtl.selectedIndex = i;
if ($('Custom'))
selectCtl.remove($('Custom').index);
return;
}
}
// Add/Select Custom Option in the font size label list.
if (!$('Custom')) {
var option = new Option(localStrings.getString('fontSizeLabelCustom'),
-1, false, true);
option.setAttribute("id", "Custom");
selectCtl.add(option);
}
$('Custom').selected = true;
};
/**
* Populate the page zoom selector with values received from the caller.
* @param {Array} items An array of items to populate the selector.
* each object is an array with three elements as follows:
* 0: The title of the item (string).
* 1: The value of the item (number).
* 2: Whether the item should be selected (boolean).
*/
AdvancedOptions.SetupPageZoomSelector = function(items) {
var element = $('defaultZoomFactor');
// Remove any existing content.
element.textContent = '';
// Insert new child nodes into select element.
var value, title, selected;
for (var i = 0; i < items.length; i++) {
title = items[i][0];
value = items[i][1];
selected = items[i][2];
element.appendChild(new Option(title, value, false, selected));
}
};
// Set the enabled state for the autoOpenFileTypesResetToDefault button.
AdvancedOptions.SetAutoOpenFileTypesDisabledAttribute = function(disabled) {
if (!cr.isChromeOS) {
$('autoOpenFileTypesResetToDefault').disabled = disabled;
if (disabled)
$('auto-open-file-types-label').classList.add('disabled');
else
$('auto-open-file-types-label').classList.remove('disabled');
}
};
// Set the enabled state for the proxy settings button.
AdvancedOptions.SetupProxySettingsSection = function(disabled, label) {
if (!cr.isChromeOS) {
$('proxiesConfigureButton').disabled = disabled;
$('proxiesLabel').textContent = label;
}
};
// Set the checked state for the sslCheckRevocation checkbox.
AdvancedOptions.SetCheckRevocationCheckboxState = function(
checked, disabled) {
$('sslCheckRevocation').checked = checked;
$('sslCheckRevocation').disabled = disabled;
};
// Set the checked state for the backgroundModeCheckbox element.
AdvancedOptions.SetBackgroundModeCheckboxState = function(checked) {
$('backgroundModeCheckbox').checked = checked;
};
// Set the Cloud Print proxy UI to enabled, disabled, or processing.
AdvancedOptions.SetupCloudPrintProxySection = function(
disabled, label, allowed) {
if (!cr.isChromeOS) {
$('cloudPrintProxyLabel').textContent = label;
if (disabled || !allowed) {
$('cloudPrintProxySetupButton').textContent =
localStrings.getString('cloudPrintProxyDisabledButton');
$('cloudPrintProxyManageButton').style.display = 'none';
} else {
$('cloudPrintProxySetupButton').textContent =
localStrings.getString('cloudPrintProxyEnabledButton');
$('cloudPrintProxyManageButton').style.display = 'inline';
}
$('cloudPrintProxySetupButton').disabled = !allowed;
}
};
AdvancedOptions.RemoveCloudPrintProxySection = function() {
if (!cr.isChromeOS) {
var proxySectionElm = $('cloud-print-proxy-section');
if (proxySectionElm)
proxySectionElm.parentNode.removeChild(proxySectionElm);
}
};
// Export
return {
AdvancedOptions: AdvancedOptions
};
});
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/**
* AlertOverlay class
* Encapsulated handling of a generic alert.
* @class
*/
function AlertOverlay() {
OptionsPage.call(this, 'alertOverlay', '', 'alertOverlay');
}
cr.addSingletonGetter(AlertOverlay);
AlertOverlay.prototype = {
// Inherit AlertOverlay from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* Whether the page can be shown. Used to make sure the page is only
* shown via AlertOverlay.Show(), and not via the address bar.
* @private
*/
canShow_: false,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to start preference initialization.
OptionsPage.prototype.initializePage.call(this);
var self = this;
$('alertOverlayOk').onclick = function(event) {
self.handleOK_();
};
$('alertOverlayCancel').onclick = function(event) {
self.handleCancel_();
};
},
/**
* Handle the 'ok' button. Clear the overlay and call the ok callback if
* available.
* @private
*/
handleOK_: function() {
OptionsPage.closeOverlay();
if (this.okCallback != undefined) {
this.okCallback.call();
}
},
/**
* Handle the 'cancel' button. Clear the overlay and call the cancel
* callback if available.
* @private
*/
handleCancel_: function() {
OptionsPage.closeOverlay();
if (this.cancelCallback != undefined) {
this.cancelCallback.call();
}
},
/**
* The page is getting hidden. Don't let it be shown again.
*/
willHidePage: function() {
canShow_ = false;
},
/** @inheritDoc */
canShowPage: function() {
return this.canShow_;
},
};
/**
* Show an alert overlay with the given message, button titles, and
* callbacks.
* @param {string} title The alert title to display to the user.
* @param {string} message The alert message to display to the user.
* @param {string} okTitle The title of the OK button. If undefined or empty,
* no button is shown.
* @param {string} cancelTitle The title of the cancel button. If undefined or
* empty, no button is shown.
* @param {function} okCallback A function to be called when the user presses
* the ok button. The alert window will be closed automatically. Can be
* undefined.
* @param {function} cancelCallback A function to be called when the user
* presses the cancel button. The alert window will be closed
* automatically. Can be undefined.
*/
AlertOverlay.show = function(
title, message, okTitle, cancelTitle, okCallback, cancelCallback) {
if (title != undefined) {
$('alertOverlayTitle').textContent = title;
$('alertOverlayTitle').style.display = 'block';
} else {
$('alertOverlayTitle').style.display = 'none';
}
if (message != undefined) {
$('alertOverlayMessage').textContent = message;
$('alertOverlayMessage').style.display = 'block';
} else {
$('alertOverlayMessage').style.display = 'none';
}
if (okTitle != undefined && okTitle != '') {
$('alertOverlayOk').textContent = okTitle;
$('alertOverlayOk').style.display = 'block';
} else {
$('alertOverlayOk').style.display = 'none';
}
if (cancelTitle != undefined && cancelTitle != '') {
$('alertOverlayCancel').textContent = cancelTitle;
$('alertOverlayCancel').style.display = 'inline';
} else {
$('alertOverlayCancel').style.display = 'none';
}
var alertOverlay = AlertOverlay.getInstance();
alertOverlay.okCallback = okCallback;
alertOverlay.cancelCallback = cancelCallback;
alertOverlay.canShow_ = true;
// Intentionally don't show the URL in the location bar as we don't want
// people trying to navigate here by hand.
OptionsPage.showPageByName('alertOverlay', false);
}
// Export
return {
AlertOverlay: AlertOverlay
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const ArrayDataModel = cr.ui.ArrayDataModel;
const List = cr.ui.List;
const ListItem = cr.ui.ListItem;
/**
* Creates a new autocomplete list item.
* @param {Object} pageInfo The page this item represents.
* @constructor
* @extends {cr.ui.ListItem}
*/
function AutocompleteListItem(pageInfo) {
var el = cr.doc.createElement('div');
el.pageInfo_ = pageInfo;
AutocompleteListItem.decorate(el);
return el;
}
/**
* Decorates an element as an autocomplete list item.
* @param {!HTMLElement} el The element to decorate.
*/
AutocompleteListItem.decorate = function(el) {
el.__proto__ = AutocompleteListItem.prototype;
el.decorate();
};
AutocompleteListItem.prototype = {
__proto__: ListItem.prototype,
/** @inheritDoc */
decorate: function() {
ListItem.prototype.decorate.call(this);
var title = this.pageInfo_['title'];
var url = this.pageInfo_['displayURL'];
var titleEl = this.ownerDocument.createElement('span');
titleEl.className = 'title';
titleEl.textContent = title || url;
this.appendChild(titleEl);
if (title && title.length > 0 && url != title) {
var separatorEl = this.ownerDocument.createTextNode(' - ');
this.appendChild(separatorEl);
var urlEl = this.ownerDocument.createElement('span');
urlEl.className = 'url';
urlEl.textContent = url;
this.appendChild(urlEl);
}
},
};
/**
* Creates a new autocomplete list popup.
* @constructor
* @extends {cr.ui.List}
*/
var AutocompleteList = cr.ui.define('list');
AutocompleteList.prototype = {
__proto__: List.prototype,
/**
* The text field the autocomplete popup is currently attached to, if any.
* @type {HTMLElement}
* @private
*/
targetInput_: null,
/**
* Keydown event listener to attach to a text field.
* @type {Function}
* @private
*/
textFieldKeyHandler_: null,
/**
* Input event listener to attach to a text field.
* @type {Function}
* @private
*/
textFieldInputHandler_: null,
/**
* A function to call when new suggestions are needed.
* @type {Function}
* @private
*/
suggestionUpdateRequestCallback_: null,
/** @inheritDoc */
decorate: function() {
List.prototype.decorate.call(this);
this.classList.add('autocomplete-suggestions');
this.selectionModel = new cr.ui.ListSingleSelectionModel;
this.textFieldKeyHandler_ = this.handleAutocompleteKeydown_.bind(this);
var self = this;
this.textFieldInputHandler_ = function(e) {
if (self.suggestionUpdateRequestCallback_)
self.suggestionUpdateRequestCallback_(self.targetInput_.value);
};
this.addEventListener('change', function(e) {
var input = self.targetInput;
if (!input || !self.selectedItem)
return;
input.value = self.selectedItem['url'];
// Programatically change the value won't trigger a change event, but
// clients are likely to want to know when changes happen, so fire one.
var changeEvent = document.createEvent('Event');
changeEvent.initEvent('change', true, true);
input.dispatchEvent(changeEvent);
});
// Start hidden; adding suggestions will unhide.
this.hidden = true;
},
/** @inheritDoc */
createItem: function(pageInfo) {
return new AutocompleteListItem(pageInfo);
},
/**
* The suggestions to show.
* @type {Array}
*/
set suggestions(suggestions) {
this.dataModel = new ArrayDataModel(suggestions);
this.hidden = !this.targetInput_ || suggestions.length == 0;
},
/**
* A function to call when the attached input field's contents change.
* The function should take one string argument, which will be the text
* to autocomplete from.
* @type {Function}
*/
set suggestionUpdateRequestCallback(callback) {
this.suggestionUpdateRequestCallback_ = callback;
},
/**
* Attaches the popup to the given input element. Requires
* that the input be wrapped in a block-level container of the same width.
* @param {HTMLElement} input The input element to attach to.
*/
attachToInput: function(input) {
if (this.targetInput_ == input)
return;
this.detach();
this.targetInput_ = input;
this.style.width = input.getBoundingClientRect().width + 'px';
this.hidden = false; // Necessary for positionPopupAroundElement to work.
cr.ui.positionPopupAroundElement(input, this, cr.ui.AnchorType.BELOW)
// Start hidden; when the data model gets results the list will show.
this.hidden = true;
input.addEventListener('keydown', this.textFieldKeyHandler_, true);
input.addEventListener('input', this.textFieldInputHandler_);
},
/**
* Detaches the autocomplete popup from its current input element, if any.
*/
detach: function() {
var input = this.targetInput_
if (!input)
return;
input.removeEventListener('keydown', this.textFieldKeyHandler_);
input.removeEventListener('input', this.textFieldInputHandler_);
this.targetInput_ = null;
this.suggestions = [];
},
/**
* Makes sure that the suggestion list matches the width of the input it is.
* attached to. Should be called any time the input is resized.
*/
syncWidthToInput: function() {
var input = this.targetInput_
if (input)
this.style.width = input.getBoundingClientRect().width + 'px';
},
/**
* The text field the autocomplete popup is currently attached to, if any.
* @return {HTMLElement}
*/
get targetInput() {
return this.targetInput_;
},
/**
* Handles input field key events that should be interpreted as autocomplete
* commands.
* @param {Event} event The keydown event.
* @private
*/
handleAutocompleteKeydown_: function(event) {
if (this.hidden)
return;
var handled = false;
switch (event.keyIdentifier) {
case 'U+001B': // Esc
this.suggestions = [];
handled = true;
break;
case 'Enter':
var hadSelection = this.selectedItem != null;
this.suggestions = [];
// Only count the event as handled if a selection is being commited.
handled = hadSelection;
break;
case 'Up':
case 'Down':
this.dispatchEvent(event);
handled = true;
break;
}
// Don't let arrow keys affect the text field, or bubble up to, e.g.,
// an enclosing list item.
if (handled) {
event.preventDefault();
event.stopPropagation();
}
},
};
return {
AutocompleteList: AutocompleteList
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
const ArrayDataModel = cr.ui.ArrayDataModel;
// The GUID of the loaded address.
var guid;
/**
* AutofillEditAddressOverlay class
* Encapsulated handling of the 'Add Page' overlay page.
* @class
*/
function AutofillEditAddressOverlay() {
OptionsPage.call(this, 'autofillEditAddress',
templateData.autofillEditAddressTitle,
'autofill-edit-address-overlay');
}
cr.addSingletonGetter(AutofillEditAddressOverlay);
AutofillEditAddressOverlay.prototype = {
__proto__: OptionsPage.prototype,
/**
* Initializes the page.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
this.createMultiValueLists_();
var self = this;
$('autofill-edit-address-cancel-button').onclick = function(event) {
self.dismissOverlay_();
}
$('autofill-edit-address-apply-button').onclick = function(event) {
self.saveAddress_();
self.dismissOverlay_();
}
self.guid = '';
self.populateCountryList_();
self.clearInputFields_();
self.connectInputEvents_();
},
/**
* Creates, decorates and initializes the multi-value lists for full name,
* phone, and email.
* @private
*/
createMultiValueLists_: function() {
var list = $('full-name-list');
options.autofillOptions.AutofillNameValuesList.decorate(list);
list.autoExpands = true;
list = $('phone-list');
options.autofillOptions.AutofillPhoneValuesList.decorate(list);
list.autoExpands = true;
list = $('email-list');
options.autofillOptions.AutofillValuesList.decorate(list);
list.autoExpands = true;
},
/**
* Updates the data model for the list named |listName| with the values from
* |entries|.
* @param {String} listName The id of the list.
* @param {Array} entries The list of items to be added to the list.
*/
setMultiValueList_: function(listName, entries) {
// Add data entries.
var list = $(listName);
list.dataModel = new ArrayDataModel(entries);
// Add special entry for adding new values.
list.dataModel.splice(list.dataModel.length, 0, null);
// Update the status of the 'OK' button.
this.inputFieldChanged_();
var self = this;
list.dataModel.addEventListener(
'splice', function(event) { self.inputFieldChanged_(); });
list.dataModel.addEventListener(
'change', function(event) { self.inputFieldChanged_(); });
},
/**
* Updates the data model for the name list with the values from |entries|.
* @param {Array} names The list of names to be added to the list.
*/
setNameList_: function(names) {
// Add the given |names| as backing data for the list.
var list = $('full-name-list');
list.dataModel = new ArrayDataModel(names);
// Add special entry for adding new values.
list.dataModel.splice(list.dataModel.length, 0, null);
var self = this;
list.dataModel.addEventListener(
'splice', function(event) { self.inputFieldChanged_(); });
list.dataModel.addEventListener(
'change', function(event) { self.inputFieldChanged_(); });
},
/**
* Clears any uncommitted input, resets the stored GUID and dismisses the
* overlay.
* @private
*/
dismissOverlay_: function() {
this.clearInputFields_();
this.guid = '';
OptionsPage.closeOverlay();
},
/**
* Aggregates the values in the input fields into an array and sends the
* array to the Autofill handler.
* @private
*/
saveAddress_: function() {
var address = new Array();
address[0] = this.guid;
var list = $('full-name-list');
address[1] = list.dataModel.slice(0, list.dataModel.length - 1);
address[2] = $('company-name').value;
address[3] = $('addr-line-1').value;
address[4] = $('addr-line-2').value;
address[5] = $('city').value;
address[6] = $('state').value;
address[7] = $('postal-code').value;
address[8] = $('country').value;
list = $('phone-list');
address[9] = list.dataModel.slice(0, list.dataModel.length - 1);
list = $('email-list');
address[10] = list.dataModel.slice(0, list.dataModel.length - 1);
chrome.send('setAddress', address);
},
/**
* Connects each input field to the inputFieldChanged_() method that enables
* or disables the 'Ok' button based on whether all the fields are empty or
* not.
* @private
*/
connectInputEvents_: function() {
var self = this;
$('company-name').oninput = $('addr-line-1').oninput =
$('addr-line-2').oninput = $('city').oninput = $('state').oninput =
$('postal-code').oninput = function(event) {
self.inputFieldChanged_();
}
$('country').onchange = function(event) {
self.countryChanged_();
}
},
/**
* Checks the values of each of the input fields and disables the 'Ok'
* button if all of the fields are empty.
* @private
*/
inputFieldChanged_: function() {
// Length of lists are tested for <= 1 due to the "add" placeholder item
// in the list.
var disabled =
$('full-name-list').items.length <= 1 &&
!$('company-name').value &&
!$('addr-line-1').value && !$('addr-line-2').value &&
!$('city').value && !$('state').value && !$('postal-code').value &&
!$('country').value && $('phone-list').items.length <= 1 &&
$('email-list').items.length <= 1;
$('autofill-edit-address-apply-button').disabled = disabled;
},
/**
* Updates the postal code and state field labels appropriately for the
* selected country.
* @private
*/
countryChanged_: function() {
var countryCode = $('country').value;
if (!countryCode)
countryCode = templateData.defaultCountryCode;
var details = templateData.autofillCountryData[countryCode];
var postal = $('postal-code-label');
postal.textContent = details['postalCodeLabel'];
$('state-label').textContent = details['stateLabel'];
// Also update the 'Ok' button as needed.
this.inputFieldChanged_();
},
/**
* Populates the country <select> list.
* @private
*/
populateCountryList_: function() {
var countryData = templateData.autofillCountryData;
var defaultCountryCode = templateData.defaultCountryCode;
// Build an array of the country names and their corresponding country
// codes, so that we can sort and insert them in order.
var countries = [];
for (var countryCode in countryData) {
var country = {
countryCode: countryCode,
name: countryData[countryCode]['name']
};
countries.push(country);
}
// Sort the countries in alphabetical order by name.
countries = countries.sort(function(a, b) {
return a.name < b.name ? -1 : 1;
});
// Insert the empty and default countries at the beginning of the array.
var emptyCountry = {
countryCode: '',
name: ''
};
var defaultCountry = {
countryCode: defaultCountryCode,
name: countryData[defaultCountryCode]['name']
};
var separator = {
countryCode: '',
name: '---',
disabled: true
}
countries.unshift(emptyCountry, defaultCountry, separator);
// Add the countries to the country <select> list.
var countryList = $('country');
for (var i = 0; i < countries.length; i++) {
var country = new Option(countries[i].name, countries[i].countryCode);
country.disabled = countries[i].disabled;
countryList.appendChild(country)
}
},
/**
* Clears the value of each input field.
* @private
*/
clearInputFields_: function() {
this.setNameList_([]);
$('company-name').value = '';
$('addr-line-1').value = '';
$('addr-line-2').value = '';
$('city').value = '';
$('state').value = '';
$('postal-code').value = '';
$('country').value = '';
this.setMultiValueList_('phone-list', []);
this.setMultiValueList_('email-list', []);
this.countryChanged_();
},
/**
* Loads the address data from |address|, sets the input fields based on
* this data and stores the GUID of the address.
* @private
*/
loadAddress_: function(address) {
this.setInputFields_(address);
this.inputFieldChanged_();
this.guid = address['guid'];
},
/**
* Sets the value of each input field according to |address|
* @private
*/
setInputFields_: function(address) {
this.setNameList_(address['fullName']);
$('company-name').value = address['companyName'];
$('addr-line-1').value = address['addrLine1'];
$('addr-line-2').value = address['addrLine2'];
$('city').value = address['city'];
$('state').value = address['state'];
$('postal-code').value = address['postalCode'];
$('country').value = address['country'];
this.setMultiValueList_('phone-list', address['phone']);
this.setMultiValueList_('email-list', address['email']);
this.countryChanged_();
},
};
AutofillEditAddressOverlay.clearInputFields = function() {
AutofillEditAddressOverlay.getInstance().clearInputFields_();
};
AutofillEditAddressOverlay.loadAddress = function(address) {
AutofillEditAddressOverlay.getInstance().loadAddress_(address);
};
AutofillEditAddressOverlay.setTitle = function(title) {
$('autofill-address-title').textContent = title;
};
AutofillEditAddressOverlay.setValidatedPhoneNumbers = function(numbers) {
AutofillEditAddressOverlay.getInstance().setMultiValueList_('phone-list',
numbers);
};
// Export
return {
AutofillEditAddressOverlay: AutofillEditAddressOverlay
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
// The GUID of the loaded credit card.
var guid_;
/**
* AutofillEditCreditCardOverlay class
* Encapsulated handling of the 'Add Page' overlay page.
* @class
*/
function AutofillEditCreditCardOverlay() {
OptionsPage.call(this, 'autofillEditCreditCard',
templateData.autofillEditCreditCardTitle,
'autofill-edit-credit-card-overlay');
}
cr.addSingletonGetter(AutofillEditCreditCardOverlay);
AutofillEditCreditCardOverlay.prototype = {
__proto__: OptionsPage.prototype,
/**
* Initializes the page.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var self = this;
$('autofill-edit-credit-card-cancel-button').onclick = function(event) {
self.dismissOverlay_();
}
$('autofill-edit-credit-card-apply-button').onclick = function(event) {
self.saveCreditCard_();
self.dismissOverlay_();
}
self.guid_ = '';
self.hasEditedNumber_ = false;
self.clearInputFields_();
self.connectInputEvents_();
self.setDefaultSelectOptions_();
},
/**
* Clears any uncommitted input, and dismisses the overlay.
* @private
*/
dismissOverlay_: function() {
this.clearInputFields_();
this.guid_ = '';
this.hasEditedNumber_ = false;
OptionsPage.closeOverlay();
},
/**
* Aggregates the values in the input fields into an array and sends the
* array to the Autofill handler.
* @private
*/
saveCreditCard_: function() {
var creditCard = new Array(5);
creditCard[0] = this.guid_;
creditCard[1] = $('name-on-card').value;
creditCard[2] = $('credit-card-number').value;
creditCard[3] = $('expiration-month').value;
creditCard[4] = $('expiration-year').value;
chrome.send('setCreditCard', creditCard);
},
/**
* Connects each input field to the inputFieldChanged_() method that enables
* or disables the 'Ok' button based on whether all the fields are empty or
* not.
* @private
*/
connectInputEvents_: function() {
var ccNumber = $('credit-card-number');
$('name-on-card').oninput = ccNumber.oninput =
$('expiration-month').onchange = $('expiration-year').onchange =
this.inputFieldChanged_.bind(this);
},
/**
* Checks the values of each of the input fields and disables the 'Ok'
* button if all of the fields are empty.
* @param {Event} opt_event Optional data for the 'input' event.
* @private
*/
inputFieldChanged_: function(opt_event) {
var disabled = !$('name-on-card').value && !$('credit-card-number').value;
$('autofill-edit-credit-card-apply-button').disabled = disabled;
},
/**
* Sets the default values of the options in the 'Expiration date' select
* controls.
* @private
*/
setDefaultSelectOptions_: function() {
// Set the 'Expiration month' default options.
var expirationMonth = $('expiration-month');
expirationMonth.options.length = 0;
for (var i = 1; i <= 12; ++i) {
var text;
if (i < 10)
text = '0' + i;
else
text = i;
var option = document.createElement('option');
option.text = text;
option.value = text;
expirationMonth.add(option, null);
}
// Set the 'Expiration year' default options.
var expirationYear = $('expiration-year');
expirationYear.options.length = 0;
var date = new Date();
var year = parseInt(date.getFullYear());
for (var i = 0; i < 10; ++i) {
var text = year + i;
var option = document.createElement('option');
option.text = text;
option.value = text;
expirationYear.add(option, null);
}
},
/**
* Clears the value of each input field.
* @private
*/
clearInputFields_: function() {
$('name-on-card').value = '';
$('credit-card-number').value = '';
$('expiration-month').selectedIndex = 0;
$('expiration-year').selectedIndex = 0;
// Reset the enabled status of the 'Ok' button.
this.inputFieldChanged_();
},
/**
* Sets the value of each input field according to |creditCard|
* @private
*/
setInputFields_: function(creditCard) {
$('name-on-card').value = creditCard['nameOnCard'];
$('credit-card-number').value = creditCard['creditCardNumber'];
// The options for the year select control may be out-dated at this point,
// e.g. the user opened the options page before midnight on New Year's Eve
// and then loaded a credit card profile to edit in the new year, so
// reload the select options just to be safe.
this.setDefaultSelectOptions_();
var idx = parseInt(creditCard['expirationMonth'], 10);
$('expiration-month').selectedIndex = idx - 1;
expYear = creditCard['expirationYear'];
var date = new Date();
var year = parseInt(date.getFullYear());
for (var i = 0; i < 10; ++i) {
var text = year + i;
if (expYear == String(text))
$('expiration-year').selectedIndex = i;
}
},
/**
* Loads the credit card data from |creditCard|, sets the input fields based
* on this data and stores the GUID of the credit card.
* @private
*/
loadCreditCard_: function(creditCard) {
this.setInputFields_(creditCard);
this.inputFieldChanged_();
this.guid_ = creditCard['guid'];
},
};
AutofillEditCreditCardOverlay.clearInputFields = function(title) {
AutofillEditCreditCardOverlay.getInstance().clearInputFields_();
};
AutofillEditCreditCardOverlay.loadCreditCard = function(creditCard) {
AutofillEditCreditCardOverlay.getInstance().loadCreditCard_(creditCard);
};
AutofillEditCreditCardOverlay.setTitle = function(title) {
$('autofill-credit-card-title').textContent = title;
};
// Export
return {
AutofillEditCreditCardOverlay: AutofillEditCreditCardOverlay
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.autofillOptions', function() {
const DeletableItem = options.DeletableItem;
const DeletableItemList = options.DeletableItemList;
const InlineEditableItem = options.InlineEditableItem;
const InlineEditableItemList = options.InlineEditableItemList;
function AutofillEditProfileButton(guid, edit) {
var editButtonEl = document.createElement('button');
editButtonEl.className = 'raw-button custom-appearance';
editButtonEl.textContent =
templateData.autofillEditProfileButton;
editButtonEl.onclick = function(e) { edit(guid); };
// Don't select the row when clicking the button.
editButtonEl.onmousedown = function(e) {
e.stopPropagation();
};
return editButtonEl;
}
/**
* Creates a new address list item.
* @param {Array} entry An array of the form [guid, label].
* @constructor
* @extends {options.DeletableItem}
*/
function AddressListItem(entry) {
var el = cr.doc.createElement('div');
el.guid = entry[0];
el.label = entry[1];
el.__proto__ = AddressListItem.prototype;
el.decorate();
return el;
}
AddressListItem.prototype = {
__proto__: DeletableItem.prototype,
/** @inheritDoc */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
// The stored label.
var label = this.ownerDocument.createElement('div');
label.className = 'autofill-list-item';
label.textContent = this.label;
this.contentElement.appendChild(label);
// The 'Edit' button.
var editButtonEl = new AutofillEditProfileButton(
this.guid,
AutofillOptions.loadAddressEditor);
this.contentElement.appendChild(editButtonEl);
},
};
/**
* Creates a new credit card list item.
* @param {Array} entry An array of the form [guid, label, icon].
* @constructor
* @extends {options.DeletableItem}
*/
function CreditCardListItem(entry) {
var el = cr.doc.createElement('div');
el.guid = entry[0];
el.label = entry[1];
el.icon = entry[2];
el.description = entry[3];
el.__proto__ = CreditCardListItem.prototype;
el.decorate();
return el;
}
CreditCardListItem.prototype = {
__proto__: DeletableItem.prototype,
/** @inheritDoc */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
// The stored label.
var label = this.ownerDocument.createElement('div');
label.className = 'autofill-list-item';
label.textContent = this.label;
this.contentElement.appendChild(label);
// The credit card icon.
var icon = this.ownerDocument.createElement('image');
icon.src = this.icon;
icon.alt = this.description;
this.contentElement.appendChild(icon);
// The 'Edit' button.
var editButtonEl = new AutofillEditProfileButton(
this.guid,
AutofillOptions.loadCreditCardEditor);
this.contentElement.appendChild(editButtonEl);
},
};
/**
* Creates a new value list item.
* @param {AutofillValuesList} list The parent list of this item.
* @param {String} entry A string value.
* @constructor
* @extends {options.InlineEditableItem}
*/
function ValuesListItem(list, entry) {
var el = cr.doc.createElement('div');
el.list = list;
el.value = entry ? entry : '';
el.__proto__ = ValuesListItem.prototype;
el.decorate();
return el;
}
ValuesListItem.prototype = {
__proto__: InlineEditableItem.prototype,
/** @inheritDoc */
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
// Note: This must be set prior to calling |createEditableTextCell|.
this.isPlaceholder = !this.value;
// The stored value.
var cell = this.createEditableTextCell(this.value);
this.contentElement.appendChild(cell);
this.input = cell.querySelector('input');
if (this.isPlaceholder) {
this.input.placeholder = this.list.getAttribute('placeholder');
this.deletable = false;
}
this.addEventListener('commitedit', this.onEditCommitted_);
},
/**
* @return This item's value.
* @protected
*/
value_: function() {
return this.input.value;
},
/**
* @param {Object} value The value to test.
* @return true if the given value is non-empty.
* @protected
*/
valueIsNonEmpty_: function(value) {
return !!value;
},
/**
* @return true if value1 is logically equal to value2.
*/
valuesAreEqual_: function(value1, value2) {
return value1 === value2;
},
/**
* Clears the item's value.
* @protected
*/
clearValue_: function() {
this.input.value = '';
},
/**
* Called when committing an edit.
* If this is an "Add ..." item, committing a non-empty value adds that
* value to the end of the values list, but also leaves this "Add ..." item
* in place.
* @param {Event} e The end event.
* @private
*/
onEditCommitted_: function(e) {
var value = this.value_();
var i = this.list.items.indexOf(this);
if (i < this.list.dataModel.length &&
this.valuesAreEqual_(value, this.list.dataModel.item(i))) {
return;
}
var entries = this.list.dataModel.slice();
if (this.valueIsNonEmpty_(value) &&
!entries.some(this.valuesAreEqual_.bind(this, value))) {
// Update with new value.
if (this.isPlaceholder) {
// It is important that updateIndex is done before validateAndSave.
// Otherwise we can not be sure about AddRow index.
this.list.dataModel.updateIndex(i);
this.list.validateAndSave(i, 0, value);
} else {
this.list.validateAndSave(i, 1, value);
}
} else {
// Reject empty values and duplicates.
if (!this.isPlaceholder)
this.list.dataModel.splice(i, 1);
else
this.clearValue_();
}
},
};
/**
* Creates a new name value list item.
* @param {AutofillNameValuesList} list The parent list of this item.
* @param {array} entry An array of [first, middle, last] names.
* @constructor
* @extends {options.ValuesListItem}
*/
function NameListItem(list, entry) {
var el = cr.doc.createElement('div');
el.list = list;
el.first = entry ? entry[0] : '';
el.middle = entry ? entry[1] : '';
el.last = entry ? entry[2] : '';
el.__proto__ = NameListItem.prototype;
el.decorate();
return el;
}
NameListItem.prototype = {
__proto__: ValuesListItem.prototype,
/** @inheritDoc */
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
// Note: This must be set prior to calling |createEditableTextCell|.
this.isPlaceholder = !this.first && !this.middle && !this.last;
// The stored value.
// For the simulated static "input element" to display correctly, the
// value must not be empty. We use a space to force the UI to render
// correctly when the value is logically empty.
var cell = this.createEditableTextCell(this.first);
this.contentElement.appendChild(cell);
this.firstNameInput = cell.querySelector('input');
cell = this.createEditableTextCell(this.middle);
this.contentElement.appendChild(cell);
this.middleNameInput = cell.querySelector('input');
cell = this.createEditableTextCell(this.last);
this.contentElement.appendChild(cell);
this.lastNameInput = cell.querySelector('input');
if (this.isPlaceholder) {
this.firstNameInput.placeholder =
templateData.autofillAddFirstNamePlaceholder;
this.middleNameInput.placeholder =
templateData.autofillAddMiddleNamePlaceholder;
this.lastNameInput.placeholder =
templateData.autofillAddLastNamePlaceholder;
this.deletable = false;
}
this.addEventListener('commitedit', this.onEditCommitted_);
},
/** @inheritDoc */
value_: function() {
return [ this.firstNameInput.value,
this.middleNameInput.value,
this.lastNameInput.value ];
},
/** @inheritDoc */
valueIsNonEmpty_: function(value) {
return value[0] || value[1] || value[2];
},
/** @inheritDoc */
valuesAreEqual_: function(value1, value2) {
// First, check for null values.
if (!value1 || !value2)
return value1 == value2;
return value1[0] === value2[0] &&
value1[1] === value2[1] &&
value1[2] === value2[2];
},
/** @inheritDoc */
clearValue_: function() {
this.firstNameInput.value = '';
this.middleNameInput.value = '';
this.lastNameInput.value = '';
},
};
/**
* Base class for shared implementation between address and credit card lists.
* @constructor
* @extends {options.DeletableItemList}
*/
var AutofillProfileList = cr.ui.define('list');
AutofillProfileList.prototype = {
__proto__: DeletableItemList.prototype,
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
this.addEventListener('blur', this.onBlur_);
},
/**
* When the list loses focus, unselect all items in the list.
* @private
*/
onBlur_: function() {
this.selectionModel.unselectAll();
},
};
/**
* Create a new address list.
* @constructor
* @extends {options.AutofillProfileList}
*/
var AutofillAddressList = cr.ui.define('list');
AutofillAddressList.prototype = {
__proto__: AutofillProfileList.prototype,
decorate: function() {
AutofillProfileList.prototype.decorate.call(this);
},
/** @inheritDoc */
activateItemAtIndex: function(index) {
AutofillOptions.loadAddressEditor(this.dataModel.item(index)[0]);
},
/** @inheritDoc */
createItem: function(entry) {
return new AddressListItem(entry);
},
/** @inheritDoc */
deleteItemAtIndex: function(index) {
AutofillOptions.removeAddress(this.dataModel.item(index)[0]);
},
};
/**
* Create a new credit card list.
* @constructor
* @extends {options.DeletableItemList}
*/
var AutofillCreditCardList = cr.ui.define('list');
AutofillCreditCardList.prototype = {
__proto__: AutofillProfileList.prototype,
decorate: function() {
AutofillProfileList.prototype.decorate.call(this);
},
/** @inheritDoc */
activateItemAtIndex: function(index) {
AutofillOptions.loadCreditCardEditor(this.dataModel.item(index)[0]);
},
/** @inheritDoc */
createItem: function(entry) {
return new CreditCardListItem(entry);
},
/** @inheritDoc */
deleteItemAtIndex: function(index) {
AutofillOptions.removeCreditCard(this.dataModel.item(index)[0]);
},
};
/**
* Create a new value list.
* @constructor
* @extends {options.InlineEditableItemList}
*/
var AutofillValuesList = cr.ui.define('list');
AutofillValuesList.prototype = {
__proto__: InlineEditableItemList.prototype,
/** @inheritDoc */
createItem: function(entry) {
return new ValuesListItem(this, entry);
},
/** @inheritDoc */
deleteItemAtIndex: function(index) {
this.dataModel.splice(index, 1);
},
/** @inheritDoc */
shouldFocusPlaceholder: function() {
return false;
},
/**
* Called when the list hierarchy as a whole loses or gains focus.
* If the list was focused in response to a mouse click, call into the
* superclass's implementation. If the list was focused in response to a
* keyboard navigation, focus the first item.
* If the list loses focus, unselect all the elements.
* @param {Event} e The change event.
* @private
*/
handleListFocusChange_: function(e) {
// We check to see whether there is a selected item as a proxy for
// distinguishing between mouse- and keyboard-originated focus events.
var selectedItem = this.selectedItem;
if (selectedItem)
InlineEditableItemList.prototype.handleListFocusChange_.call(this, e);
if (!e.newValue) {
// When the list loses focus, unselect all the elements.
this.selectionModel.unselectAll();
} else {
// When the list gains focus, select the first item if nothing else is
// selected.
var firstItem = this.getListItemByIndex(0);
if (!selectedItem && firstItem && e.newValue)
firstItem.handleFocus_();
}
},
/**
* Called when a new list item should be validated; subclasses are
* responsible for implementing if validation is required.
* @param {number} index The index of the item that was inserted or changed.
* @param {number} remove The number items to remove.
* @param {string} value The value of the item to insert.
*/
validateAndSave: function(index, remove, value) {
this.dataModel.splice(index, remove, value);
},
};
/**
* Create a new value list for phone number validation.
* @constructor
* @extends {options.AutofillValuesList}
*/
var AutofillNameValuesList = cr.ui.define('list');
AutofillNameValuesList.prototype = {
__proto__: AutofillValuesList.prototype,
/** @inheritDoc */
createItem: function(entry) {
return new NameListItem(this, entry);
},
};
/**
* Create a new value list for phone number validation.
* @constructor
* @extends {options.AutofillValuesList}
*/
var AutofillPhoneValuesList = cr.ui.define('list');
AutofillPhoneValuesList.prototype = {
__proto__: AutofillValuesList.prototype,
/** @inheritDoc */
validateAndSave: function(index, remove, value) {
var numbers = this.dataModel.slice(0, this.dataModel.length - 1);
numbers.splice(index, remove, value);
var info = new Array();
info[0] = index;
info[1] = numbers;
info[2] = $('country').value;
chrome.send('validatePhoneNumbers', info);
},
};
return {
AddressListItem: AddressListItem,
CreditCardListItem: CreditCardListItem,
ValuesListItem: ValuesListItem,
NameListItem: NameListItem,
AutofillAddressList: AutofillAddressList,
AutofillCreditCardList: AutofillCreditCardList,
AutofillValuesList: AutofillValuesList,
AutofillNameValuesList: AutofillNameValuesList,
AutofillPhoneValuesList: AutofillPhoneValuesList,
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
const ArrayDataModel = cr.ui.ArrayDataModel;
/////////////////////////////////////////////////////////////////////////////
// AutofillOptions class:
/**
* Encapsulated handling of Autofill options page.
* @constructor
*/
function AutofillOptions() {
OptionsPage.call(this,
'autofill',
templateData.autofillOptionsPageTabTitle,
'autofill-options');
}
cr.addSingletonGetter(AutofillOptions);
AutofillOptions.prototype = {
__proto__: OptionsPage.prototype,
/**
* The address list.
* @type {DeletableItemList}
* @private
*/
addressList_: null,
/**
* The credit card list.
* @type {DeletableItemList}
* @private
*/
creditCardList_: null,
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
this.createAddressList_();
this.createCreditCardList_();
var self = this;
$('autofill-add-address').onclick = function(event) {
self.showAddAddressOverlay_();
};
$('autofill-add-creditcard').onclick = function(event) {
self.showAddCreditCardOverlay_();
};
// TODO(jhawkins): What happens when Autofill is disabled whilst on the
// Autofill options page?
},
/**
* Creates, decorates and initializes the address list.
* @private
*/
createAddressList_: function() {
this.addressList_ = $('address-list');
options.autofillOptions.AutofillAddressList.decorate(this.addressList_);
this.addressList_.autoExpands = true;
},
/**
* Creates, decorates and initializes the credit card list.
* @private
*/
createCreditCardList_: function() {
this.creditCardList_ = $('creditcard-list');
options.autofillOptions.AutofillCreditCardList.decorate(
this.creditCardList_);
this.creditCardList_.autoExpands = true;
},
/**
* Shows the 'Add address' overlay, specifically by loading the
* 'Edit address' overlay, emptying the input fields and modifying the
* overlay title.
* @private
*/
showAddAddressOverlay_: function() {
var title = localStrings.getString('addAddressTitle');
AutofillEditAddressOverlay.setTitle(title);
AutofillEditAddressOverlay.clearInputFields();
OptionsPage.navigateToPage('autofillEditAddress');
},
/**
* Shows the 'Add credit card' overlay, specifically by loading the
* 'Edit credit card' overlay, emptying the input fields and modifying the
* overlay title.
* @private
*/
showAddCreditCardOverlay_: function() {
var title = localStrings.getString('addCreditCardTitle');
AutofillEditCreditCardOverlay.setTitle(title);
AutofillEditCreditCardOverlay.clearInputFields();
OptionsPage.navigateToPage('autofillEditCreditCard');
},
/**
* Updates the data model for the address list with the values from
* |entries|.
* @param {Array} entries The list of addresses.
*/
setAddressList_: function(entries) {
this.addressList_.dataModel = new ArrayDataModel(entries);
},
/**
* Updates the data model for the credit card list with the values from
* |entries|.
* @param {Array} entries The list of credit cards.
*/
setCreditCardList_: function(entries) {
this.creditCardList_.dataModel = new ArrayDataModel(entries);
},
/**
* Removes the Autofill address represented by |guid|.
* @param {String} guid The GUID of the address to remove.
* @private
*/
removeAddress_: function(guid) {
chrome.send('removeAddress', [guid]);
},
/**
* Removes the Autofill credit card represented by |guid|.
* @param {String} guid The GUID of the credit card to remove.
* @private
*/
removeCreditCard_: function(guid) {
chrome.send('removeCreditCard', [guid]);
},
/**
* Requests profile data for the address represented by |guid| from the
* PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler
* calls showEditAddressOverlay().
* @param {String} guid The GUID of the address to edit.
* @private
*/
loadAddressEditor_: function(guid) {
chrome.send('loadAddressEditor', [guid]);
},
/**
* Requests profile data for the credit card represented by |guid| from the
* PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler
* calls showEditCreditCardOverlay().
* @param {String} guid The GUID of the credit card to edit.
* @private
*/
loadCreditCardEditor_: function(guid) {
chrome.send('loadCreditCardEditor', [guid]);
},
/**
* Shows the 'Edit address' overlay, using the data in |address| to fill the
* input fields. |address| is a list with one item, an associative array
* that contains the address data.
* @private
*/
showEditAddressOverlay_: function(address) {
var title = localStrings.getString('editAddressTitle');
AutofillEditAddressOverlay.setTitle(title);
AutofillEditAddressOverlay.loadAddress(address);
OptionsPage.navigateToPage('autofillEditAddress');
},
/**
* Shows the 'Edit credit card' overlay, using the data in |credit_card| to
* fill the input fields. |address| is a list with one item, an associative
* array that contains the credit card data.
* @private
*/
showEditCreditCardOverlay_: function(creditCard) {
var title = localStrings.getString('editCreditCardTitle');
AutofillEditCreditCardOverlay.setTitle(title);
AutofillEditCreditCardOverlay.loadCreditCard(creditCard);
OptionsPage.navigateToPage('autofillEditCreditCard');
},
};
AutofillOptions.setAddressList = function(entries) {
AutofillOptions.getInstance().setAddressList_(entries);
};
AutofillOptions.setCreditCardList = function(entries) {
AutofillOptions.getInstance().setCreditCardList_(entries);
};
AutofillOptions.removeAddress = function(guid) {
AutofillOptions.getInstance().removeAddress_(guid);
};
AutofillOptions.removeCreditCard = function(guid) {
AutofillOptions.getInstance().removeCreditCard_(guid);
};
AutofillOptions.loadAddressEditor = function(guid) {
AutofillOptions.getInstance().loadAddressEditor_(guid);
};
AutofillOptions.loadCreditCardEditor = function(guid) {
AutofillOptions.getInstance().loadCreditCardEditor_(guid);
};
AutofillOptions.editAddress = function(address) {
AutofillOptions.getInstance().showEditAddressOverlay_(address);
};
AutofillOptions.editCreditCard = function(creditCard) {
AutofillOptions.getInstance().showEditCreditCardOverlay_(creditCard);
};
// Export
return {
AutofillOptions: AutofillOptions
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
const ArrayDataModel = cr.ui.ArrayDataModel;
//
// BrowserOptions class
// Encapsulated handling of browser options page.
//
function BrowserOptions() {
OptionsPage.call(this, 'browser',
templateData.browserPageTabTitle,
'browserPage');
}
cr.addSingletonGetter(BrowserOptions);
BrowserOptions.prototype = {
// Inherit BrowserOptions from OptionsPage.
__proto__: options.OptionsPage.prototype,
startup_pages_pref_: {
'name': 'session.urls_to_restore_on_startup',
'disabled': false
},
/**
* At autocomplete list that can be attached to a text field during editing.
* @type {HTMLElement}
* @private
*/
autocompleteList_: null,
// The cached value of the instant.confirm_dialog_shown preference.
instantConfirmDialogShown_: false,
/**
* Initialize BrowserOptions page.
*/
initializePage: function() {
// Call base class implementation to start preference initialization.
OptionsPage.prototype.initializePage.call(this);
// Wire up controls.
$('startupUseCurrentButton').onclick = function(event) {
chrome.send('setStartupPagesToCurrentPages');
};
$('defaultSearchManageEnginesButton').onclick = function(event) {
OptionsPage.navigateToPage('searchEngines');
chrome.send('coreOptionsUserMetricsAction',
['Options_ManageSearchEngines']);
};
$('defaultSearchEngine').onchange = this.setDefaultSearchEngine_;
var self = this;
$('instantEnabledCheckbox').customChangeHandler = function(event) {
if (this.checked) {
if (self.instantConfirmDialogShown_)
chrome.send('enableInstant');
else
OptionsPage.navigateToPage('instantConfirm');
} else {
chrome.send('disableInstant');
}
return true;
};
$('instantFieldTrialCheckbox').addEventListener('change',
function(event) {
this.checked = true;
chrome.send('disableInstant');
});
Preferences.getInstance().addEventListener('instant.confirm_dialog_shown',
this.onInstantConfirmDialogShownChanged_.bind(this));
Preferences.getInstance().addEventListener('instant.enabled',
this.onInstantEnabledChanged_.bind(this));
Preferences.getInstance().addEventListener(
$('homepageUseNTPButton').pref,
this.onHomepageUseNTPChanged_);
var homepageField = $('homepageURL');
homepageField.addEventListener('focus', function(event) {
self.autocompleteList_.attachToInput(homepageField);
});
homepageField.addEventListener('blur', function(event) {
self.autocompleteList_.detach();
});
homepageField.addEventListener('keydown', function(event) {
// Remove focus when the user hits enter since people expect feedback
// indicating that they are done editing.
if (event.keyIdentifier == 'Enter')
homepageField.blur();
});
// Text fields may change widths when the window changes size, so make
// sure the suggestion list stays in sync.
window.addEventListener('resize', function() {
self.autocompleteList_.syncWidthToInput();
});
// Ensure that changes are committed when closing the page.
window.addEventListener('unload', function() {
if (document.activeElement == homepageField)
homepageField.blur();
});
if (!cr.isChromeOS) {
$('defaultBrowserUseAsDefaultButton').onclick = function(event) {
chrome.send('becomeDefaultBrowser');
};
}
var startupPagesList = $('startupPagesList');
options.browser_options.StartupPageList.decorate(startupPagesList);
startupPagesList.autoExpands = true;
// Check if we are in the guest mode.
if (cr.commandLine.options['--bwsi']) {
// Hide the startup section.
$('startupSection').hidden = true;
} else {
// Initialize control enabled states.
Preferences.getInstance().addEventListener('session.restore_on_startup',
this.updateCustomStartupPageControlStates_.bind(this));
Preferences.getInstance().addEventListener(
this.startup_pages_pref_.name,
this.handleStartupPageListChange_.bind(this));
this.updateCustomStartupPageControlStates_();
}
var suggestionList = new options.AutocompleteList();
suggestionList.autoExpands = true;
suggestionList.suggestionUpdateRequestCallback =
this.requestAutocompleteSuggestions_.bind(this);
$('main-content').appendChild(suggestionList);
this.autocompleteList_ = suggestionList;
startupPagesList.autocompleteList = suggestionList;
},
/**
* Called when the value of the instant.confirm_dialog_shown preference
* changes. Cache this value.
* @param {Event} event Change event.
* @private
*/
onInstantConfirmDialogShownChanged_: function(event) {
this.instantConfirmDialogShown_ = event.value['value'];
},
/**
* Called when the value of the instant.enabled preference changes. Request
* the state of the Instant field trial experiment.
* @param {Event} event Change event.
* @private
*/
onInstantEnabledChanged_: function(event) {
chrome.send('getInstantFieldTrialStatus');
},
/**
* Called to set the Instant field trial status.
* @param {boolean} enabled If true, the experiment is enabled.
* @private
*/
setInstantFieldTrialStatus_: function(enabled) {
$('instantEnabledCheckbox').hidden = enabled;
$('instantFieldTrialCheckbox').hidden = !enabled;
$('instantLabel').htmlFor = enabled ? 'instantFieldTrialCheckbox'
: 'instantEnabledCheckbox';
},
/**
* Called when the value of the homepage-use-NTP pref changes.
* Updates the disabled state of the homepage text field.
* Notice that the text field can be disabled for other reasons too
* (it can be managed by policy, for instance).
* @param {Event} event Change event.
* @private
*/
onHomepageUseNTPChanged_: function(event) {
var homepageField = $('homepageURL');
var homepageUseURLButton = $('homepageUseURLButton');
homepageField.setDisabled('radioNotSelected',
!homepageUseURLButton.checked);
},
/**
* Update the Default Browsers section based on the current state.
* @param {string} statusString Description of the current default state.
* @param {boolean} isDefault Whether or not the browser is currently
* default.
* @param {boolean} canBeDefault Whether or not the browser can be default.
* @private
*/
updateDefaultBrowserState_: function(statusString, isDefault,
canBeDefault) {
var label = $('defaultBrowserState');
label.textContent = statusString;
$('defaultBrowserUseAsDefaultButton').disabled = !canBeDefault ||
isDefault;
},
/**
* Clears the search engine popup.
* @private
*/
clearSearchEngines_: function() {
$('defaultSearchEngine').textContent = '';
},
/**
* Updates the search engine popup with the given entries.
* @param {Array} engines List of available search engines.
* @param {number} defaultValue The value of the current default engine.
* @param {boolean} defaultManaged Whether the default search provider is
* managed. If true, the default search provider can't be changed.
*/
updateSearchEngines_: function(engines, defaultValue, defaultManaged) {
this.clearSearchEngines_();
engineSelect = $('defaultSearchEngine');
engineSelect.disabled = defaultManaged;
engineCount = engines.length;
var defaultIndex = -1;
for (var i = 0; i < engineCount; i++) {
var engine = engines[i];
var option = new Option(engine['name'], engine['index']);
if (defaultValue == option.value)
defaultIndex = i;
engineSelect.appendChild(option);
}
if (defaultIndex >= 0)
engineSelect.selectedIndex = defaultIndex;
},
/**
* Returns true if the custom startup page control block should
* be enabled.
* @returns {boolean} Whether the startup page controls should be
* enabled.
*/
shouldEnableCustomStartupPageControls: function(pages) {
return $('startupShowPagesButton').checked &&
!this.startup_pages_pref_.disabled;
},
/**
* Updates the startup pages list with the given entries.
* @param {Array} pages List of startup pages.
* @private
*/
updateStartupPages_: function(pages) {
var model = new ArrayDataModel(pages);
// Add a "new page" row.
model.push({
'modelIndex': '-1'
});
$('startupPagesList').dataModel = model;
},
/**
* Sets the enabled state of the custom startup page list controls
* based on the current startup radio button selection.
* @private
*/
updateCustomStartupPageControlStates_: function() {
var disable = !this.shouldEnableCustomStartupPageControls();
var startupPagesList = $('startupPagesList');
startupPagesList.disabled = disable;
// Explicitly set disabled state for input text elements.
var inputs = startupPagesList.querySelectorAll("input[type='text']");
for (var i = 0; i < inputs.length; i++)
inputs[i].disabled = disable;
$('startupUseCurrentButton').disabled = disable;
},
/**
* Handle change events of the preference
* 'session.urls_to_restore_on_startup'.
* @param {event} preference changed event.
* @private
*/
handleStartupPageListChange_: function(event) {
this.startup_pages_pref_.disabled = event.value['disabled'];
this.updateCustomStartupPageControlStates_();
},
/**
* Set the default search engine based on the popup selection.
*/
setDefaultSearchEngine_: function() {
var engineSelect = $('defaultSearchEngine');
var selectedIndex = engineSelect.selectedIndex;
if (selectedIndex >= 0) {
var selection = engineSelect.options[selectedIndex];
chrome.send('setDefaultSearchEngine', [String(selection.value)]);
}
},
/**
* Sends an asynchronous request for new autocompletion suggestions for the
* the given query. When new suggestions are available, the C++ handler will
* call updateAutocompleteSuggestions_.
* @param {string} query List of autocomplete suggestions.
* @private
*/
requestAutocompleteSuggestions_: function(query) {
chrome.send('requestAutocompleteSuggestions', [query]);
},
/**
* Updates the autocomplete suggestion list with the given entries.
* @param {Array} pages List of autocomplete suggestions.
* @private
*/
updateAutocompleteSuggestions_: function(suggestions) {
var list = this.autocompleteList_;
// If the trigger for this update was a value being selected from the
// current list, do nothing.
if (list.targetInput && list.selectedItem &&
list.selectedItem['url'] == list.targetInput.value)
return;
list.suggestions = suggestions;
},
};
BrowserOptions.updateDefaultBrowserState = function(statusString, isDefault,
canBeDefault) {
if (!cr.isChromeOS) {
BrowserOptions.getInstance().updateDefaultBrowserState_(statusString,
isDefault,
canBeDefault);
}
};
BrowserOptions.updateSearchEngines = function(engines, defaultValue,
defaultManaged) {
BrowserOptions.getInstance().updateSearchEngines_(engines, defaultValue,
defaultManaged);
};
BrowserOptions.updateStartupPages = function(pages) {
BrowserOptions.getInstance().updateStartupPages_(pages);
};
BrowserOptions.updateAutocompleteSuggestions = function(suggestions) {
BrowserOptions.getInstance().updateAutocompleteSuggestions_(suggestions);
};
BrowserOptions.setInstantFieldTrialStatus = function(enabled) {
BrowserOptions.getInstance().setInstantFieldTrialStatus_(enabled);
};
// Export
return {
BrowserOptions: BrowserOptions
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.browser_options', function() {
const AutocompleteList = options.AutocompleteList;
const InlineEditableItem = options.InlineEditableItem;
const InlineEditableItemList = options.InlineEditableItemList;
/**
* Creates a new startup page list item.
* @param {Object} pageInfo The page this item represents.
* @constructor
* @extends {cr.ui.ListItem}
*/
function StartupPageListItem(pageInfo) {
var el = cr.doc.createElement('div');
el.pageInfo_ = pageInfo;
StartupPageListItem.decorate(el);
return el;
}
/**
* Decorates an element as a startup page list item.
* @param {!HTMLElement} el The element to decorate.
*/
StartupPageListItem.decorate = function(el) {
el.__proto__ = StartupPageListItem.prototype;
el.decorate();
};
StartupPageListItem.prototype = {
__proto__: InlineEditableItem.prototype,
/**
* Input field for editing the page url.
* @type {HTMLElement}
* @private
*/
urlField_: null,
/** @inheritDoc */
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
var pageInfo = this.pageInfo_;
if (pageInfo['modelIndex'] == '-1') {
this.isPlaceholder = true;
pageInfo['title'] = localStrings.getString('startupAddLabel');
pageInfo['url'] = '';
}
var titleEl = this.ownerDocument.createElement('div');
titleEl.className = 'title';
titleEl.classList.add('favicon-cell');
titleEl.classList.add('weakrtl');
titleEl.textContent = pageInfo['title'];
if (!this.isPlaceholder) {
titleEl.style.backgroundImage = url('chrome://favicon/' +
pageInfo['url']);
titleEl.title = pageInfo['tooltip'];
}
this.contentElement.appendChild(titleEl);
var urlEl = this.createEditableTextCell(pageInfo['url']);
urlEl.className = 'url';
urlEl.classList.add('weakrtl');
this.contentElement.appendChild(urlEl);
var urlField = urlEl.querySelector('input')
urlField.required = true;
urlField.className = 'weakrtl';
this.urlField_ = urlField;
this.addEventListener('commitedit', this.onEditCommitted_);
var self = this;
urlField.addEventListener('focus', function(event) {
self.parentNode.autocompleteList.attachToInput(urlField);
});
urlField.addEventListener('blur', function(event) {
self.parentNode.autocompleteList.detach();
});
if (!this.isPlaceholder)
this.draggable = true;
},
/** @inheritDoc */
get currentInputIsValid() {
return this.urlField_.validity.valid;
},
/** @inheritDoc */
get hasBeenEdited() {
return this.urlField_.value != this.pageInfo_['url'];
},
/**
* Called when committing an edit; updates the model.
* @param {Event} e The end event.
* @private
*/
onEditCommitted_: function(e) {
var url = this.urlField_.value;
if (this.isPlaceholder)
chrome.send('addStartupPage', [url]);
else
chrome.send('editStartupPage', [this.pageInfo_['modelIndex'], url]);
},
};
var StartupPageList = cr.ui.define('list');
StartupPageList.prototype = {
__proto__: InlineEditableItemList.prototype,
/**
* An autocomplete suggestion list for URL editing.
* @type {AutocompleteList}
*/
autocompleteList: null,
/**
* The drop position information: "below" or "above".
*/
dropPos: null,
/** @inheritDoc */
decorate: function() {
InlineEditableItemList.prototype.decorate.call(this);
// Listen to drag and drop events.
this.addEventListener('dragstart', this.handleDragStart_.bind(this));
this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
this.addEventListener('dragover', this.handleDragOver_.bind(this));
this.addEventListener('drop', this.handleDrop_.bind(this));
this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
this.addEventListener('dragend', this.handleDragEnd_.bind(this));
},
/** @inheritDoc */
createItem: function(pageInfo) {
var item = new StartupPageListItem(pageInfo);
item.urlField_.disabled = this.disabled;
return item;
},
/** @inheritDoc */
deleteItemAtIndex: function(index) {
chrome.send('removeStartupPages', [String(index)]);
},
/*
* Computes the target item of drop event.
* @param {Event} e The drop or dragover event.
* @private
*/
getTargetFromDropEvent_ : function(e) {
var target = e.target;
// e.target may be an inner element of the list item
while (target != null && !(target instanceof StartupPageListItem)) {
target = target.parentNode;
}
return target;
},
/*
* Handles the dragstart event.
* @param {Event} e The dragstart event.
* @private
*/
handleDragStart_: function(e) {
// Prevent dragging if the list is disabled.
if (this.disabled) {
e.preventDefault();
return false;
}
var target = e.target;
// StartupPageListItem should be the only draggable element type in the
// page but let's make sure.
if (target instanceof StartupPageListItem) {
this.draggedItem = target;
this.draggedItem.editable = false;
e.dataTransfer.effectAllowed = 'move';
// We need to put some kind of data in the drag or it will be
// ignored. Use the URL in case the user drags to a text field or the
// desktop.
e.dataTransfer.setData('text/plain', target.urlField_.value);
}
},
/*
* Handles the dragenter event.
* @param {Event} e The dragenter event.
* @private
*/
handleDragEnter_: function(e) {
e.preventDefault();
},
/*
* Handles the dragover event.
* @param {Event} e The dragover event.
* @private
*/
handleDragOver_: function(e) {
var dropTarget = this.getTargetFromDropEvent_(e);
// Determines whether the drop target is to accept the drop.
// The drop is only successful on another StartupPageListItem.
if (!(dropTarget instanceof StartupPageListItem) ||
dropTarget == this.draggedItem || dropTarget.isPlaceholder) {
this.hideDropMarker_();
return;
}
// Compute the drop postion. Should we move the dragged item to
// below or above the drop target?
var rect = dropTarget.getBoundingClientRect();
var dy = e.clientY - rect.top;
var yRatio = dy / rect.height;
var dropPos = yRatio <= .5 ? 'above' : 'below';
this.dropPos = dropPos;
this.showDropMarker_(dropTarget, dropPos);
e.preventDefault();
},
/*
* Handles the drop event.
* @param {Event} e The drop event.
* @private
*/
handleDrop_: function(e) {
var dropTarget = this.getTargetFromDropEvent_(e);
this.hideDropMarker_();
// Insert the selection at the new position.
var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_);
if (this.dropPos == 'below')
newIndex += 1;
var selected = this.selectionModel.selectedIndexes;
var stringized_selected = [];
for (var j = 0; j < selected.length; j++)
stringized_selected.push(String(selected[j]));
chrome.send('dragDropStartupPage',
[String(newIndex), stringized_selected] );
},
/*
* Handles the dragleave event.
* @param {Event} e The dragleave event
* @private
*/
handleDragLeave_: function(e) {
this.hideDropMarker_();
},
/**
* Handles the dragend event.
* @param {Event} e The dragend event
* @private
*/
handleDragEnd_: function(e) {
this.draggedItem.editable = true;
this.draggedItem.updateEditState();
},
/*
* Shows and positions the marker to indicate the drop target.
* @param {HTMLElement} target The current target list item of drop
* @param {string} pos 'below' or 'above'
* @private
*/
showDropMarker_ : function(target, pos) {
window.clearTimeout(this.hideDropMarkerTimer_);
var marker = $('startupPagesListDropmarker');
var rect = target.getBoundingClientRect();
var markerHeight = 6;
if (pos == 'above') {
marker.style.top = (rect.top - markerHeight/2) + 'px';
} else {
marker.style.top = (rect.bottom - markerHeight/2) + 'px';
}
marker.style.width = rect.width + 'px';
marker.style.left = rect.left + 'px';
marker.style.display = 'block';
},
/*
* Hides the drop marker.
* @private
*/
hideDropMarker_ : function() {
// Hide the marker in a timeout to reduce flickering as we move between
// valid drop targets.
window.clearTimeout(this.hideDropMarkerTimer_);
this.hideDropMarkerTimer_ = window.setTimeout(function() {
$('startupPagesListDropmarker').style.display = '';
}, 100);
},
};
return {
StartupPageList: StartupPageList
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/**
* ClearBrowserDataOverlay class
* Encapsulated handling of the 'Clear Browser Data' overlay page.
* @class
*/
function ClearBrowserDataOverlay() {
OptionsPage.call(this, 'clearBrowserData',
templateData.clearBrowserDataOverlayTabTitle,
'clearBrowserDataOverlay');
}
cr.addSingletonGetter(ClearBrowserDataOverlay);
ClearBrowserDataOverlay.prototype = {
// Inherit ClearBrowserDataOverlay from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to starts preference initialization.
OptionsPage.prototype.initializePage.call(this);
var f = this.updateCommitButtonState_.bind(this);
var types = ['browser.clear_data.browsing_history',
'browser.clear_data.download_history',
'browser.clear_data.cache',
'browser.clear_data.cookies',
'browser.clear_data.passwords',
'browser.clear_data.form_data'];
types.forEach(function(type) {
Preferences.getInstance().addEventListener(type, f);
});
var checkboxes = document.querySelectorAll(
'#cbdContentArea input[type=checkbox]');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].onclick = f;
}
this.updateCommitButtonState_();
$('clearBrowserDataDismiss').onclick = function(event) {
ClearBrowserDataOverlay.dismiss();
};
$('clearBrowserDataCommit').onclick = function(event) {
chrome.send('performClearBrowserData');
};
},
// Set the enabled state of the commit button.
updateCommitButtonState_: function() {
var checkboxes = document.querySelectorAll(
'#cbdContentArea input[type=checkbox]');
var isChecked = false;
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) {
isChecked = true;
break;
}
}
$('clearBrowserDataCommit').disabled = !isChecked;
},
};
//
// Chrome callbacks
//
ClearBrowserDataOverlay.setClearingState = function(state) {
$('deleteBrowsingHistoryCheckbox').disabled = state;
$('deleteDownloadHistoryCheckbox').disabled = state;
$('deleteCacheCheckbox').disabled = state;
$('deleteCookiesCheckbox').disabled = state;
$('deletePasswordsCheckbox').disabled = state;
$('deleteFormDataCheckbox').disabled = state;
$('clearBrowserDataTimePeriod').disabled = state;
$('cbdThrobber').style.visibility = state ? 'visible' : 'hidden';
if (state)
$('clearBrowserDataCommit').disabled = true;
else
ClearBrowserDataOverlay.getInstance().updateCommitButtonState_();
};
ClearBrowserDataOverlay.doneClearing = function() {
// The delay gives the user some feedback that the clearing
// actually worked. Otherwise the dialog just vanishes instantly in most
// cases.
window.setTimeout(function() {
ClearBrowserDataOverlay.dismiss();
}, 200);
};
ClearBrowserDataOverlay.dismiss = function() {
OptionsPage.closeOverlay();
this.setClearingState(false);
};
// Export
return {
ClearBrowserDataOverlay: ClearBrowserDataOverlay
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
//////////////////////////////////////////////////////////////////////////////
// ContentSettings class:
/**
* Encapsulated handling of content settings page.
* @constructor
*/
function ContentSettings() {
this.activeNavTab = null;
OptionsPage.call(this, 'content', templateData.contentSettingsPageTabTitle,
'content-settings-page');
}
cr.addSingletonGetter(ContentSettings);
ContentSettings.prototype = {
__proto__: OptionsPage.prototype,
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
chrome.send('getContentFilterSettings');
var exceptionsButtons =
this.pageDiv.querySelectorAll('.exceptions-list-button');
for (var i = 0; i < exceptionsButtons.length; i++) {
exceptionsButtons[i].onclick = function(event) {
var page = ContentSettingsExceptionsArea.getInstance();
page.showList(
event.target.getAttribute('contentType'));
OptionsPage.navigateToPage('contentExceptions');
// Add on the proper hash for the content type, and store that in the
// history so back/forward and tab restore works.
var hash = event.target.getAttribute('contentType');
window.history.replaceState({pageName: page.name}, page.title,
'/' + page.name + "#" + hash);
};
}
var manageHandlersButton = $('manage-handlers-button');
if (manageHandlersButton) {
manageHandlersButton.onclick = function(event) {
OptionsPage.navigateToPage('handlers');
};
}
var manageIntentsButton = $('manage-intents-button');
if (manageIntentsButton) {
manageIntentsButton.onclick = function(event) {
OptionsPage.navigateToPage('intents');
};
}
// Cookies filter page ---------------------------------------------------
$('show-cookies-button').onclick = function(event) {
chrome.send('coreOptionsUserMetricsAction', ['Options_ShowCookies']);
OptionsPage.navigateToPage('cookies');
};
if (!templateData.enable_web_intents && $('intent-section'))
$('intent-section').hidden = true;
},
};
ContentSettings.updateHandlersEnabledRadios = function(enabled) {
var selector = '#content-settings-page input[type=radio][value=' +
(enabled ? 'allow' : 'block') + '].handler-radio';
document.querySelector(selector).checked = true;
};
/**
* Sets the values for all the content settings radios.
* @param {Object} dict A mapping from radio groups to the checked value for
* that group.
*/
ContentSettings.setContentFilterSettingsValue = function(dict) {
for (var group in dict) {
document.querySelector('input[type=radio][name=' + group + '][value=' +
dict[group]['value'] + ']').checked = true;
var radios = document.querySelectorAll('input[type=radio][name=' +
group + ']');
var managedBy = dict[group]['managedBy'];
for (var i = 0, len = radios.length; i < len; i++) {
radios[i].disabled = (managedBy != 'default');
radios[i].controlledBy = managedBy;
}
}
OptionsPage.updateManagedBannerVisibility();
};
/**
* Initializes an exceptions list.
* @param {string} type The content type that we are setting exceptions for.
* @param {Array} list An array of pairs, where the first element of each pair
* is the filter string, and the second is the setting (allow/block).
*/
ContentSettings.setExceptions = function(type, list) {
var exceptionsList =
document.querySelector('div[contentType=' + type + ']' +
' list[mode=normal]');
exceptionsList.setExceptions(list);
};
ContentSettings.setHandlers = function(list) {
$('handlers-list').setHandlers(list);
};
ContentSettings.setIgnoredHandlers = function(list) {
$('ignored-handlers-list').setHandlers(list);
};
ContentSettings.setOTRExceptions = function(type, list) {
var exceptionsList =
document.querySelector('div[contentType=' + type + ']' +
' list[mode=otr]');
exceptionsList.parentNode.hidden = false;
exceptionsList.setExceptions(list);
};
/**
* The browser's response to a request to check the validity of a given URL
* pattern.
* @param {string} type The content type.
* @param {string} mode The browser mode.
* @param {string} pattern The pattern.
* @param {bool} valid Whether said pattern is valid in the context of
* a content exception setting.
*/
ContentSettings.patternValidityCheckComplete =
function(type, mode, pattern, valid) {
var exceptionsList =
document.querySelector('div[contentType=' + type + '] ' +
'list[mode=' + mode + ']');
exceptionsList.patternValidityCheckComplete(pattern, valid);
};
// Export
return {
ContentSettings: ContentSettings
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.contentSettings', function() {
const InlineEditableItemList = options.InlineEditableItemList;
const InlineEditableItem = options.InlineEditableItem;
const ArrayDataModel = cr.ui.ArrayDataModel;
/**
* Creates a new exceptions list item.
* @param {string} contentType The type of the list.
* @param {string} mode The browser mode, 'otr' or 'normal'.
* @param {boolean} enableAskOption Whether to show an 'ask every time'
* option in the select.
* @param {Object} exception A dictionary that contains the data of the
* exception.
* @constructor
* @extends {options.InlineEditableItem}
*/
function ExceptionsListItem(contentType, mode, enableAskOption, exception) {
var el = cr.doc.createElement('div');
el.mode = mode;
el.contentType = contentType;
el.enableAskOption = enableAskOption;
el.dataItem = exception;
el.__proto__ = ExceptionsListItem.prototype;
el.decorate();
return el;
}
ExceptionsListItem.prototype = {
__proto__: InlineEditableItem.prototype,
/**
* Called when an element is decorated as a list item.
*/
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
this.isPlaceholder = !this.pattern;
var patternCell = this.createEditableTextCell(this.pattern);
patternCell.className = 'exception-pattern';
patternCell.classList.add('weakrtl');
this.contentElement.appendChild(patternCell);
if (this.pattern)
this.patternLabel = patternCell.querySelector('.static-text');
var input = patternCell.querySelector('input');
// TODO(stuartmorgan): Create an createEditableSelectCell abstracting
// this code.
// Setting label for display mode. |pattern| will be null for the 'add new
// exception' row.
if (this.pattern) {
var settingLabel = cr.doc.createElement('span');
settingLabel.textContent = this.settingForDisplay();
settingLabel.className = 'exception-setting';
settingLabel.setAttribute('displaymode', 'static');
this.contentElement.appendChild(settingLabel);
this.settingLabel = settingLabel;
}
// Setting select element for edit mode.
var select = cr.doc.createElement('select');
var optionAllow = cr.doc.createElement('option');
optionAllow.textContent = templateData.allowException;
optionAllow.value = 'allow';
select.appendChild(optionAllow);
if (this.enableAskOption) {
var optionAsk = cr.doc.createElement('option');
optionAsk.textContent = templateData.askException;
optionAsk.value = 'ask';
select.appendChild(optionAsk);
}
if (this.contentType == 'cookies') {
var optionSession = cr.doc.createElement('option');
optionSession.textContent = templateData.sessionException;
optionSession.value = 'session';
select.appendChild(optionSession);
}
if (this.contentType != 'fullscreen') {
var optionBlock = cr.doc.createElement('option');
optionBlock.textContent = templateData.blockException;
optionBlock.value = 'block';
select.appendChild(optionBlock);
}
this.contentElement.appendChild(select);
select.className = 'exception-setting';
if (this.pattern)
select.setAttribute('displaymode', 'edit');
// Used to track whether the URL pattern in the input is valid.
// This will be true if the browser process has informed us that the
// current text in the input is valid. Changing the text resets this to
// false, and getting a response from the browser sets it back to true.
// It starts off as false for empty string (new exceptions) or true for
// already-existing exceptions (which we assume are valid).
this.inputValidityKnown = this.pattern;
// This one tracks the actual validity of the pattern in the input. This
// starts off as true so as not to annoy the user when he adds a new and
// empty input.
this.inputIsValid = true;
this.input = input;
this.select = select;
this.updateEditables();
// Editing notifications and geolocation is disabled for now.
if (this.contentType == 'notifications' ||
this.contentType == 'location') {
this.editable = false;
}
// If the source of the content setting exception is not the user
// preference, then the content settings exception is managed and the user
// can't edit it.
if (this.dataItem.source &&
this.dataItem.source != 'preference') {
this.setAttribute('managedby', this.dataItem.source);
this.deletable = false;
this.editable = false;
}
var listItem = this;
// Handle events on the editable nodes.
input.oninput = function(event) {
listItem.inputValidityKnown = false;
chrome.send('checkExceptionPatternValidity',
[listItem.contentType, listItem.mode, input.value]);
};
// Listen for edit events.
this.addEventListener('canceledit', this.onEditCancelled_);
this.addEventListener('commitedit', this.onEditCommitted_);
},
/**
* The pattern (e.g., a URL) for the exception.
* @type {string}
*/
get pattern() {
return this.dataItem['displayPattern'];
},
set pattern(pattern) {
this.dataItem['displayPattern'] = pattern;
},
/**
* The setting (allow/block) for the exception.
* @type {string}
*/
get setting() {
return this.dataItem['setting'];
},
set setting(setting) {
this.dataItem['setting'] = setting;
},
/**
* Gets a human-readable setting string.
* @type {string}
*/
settingForDisplay: function() {
var setting = this.setting;
if (setting == 'allow')
return templateData.allowException;
else if (setting == 'block')
return templateData.blockException;
else if (setting == 'ask')
return templateData.askException;
else if (setting == 'session')
return templateData.sessionException;
},
/**
* Update this list item to reflect whether the input is a valid pattern.
* @param {boolean} valid Whether said pattern is valid in the context of
* a content exception setting.
*/
setPatternValid: function(valid) {
if (valid || !this.input.value)
this.input.setCustomValidity('');
else
this.input.setCustomValidity(' ');
this.inputIsValid = valid;
this.inputValidityKnown = true;
},
/**
* Set the <input> to its original contents. Used when the user quits
* editing.
*/
resetInput: function() {
this.input.value = this.pattern;
},
/**
* Copy the data model values to the editable nodes.
*/
updateEditables: function() {
this.resetInput();
var settingOption =
this.select.querySelector('[value=\'' + this.setting + '\']');
if (settingOption)
settingOption.selected = true;
},
/** @inheritDoc */
get currentInputIsValid() {
return this.inputValidityKnown && this.inputIsValid;
},
/** @inheritDoc */
get hasBeenEdited() {
var livePattern = this.input.value;
var liveSetting = this.select.value;
return livePattern != this.pattern || liveSetting != this.setting;
},
/**
* Called when committing an edit.
* @param {Event} e The end event.
* @private
*/
onEditCommitted_: function(e) {
var newPattern = this.input.value;
var newSetting = this.select.value;
this.finishEdit(newPattern, newSetting);
},
/**
* Called when cancelling an edit; resets the control states.
* @param {Event} e The cancel event.
* @private
*/
onEditCancelled_: function() {
this.updateEditables();
this.setPatternValid(true);
},
/**
* Editing is complete; update the model.
* @param {string} newPattern The pattern that the user entered.
* @param {string} newSetting The setting the user chose.
*/
finishEdit: function(newPattern, newSetting) {
this.patternLabel.textContent = newPattern;
this.settingLabel.textContent = this.settingForDisplay();
var oldPattern = this.pattern;
this.pattern = newPattern;
this.setting = newSetting;
// TODO(estade): this will need to be updated if geolocation/notifications
// become editable.
if (oldPattern != newPattern) {
chrome.send('removeException',
[this.contentType, this.mode, oldPattern]);
}
chrome.send('setException',
[this.contentType, this.mode, newPattern, newSetting]);
}
};
/**
* Creates a new list item for the Add New Item row, which doesn't represent
* an actual entry in the exceptions list but allows the user to add new
* exceptions.
* @param {string} contentType The type of the list.
* @param {string} mode The browser mode, 'otr' or 'normal'.
* @param {boolean} enableAskOption Whether to show an 'ask every time'
* option in the select.
* @constructor
* @extends {cr.ui.ExceptionsListItem}
*/
function ExceptionsAddRowListItem(contentType, mode, enableAskOption) {
var el = cr.doc.createElement('div');
el.mode = mode;
el.contentType = contentType;
el.enableAskOption = enableAskOption;
el.dataItem = [];
el.__proto__ = ExceptionsAddRowListItem.prototype;
el.decorate();
return el;
}
ExceptionsAddRowListItem.prototype = {
__proto__: ExceptionsListItem.prototype,
decorate: function() {
ExceptionsListItem.prototype.decorate.call(this);
this.input.placeholder = templateData.addNewExceptionInstructions;
// Do we always want a default of allow?
this.setting = 'allow';
},
/**
* Clear the <input> and let the placeholder text show again.
*/
resetInput: function() {
this.input.value = '';
},
/** @inheritDoc */
get hasBeenEdited() {
return this.input.value != '';
},
/**
* Editing is complete; update the model. As long as the pattern isn't
* empty, we'll just add it.
* @param {string} newPattern The pattern that the user entered.
* @param {string} newSetting The setting the user chose.
*/
finishEdit: function(newPattern, newSetting) {
this.resetInput();
chrome.send('setException',
[this.contentType, this.mode, newPattern, newSetting]);
},
};
/**
* Creates a new exceptions list.
* @constructor
* @extends {cr.ui.List}
*/
var ExceptionsList = cr.ui.define('list');
ExceptionsList.prototype = {
__proto__: InlineEditableItemList.prototype,
/**
* Called when an element is decorated as a list.
*/
decorate: function() {
InlineEditableItemList.prototype.decorate.call(this);
this.classList.add('settings-list');
for (var parentNode = this.parentNode; parentNode;
parentNode = parentNode.parentNode) {
if (parentNode.hasAttribute('contentType')) {
this.contentType = parentNode.getAttribute('contentType');
break;
}
}
this.mode = this.getAttribute('mode');
var exceptionList = this;
// Whether the exceptions in this list allow an 'Ask every time' option.
this.enableAskOption = this.contentType == 'plugins';
this.autoExpands = true;
this.reset();
},
/**
* Creates an item to go in the list.
* @param {Object} entry The element from the data model for this row.
*/
createItem: function(entry) {
if (entry) {
return new ExceptionsListItem(this.contentType,
this.mode,
this.enableAskOption,
entry);
} else {
var addRowItem = new ExceptionsAddRowListItem(this.contentType,
this.mode,
this.enableAskOption);
addRowItem.deletable = false;
return addRowItem;
}
},
/**
* Sets the exceptions in the js model.
* @param {Object} entries A list of dictionaries of values, each dictionary
* represents an exception.
*/
setExceptions: function(entries) {
var deleteCount = this.dataModel.length;
if (this.isEditable()) {
// We don't want to remove the Add New Exception row.
deleteCount = deleteCount - 1;
}
var args = [0, deleteCount];
args.push.apply(args, entries);
this.dataModel.splice.apply(this.dataModel, args);
},
/**
* The browser has finished checking a pattern for validity. Update the
* list item to reflect this.
* @param {string} pattern The pattern.
* @param {bool} valid Whether said pattern is valid in the context of
* a content exception setting.
*/
patternValidityCheckComplete: function(pattern, valid) {
var listItems = this.items;
for (var i = 0; i < listItems.length; i++) {
var listItem = listItems[i];
// Don't do anything for messages for the item if it is not the intended
// recipient, or if the response is stale (i.e. the input value has
// changed since we sent the request to analyze it).
if (pattern == listItem.input.value)
listItem.setPatternValid(valid);
}
},
/**
* Returns whether the rows are editable in this list.
*/
isEditable: function() {
// Editing notifications and geolocation is disabled for now.
return !(this.contentType == 'notifications' ||
this.contentType == 'location' ||
this.contentType == 'fullscreen');
},
/**
* Removes all exceptions from the js model.
*/
reset: function() {
if (this.isEditable()) {
// The null creates the Add New Exception row.
this.dataModel = new ArrayDataModel([null]);
} else {
this.dataModel = new ArrayDataModel([]);
}
},
/** @inheritDoc */
deleteItemAtIndex: function(index) {
var listItem = this.getListItemByIndex(index);
if (listItem.undeletable)
return;
var dataItem = listItem.dataItem;
var args = [listItem.contentType];
if (listItem.contentType == 'location')
args.push(dataItem['origin'], dataItem['embeddingOrigin']);
else if (listItem.contentType == 'notifications')
args.push(dataItem['origin'], dataItem['setting']);
else
args.push(listItem.mode, listItem.pattern);
chrome.send('removeException', args);
},
};
var OptionsPage = options.OptionsPage;
/**
* Encapsulated handling of content settings list subpage.
* @constructor
*/
function ContentSettingsExceptionsArea() {
OptionsPage.call(this, 'contentExceptions',
templateData.contentSettingsPageTabTitle,
'content-settings-exceptions-area');
}
cr.addSingletonGetter(ContentSettingsExceptionsArea);
ContentSettingsExceptionsArea.prototype = {
__proto__: OptionsPage.prototype,
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var exceptionsLists = this.pageDiv.querySelectorAll('list');
for (var i = 0; i < exceptionsLists.length; i++) {
options.contentSettings.ExceptionsList.decorate(exceptionsLists[i]);
}
ContentSettingsExceptionsArea.hideOTRLists();
// If the user types in the URL without a hash, show just cookies.
this.showList('cookies');
},
/**
* Shows one list and hides all others.
* @param {string} type The content type.
*/
showList: function(type) {
var header = this.pageDiv.querySelector('h1');
header.textContent = templateData[type + '_header'];
var divs = this.pageDiv.querySelectorAll('div[contentType]');
for (var i = 0; i < divs.length; i++) {
if (divs[i].getAttribute('contentType') == type)
divs[i].hidden = false;
else
divs[i].hidden = true;
}
},
/**
* Called after the page has been shown. Show the content type for the
* location's hash.
*/
didShowPage: function() {
var hash = location.hash;
if (hash)
this.showList(hash.slice(1));
},
};
/**
* Called when the last incognito window is closed.
*/
ContentSettingsExceptionsArea.OTRProfileDestroyed = function() {
this.hideOTRLists();
};
/**
* Clears and hides the incognito exceptions lists.
*/
ContentSettingsExceptionsArea.hideOTRLists = function() {
var otrLists = document.querySelectorAll('list[mode=otr]');
for (var i = 0; i < otrLists.length; i++) {
otrLists[i].reset();
otrLists[i].parentNode.hidden = true;
}
};
return {
ExceptionsListItem: ExceptionsListItem,
ExceptionsAddRowListItem: ExceptionsAddRowListItem,
ExceptionsList: ExceptionsList,
ContentSettingsExceptionsArea: ContentSettingsExceptionsArea,
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
//////////////////////////////////////////////////////////////////////////////
// ContentSettingsRadio class:
// Define a constructor that uses an input element as its underlying element.
var ContentSettingsRadio = cr.ui.define('input');
ContentSettingsRadio.prototype = {
__proto__: HTMLInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
this.type = 'radio';
var self = this;
this.addEventListener('change',
function(e) {
chrome.send('setContentFilter', [this.name, this.value]);
});
},
};
/**
* Whether the content setting is controlled by something else than the user's
* settings (either 'policy' or 'extension').
* @type {string}
*/
cr.defineProperty(ContentSettingsRadio, 'controlledBy', cr.PropertyKind.ATTR);
//////////////////////////////////////////////////////////////////////////////
// HandlersEnabledRadio class:
// Define a constructor that uses an input element as its underlying element.
var HandlersEnabledRadio = cr.ui.define('input');
HandlersEnabledRadio.prototype = {
__proto__: HTMLInputElement.prototype,
/**
* Initialization function for the cr.ui framework.
*/
decorate: function() {
this.type = 'radio';
var self = this;
this.addEventListener('change',
function(e) {
chrome.send('setHandlersEnabled', [this.value == 'allow']);
});
},
};
// Export
return {
ContentSettingsRadio: ContentSettingsRadio,
HandlersEnabledRadio: HandlersEnabledRadio
};
});
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const DeletableItemList = options.DeletableItemList;
const DeletableItem = options.DeletableItem;
const ArrayDataModel = cr.ui.ArrayDataModel;
const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
// This structure maps the various cookie type names from C++ (hence the
// underscores) to arrays of the different types of data each has, along with
// the i18n name for the description of that data type.
const cookieInfo = {
'cookie': [ ['name', 'label_cookie_name'],
['content', 'label_cookie_content'],
['domain', 'label_cookie_domain'],
['path', 'label_cookie_path'],
['sendfor', 'label_cookie_send_for'],
['accessibleToScript', 'label_cookie_accessible_to_script'],
['created', 'label_cookie_created'],
['expires', 'label_cookie_expires'] ],
'app_cache': [ ['manifest', 'label_app_cache_manifest'],
['size', 'label_local_storage_size'],
['created', 'label_cookie_created'],
['accessed', 'label_cookie_last_accessed'] ],
'database': [ ['name', 'label_cookie_name'],
['desc', 'label_webdb_desc'],
['size', 'label_local_storage_size'],
['modified', 'label_local_storage_last_modified'] ],
'local_storage': [ ['origin', 'label_local_storage_origin'],
['size', 'label_local_storage_size'],
['modified', 'label_local_storage_last_modified'] ],
'indexed_db': [ ['origin', 'label_indexed_db_origin'],
['size', 'label_indexed_db_size'],
['modified', 'label_indexed_db_last_modified'] ],
'file_system': [ ['origin', 'label_file_system_origin'],
['persistent', 'label_file_system_persistent_usage' ],
['temporary', 'label_file_system_temporary_usage' ] ],
};
const localStrings = new LocalStrings();
/**
* Returns the item's height, like offsetHeight but such that it works better
* when the page is zoomed. See the similar calculation in @{code cr.ui.List}.
* This version also accounts for the animation done in this file.
* @param {Element} item The item to get the height of.
* @return {number} The height of the item, calculated with zooming in mind.
*/
function getItemHeight(item) {
var height = item.style.height;
// Use the fixed animation target height if set, in case the element is
// currently being animated and we'd get an intermediate height below.
if (height && height.substr(-2) == 'px')
return parseInt(height.substr(0, height.length - 2));
return item.getBoundingClientRect().height;
}
/**
* Create tree nodes for the objects in the data array, and insert them all
* into the given list using its @{code splice} method at the given index.
* @param {Array.<Object>} data The data objects for the nodes to add.
* @param {number} start The index at which to start inserting the nodes.
* @return {Array.<CookieTreeNode>} An array of CookieTreeNodes added.
*/
function spliceTreeNodes(data, start, list) {
var nodes = data.map(function(x) { return new CookieTreeNode(x); });
// Insert [start, 0] at the beginning of the array of nodes, making it
// into the arguments we want to pass to @{code list.splice} below.
nodes.splice(0, 0, start, 0);
list.splice.apply(list, nodes);
// Remove the [start, 0] prefix and return the array of nodes.
nodes.splice(0, 2);
return nodes;
}
var parentLookup = {};
var lookupRequests = {};
/**
* Creates a new list item for sites data. Note that these are created and
* destroyed lazily as they scroll into and out of view, so they must be
* stateless. We cache the expanded item in @{code CookiesList} though, so it
* can keep state. (Mostly just which item is selected.)
* @param {Object} origin Data used to create a cookie list item.
* @param {CookiesList} list The list that will contain this item.
* @constructor
* @extends {DeletableItem}
*/
function CookieListItem(origin, list) {
var listItem = new DeletableItem(null);
listItem.__proto__ = CookieListItem.prototype;
listItem.origin = origin;
listItem.list = list;
listItem.decorate();
// This hooks up updateOrigin() to the list item, makes the top-level
// tree nodes (i.e., origins) register their IDs in parentLookup, and
// causes them to request their children if they have none. Note that we
// have special logic in the setter for the parent property to make sure
// that we can still garbage collect list items when they scroll out of
// view, even though it appears that we keep a direct reference.
if (origin) {
origin.parent = listItem;
origin.updateOrigin();
}
return listItem;
}
CookieListItem.prototype = {
__proto__: DeletableItem.prototype,
/** @inheritDoc */
decorate: function() {
this.siteChild = this.ownerDocument.createElement('div');
this.siteChild.className = 'cookie-site';
this.dataChild = this.ownerDocument.createElement('div');
this.dataChild.className = 'cookie-data';
this.sizeChild = this.ownerDocument.createElement('div');
this.sizeChild.className = 'cookie-size';
this.itemsChild = this.ownerDocument.createElement('div');
this.itemsChild.className = 'cookie-items';
this.infoChild = this.ownerDocument.createElement('div');
this.infoChild.className = 'cookie-details';
this.infoChild.hidden = true;
var remove = this.ownerDocument.createElement('button');
remove.textContent = localStrings.getString('remove_cookie');
remove.onclick = this.removeCookie_.bind(this);
this.infoChild.appendChild(remove);
var content = this.contentElement;
content.appendChild(this.siteChild);
content.appendChild(this.dataChild);
content.appendChild(this.sizeChild);
content.appendChild(this.itemsChild);
this.itemsChild.appendChild(this.infoChild);
if (this.origin && this.origin.data) {
this.siteChild.textContent = this.origin.data.title;
this.siteChild.setAttribute('title', this.origin.data.title);
}
this.itemList_ = [];
},
/** @type {boolean} */
get expanded() {
return this.expanded_;
},
set expanded(expanded) {
if (this.expanded_ == expanded)
return;
this.expanded_ = expanded;
if (expanded) {
var oldExpanded = this.list.expandedItem;
this.list.expandedItem = this;
this.updateItems_();
if (oldExpanded)
oldExpanded.expanded = false;
this.classList.add('show-items');
} else {
if (this.list.expandedItem == this) {
this.list.leadItemHeight = 0;
this.list.expandedItem = null;
}
this.style.height = '';
this.itemsChild.style.height = '';
this.classList.remove('show-items');
}
},
/**
* The callback for the "remove" button shown when an item is selected.
* Requests that the currently selected cookie be removed.
* @private
*/
removeCookie_: function() {
if (this.selectedIndex_ >= 0) {
var item = this.itemList_[this.selectedIndex_];
if (item && item.node)
chrome.send('removeCookie', [item.node.pathId]);
}
},
/**
* Disable animation within this cookie list item, in preparation for making
* changes that will need to be animated. Makes it possible to measure the
* contents without displaying them, to set animation targets.
* @private
*/
disableAnimation_: function() {
this.itemsHeight_ = getItemHeight(this.itemsChild);
this.classList.add('measure-items');
},
/**
* Enable animation after changing the contents of this cookie list item.
* See @{code disableAnimation_}.
* @private
*/
enableAnimation_: function() {
if (!this.classList.contains('measure-items'))
this.disableAnimation_();
this.itemsChild.style.height = '';
// This will force relayout in order to calculate the new heights.
var itemsHeight = getItemHeight(this.itemsChild);
var fixedHeight = getItemHeight(this) + itemsHeight - this.itemsHeight_;
this.itemsChild.style.height = this.itemsHeight_ + 'px';
// Force relayout before enabling animation, so that if we have
// changed things since the last layout, they will not be animated
// during subsequent layouts.
this.itemsChild.offsetHeight;
this.classList.remove('measure-items');
this.itemsChild.style.height = itemsHeight + 'px';
this.style.height = fixedHeight + 'px';
if (this.expanded)
this.list.leadItemHeight = fixedHeight;
},
/**
* Updates the origin summary to reflect changes in its items.
* Both CookieListItem and CookieTreeNode implement this API.
* This implementation scans the descendants to update the text.
*/
updateOrigin: function() {
var info = {
cookies: 0,
database: false,
localStorage: false,
appCache: false,
indexedDb: false,
fileSystem: false,
};
if (this.origin)
this.origin.collectSummaryInfo(info);
var list = [];
if (info.cookies > 1)
list.push(localStrings.getStringF('cookie_plural', info.cookies));
else if (info.cookies > 0)
list.push(localStrings.getString('cookie_singular'));
if (info.database || info.indexedDb)
list.push(localStrings.getString('cookie_database_storage'));
if (info.localStorage)
list.push(localStrings.getString('cookie_local_storage'));
if (info.appCache)
list.push(localStrings.getString('cookie_app_cache'));
if (info.fileSystem)
list.push(localStrings.getString('cookie_file_system'));
var text = '';
for (var i = 0; i < list.length; ++i)
if (text.length > 0)
text += ', ' + list[i];
else
text = list[i];
this.dataChild.textContent = text;
if (info.quota && info.quota.totalUsage) {
this.sizeChild.textContent = info.quota.totalUsage;
}
if (this.expanded)
this.updateItems_();
},
/**
* Updates the items section to reflect changes, animating to the new state.
* Removes existing contents and calls @{code CookieTreeNode.createItems}.
* @private
*/
updateItems_: function() {
this.disableAnimation_();
this.itemsChild.textContent = '';
this.infoChild.hidden = true;
this.selectedIndex_ = -1;
this.itemList_ = [];
if (this.origin)
this.origin.createItems(this);
this.itemsChild.appendChild(this.infoChild);
this.enableAnimation_();
},
/**
* Append a new cookie node "bubble" to this list item.
* @param {CookieTreeNode} node The cookie node to add a bubble for.
* @param {Element} div The DOM element for the bubble itself.
* @return {number} The index the bubble was added at.
*/
appendItem: function(node, div) {
this.itemList_.push({node: node, div: div});
this.itemsChild.appendChild(div);
return this.itemList_.length - 1;
},
/**
* The currently selected cookie node ("cookie bubble") index.
* @type {number}
* @private
*/
selectedIndex_: -1,
/**
* Get the currently selected cookie node ("cookie bubble") index.
* @type {number}
*/
get selectedIndex() {
return this.selectedIndex_;
},
/**
* Set the currently selected cookie node ("cookie bubble") index to
* @{code itemIndex}, unselecting any previously selected node first.
* @param {number} itemIndex The index to set as the selected index.
*/
set selectedIndex(itemIndex) {
// Get the list index up front before we change anything.
var index = this.list.getIndexOfListItem(this);
// Unselect any previously selected item.
if (this.selectedIndex_ >= 0) {
var item = this.itemList_[this.selectedIndex_];
if (item && item.div)
item.div.removeAttribute('selected');
}
// Special case: decrementing -1 wraps around to the end of the list.
if (itemIndex == -2)
itemIndex = this.itemList_.length - 1;
// Check if we're going out of bounds and hide the item details.
if (itemIndex < 0 || itemIndex >= this.itemList_.length) {
this.selectedIndex_ = -1;
this.disableAnimation_();
this.infoChild.hidden = true;
this.enableAnimation_();
return;
}
// Set the new selected item and show the item details for it.
this.selectedIndex_ = itemIndex;
this.itemList_[itemIndex].div.setAttribute('selected', '');
this.disableAnimation_();
this.itemList_[itemIndex].node.setDetailText(this.infoChild,
this.list.infoNodes);
this.infoChild.hidden = false;
this.enableAnimation_();
// If we're near the bottom of the list this may cause the list item to go
// beyond the end of the visible area. Fix it after the animation is done.
var list = this.list;
window.setTimeout(function() { list.scrollIndexIntoView(index); }, 150);
},
};
/**
* {@code CookieTreeNode}s mirror the structure of the cookie tree lazily, and
* contain all the actual data used to generate the {@code CookieListItem}s.
* @param {Object} data The data object for this node.
* @constructor
*/
function CookieTreeNode(data) {
this.data = data;
this.children = [];
}
CookieTreeNode.prototype = {
/**
* Insert the given list of cookie tree nodes at the given index.
* Both CookiesList and CookieTreeNode implement this API.
* @param {Array.<Object>} data The data objects for the nodes to add.
* @param {number} start The index at which to start inserting the nodes.
*/
insertAt: function(data, start) {
var nodes = spliceTreeNodes(data, start, this.children);
for (var i = 0; i < nodes.length; i++)
nodes[i].parent = this;
this.updateOrigin();
},
/**
* Remove a cookie tree node from the given index.
* Both CookiesList and CookieTreeNode implement this API.
* @param {number} index The index of the tree node to remove.
*/
remove: function(index) {
if (index < this.children.length) {
this.children.splice(index, 1);
this.updateOrigin();
}
},
/**
* Clears all children.
* Both CookiesList and CookieTreeNode implement this API.
* It is used by CookiesList.loadChildren().
*/
clear: function() {
// We might leave some garbage in parentLookup for removed children.
// But that should be OK because parentLookup is cleared when we
// reload the tree.
this.children = [];
this.updateOrigin();
},
/**
* The counter used by startBatchUpdates() and endBatchUpdates().
* @type {number}
*/
batchCount_: 0,
/**
* See cr.ui.List.startBatchUpdates().
* Both CookiesList (via List) and CookieTreeNode implement this API.
*/
startBatchUpdates: function() {
this.batchCount_++;
},
/**
* See cr.ui.List.endBatchUpdates().
* Both CookiesList (via List) and CookieTreeNode implement this API.
*/
endBatchUpdates: function() {
if (!--this.batchCount_)
this.updateOrigin();
},
/**
* Requests updating the origin summary to reflect changes in this item.
* Both CookieListItem and CookieTreeNode implement this API.
*/
updateOrigin: function() {
if (!this.batchCount_ && this.parent)
this.parent.updateOrigin();
},
/**
* Summarize the information in this node and update @{code info}.
* This will recurse into child nodes to summarize all descendants.
* @param {Object} info The info object from @{code updateOrigin}.
*/
collectSummaryInfo: function(info) {
if (this.children.length > 0) {
for (var i = 0; i < this.children.length; ++i)
this.children[i].collectSummaryInfo(info);
} else if (this.data && !this.data.hasChildren) {
if (this.data.type == 'cookie') {
info.cookies++;
} else if (this.data.type == 'database') {
info.database = true;
} else if (this.data.type == 'local_storage') {
info.localStorage = true;
} else if (this.data.type == 'app_cache') {
info.appCache = true;
} else if (this.data.type == 'indexed_db') {
info.indexedDb = true;
} else if (this.data.type == 'file_system') {
info.fileSystem = true;
} else if (this.data.type == 'quota') {
info.quota = this.data;
}
}
},
/**
* Create the cookie "bubbles" for this node, recursing into children
* if there are any. Append the cookie bubbles to @{code item}.
* @param {CookieListItem} item The cookie list item to create items in.
*/
createItems: function(item) {
if (this.children.length > 0) {
for (var i = 0; i < this.children.length; ++i)
this.children[i].createItems(item);
} else if (this.data && !this.data.hasChildren) {
var text = '';
switch (this.data.type) {
case 'cookie':
case 'database':
text = this.data.name;
break;
case 'local_storage':
text = localStrings.getString('cookie_local_storage');
break;
case 'app_cache':
text = localStrings.getString('cookie_app_cache');
break;
case 'indexed_db':
text = localStrings.getString('cookie_indexed_db');
break;
case 'file_system':
text = localStrings.getString('cookie_file_system');
break;
}
if (!text)
return;
var div = item.ownerDocument.createElement('div');
div.className = 'cookie-item';
// Help out screen readers and such: this is a clickable thing.
div.setAttribute('role', 'button');
div.textContent = text;
var index = item.appendItem(this, div);
div.onclick = function() {
if (item.selectedIndex == index)
item.selectedIndex = -1;
else
item.selectedIndex = index;
};
}
},
/**
* Set the detail text to be displayed to that of this cookie tree node.
* Uses preallocated DOM elements for each cookie node type from @{code
* infoNodes}, and inserts the appropriate elements to @{code element}.
* @param {Element} element The DOM element to insert elements to.
* @param {Object.<string, {table: Element, info: Object.<string,
* Element>}>} infoNodes The map from cookie node types to maps from
* cookie attribute names to DOM elements to display cookie attribute
* values, created by @{code CookiesList.decorate}.
*/
setDetailText: function(element, infoNodes) {
var table;
if (this.data && !this.data.hasChildren) {
if (cookieInfo[this.data.type]) {
var info = cookieInfo[this.data.type];
var nodes = infoNodes[this.data.type].info;
for (var i = 0; i < info.length; ++i) {
var name = info[i][0];
if (name != 'id' && this.data[name])
nodes[name].textContent = this.data[name];
else
nodes[name].textContent = '';
}
table = infoNodes[this.data.type].table;
}
}
while (element.childNodes.length > 1)
element.removeChild(element.firstChild);
if (table)
element.insertBefore(table, element.firstChild);
},
/**
* The parent of this cookie tree node.
* @type {?CookieTreeNode|CookieListItem}
*/
get parent(parent) {
// See below for an explanation of this special case.
if (typeof this.parent_ == 'number')
return this.list_.getListItemByIndex(this.parent_);
return this.parent_;
},
set parent(parent) {
if (parent == this.parent)
return;
if (parent instanceof CookieListItem) {
// If the parent is to be a CookieListItem, then we keep the reference
// to it by its containing list and list index, rather than directly.
// This allows the list items to be garbage collected when they scroll
// out of view (except the expanded item, which we cache). This is
// transparent except in the setter and getter, where we handle it.
this.parent_ = parent.listIndex;
this.list_ = parent.list;
parent.addEventListener('listIndexChange',
this.parentIndexChanged_.bind(this));
} else {
this.parent_ = parent;
}
if (this.data && this.data.id) {
if (parent)
parentLookup[this.data.id] = this;
else
delete parentLookup[this.data.id];
}
if (this.data && this.data.hasChildren &&
!this.children.length && !lookupRequests[this.data.id]) {
lookupRequests[this.data.id] = true;
chrome.send('loadCookie', [this.pathId]);
}
},
/**
* Called when the parent is a CookieListItem whose index has changed.
* See the code above that avoids keeping a direct reference to
* CookieListItem parents, to allow them to be garbage collected.
* @private
*/
parentIndexChanged_: function(event) {
if (typeof this.parent_ == 'number') {
this.parent_ = event.newValue;
// We set a timeout to update the origin, rather than doing it right
// away, because this callback may occur while the list items are
// being repopulated following a scroll event. Calling updateOrigin()
// immediately could trigger relayout that would reset the scroll
// position within the list, among other things.
window.setTimeout(this.updateOrigin.bind(this), 0);
}
},
/**
* The cookie tree path id.
* @type {string}
*/
get pathId() {
var parent = this.parent;
if (parent && parent instanceof CookieTreeNode)
return parent.pathId + ',' + this.data.id;
return this.data.id;
},
};
/**
* Creates a new cookies list.
* @param {Object=} opt_propertyBag Optional properties.
* @constructor
* @extends {DeletableItemList}
*/
var CookiesList = cr.ui.define('list');
CookiesList.prototype = {
__proto__: DeletableItemList.prototype,
/** @inheritDoc */
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
this.classList.add('cookie-list');
this.data_ = [];
this.dataModel = new ArrayDataModel(this.data_);
this.addEventListener('keydown', this.handleKeyLeftRight_.bind(this));
var sm = new ListSingleSelectionModel();
sm.addEventListener('change', this.cookieSelectionChange_.bind(this));
sm.addEventListener('leadIndexChange', this.cookieLeadChange_.bind(this));
this.selectionModel = sm;
this.infoNodes = {};
var doc = this.ownerDocument;
// Create a table for each type of site data (e.g. cookies, databases,
// etc.) and save it so that we can reuse it for all origins.
for (var type in cookieInfo) {
var table = doc.createElement('table');
table.className = 'cookie-details-table';
var tbody = doc.createElement('tbody');
table.appendChild(tbody);
var info = {};
for (var i = 0; i < cookieInfo[type].length; i++) {
var tr = doc.createElement('tr');
var name = doc.createElement('td');
var data = doc.createElement('td');
var pair = cookieInfo[type][i];
name.className = 'cookie-details-label';
name.textContent = localStrings.getString(pair[1]);
data.className = 'cookie-details-value';
data.textContent = '';
tr.appendChild(name);
tr.appendChild(data);
tbody.appendChild(tr);
info[pair[0]] = data;
}
this.infoNodes[type] = {table: table, info: info};
}
},
/**
* Handles key down events and looks for left and right arrows, then
* dispatches to the currently expanded item, if any.
* @param {Event} e The keydown event.
* @private
*/
handleKeyLeftRight_: function(e) {
var id = e.keyIdentifier;
if ((id == 'Left' || id == 'Right') && this.expandedItem) {
var cs = this.ownerDocument.defaultView.getComputedStyle(this);
var rtl = cs.direction == 'rtl';
if ((!rtl && id == 'Left') || (rtl && id == 'Right'))
this.expandedItem.selectedIndex--;
else
this.expandedItem.selectedIndex++;
this.scrollIndexIntoView(this.expandedItem.listIndex);
// Prevent the page itself from scrolling.
e.preventDefault();
}
},
/**
* Called on selection model selection changes.
* @param {Event} ce The selection change event.
* @private
*/
cookieSelectionChange_: function(ce) {
ce.changes.forEach(function(change) {
var listItem = this.getListItemByIndex(change.index);
if (listItem) {
if (!change.selected) {
// We set a timeout here, rather than setting the item unexpanded
// immediately, so that if another item gets set expanded right
// away, it will be expanded before this item is unexpanded. It
// will notice that, and unexpand this item in sync with its own
// expansion. Later, this callback will end up having no effect.
window.setTimeout(function() {
if (!listItem.selected || !listItem.lead)
listItem.expanded = false;
}, 0);
} else if (listItem.lead) {
listItem.expanded = true;
}
}
}, this);
},
/**
* Called on selection model lead changes.
* @param {Event} pe The lead change event.
* @private
*/
cookieLeadChange_: function(pe) {
if (pe.oldValue != -1) {
var listItem = this.getListItemByIndex(pe.oldValue);
if (listItem) {
// See cookieSelectionChange_ above for why we use a timeout here.
window.setTimeout(function() {
if (!listItem.lead || !listItem.selected)
listItem.expanded = false;
}, 0);
}
}
if (pe.newValue != -1) {
var listItem = this.getListItemByIndex(pe.newValue);
if (listItem && listItem.selected)
listItem.expanded = true;
}
},
/**
* The currently expanded item. Used by CookieListItem above.
* @type {?CookieListItem}
*/
expandedItem: null,
// from cr.ui.List
/** @inheritDoc */
createItem: function(data) {
// We use the cached expanded item in order to allow it to maintain some
// state (like its fixed height, and which bubble is selected).
if (this.expandedItem && this.expandedItem.origin == data)
return this.expandedItem;
return new CookieListItem(data, this);
},
// from options.DeletableItemList
/** @inheritDoc */
deleteItemAtIndex: function(index) {
var item = this.data_[index];
if (item) {
var pathId = item.pathId;
if (pathId)
chrome.send('removeCookie', [pathId]);
}
},
/**
* Insert the given list of cookie tree nodes at the given index.
* Both CookiesList and CookieTreeNode implement this API.
* @param {Array.<Object>} data The data objects for the nodes to add.
* @param {number} start The index at which to start inserting the nodes.
*/
insertAt: function(data, start) {
spliceTreeNodes(data, start, this.dataModel);
},
/**
* Remove a cookie tree node from the given index.
* Both CookiesList and CookieTreeNode implement this API.
* @param {number} index The index of the tree node to remove.
*/
remove: function(index) {
if (index < this.data_.length)
this.dataModel.splice(index, 1);
},
/**
* Clears the list.
* Both CookiesList and CookieTreeNode implement this API.
* It is used by CookiesList.loadChildren().
*/
clear: function() {
parentLookup = {};
this.data_ = [];
this.dataModel = new ArrayDataModel(this.data_);
this.redraw();
},
/**
* Add tree nodes by given parent.
* @param {Object} parent The parent node.
* @param {number} start The index at which to start inserting the nodes.
* @param {Array} nodesData Nodes data array.
* @private
*/
addByParent_: function(parent, start, nodesData) {
if (!parent)
return;
parent.startBatchUpdates();
parent.insertAt(nodesData, start);
parent.endBatchUpdates();
cr.dispatchSimpleEvent(this, 'change');
},
/**
* Add tree nodes by parent id.
* This is used by cookies_view.js.
* @param {string} parentId Id of the parent node.
* @param {number} start The index at which to start inserting the nodes.
* @param {Array} nodesData Nodes data array.
*/
addByParentId: function(parentId, start, nodesData) {
var parent = parentId ? parentLookup[parentId] : this;
this.addByParent_(parent, start, nodesData);
},
/**
* Removes tree nodes by parent id.
* This is used by cookies_view.js.
* @param {string} parentId Id of the parent node.
* @param {number} start The index at which to start removing the nodes.
* @param {number} count Number of nodes to remove.
*/
removeByParentId: function(parentId, start, count) {
var parent = parentId ? parentLookup[parentId] : this;
if (!parent)
return;
parent.startBatchUpdates();
while (count-- > 0)
parent.remove(start);
parent.endBatchUpdates();
cr.dispatchSimpleEvent(this, 'change');
},
/**
* Loads the immediate children of given parent node.
* This is used by cookies_view.js.
* @param {string} parentId Id of the parent node.
* @param {Array} children The immediate children of parent node.
*/
loadChildren: function(parentId, children) {
if (parentId)
delete lookupRequests[parentId];
var parent = parentId ? parentLookup[parentId] : this;
if (!parent)
return;
parent.startBatchUpdates();
parent.clear();
this.addByParent_(parent, 0, children);
parent.endBatchUpdates();
},
};
return {
CookiesList: CookiesList
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/////////////////////////////////////////////////////////////////////////////
// CookiesView class:
/**
* Encapsulated handling of the cookies and other site data page.
* @constructor
*/
function CookiesView(model) {
OptionsPage.call(this, 'cookies',
templateData.cookiesViewPageTabTitle,
'cookiesViewPage');
}
cr.addSingletonGetter(CookiesView);
CookiesView.prototype = {
__proto__: OptionsPage.prototype,
/**
* The timer id of the timer set on search query change events.
* @type {number}
* @private
*/
queryDelayTimerId_: 0,
/**
* The most recent search query, or null if the query is empty.
* @type {?string}
* @private
*/
lastQuery_ : null,
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
$('cookies-search-box').addEventListener('search',
this.handleSearchQueryChange_.bind(this));
$('remove-all-cookies-button').onclick = function(e) {
chrome.send('removeAllCookies', []);
};
var cookiesList = $('cookies-list');
options.CookiesList.decorate(cookiesList);
window.addEventListener('resize', this.handleResize_.bind(this));
this.addEventListener('visibleChange', this.handleVisibleChange_);
},
/**
* Search cookie using text in |cookies-search-box|.
*/
searchCookie: function() {
this.queryDelayTimerId_ = 0;
var filter = $('cookies-search-box').value;
if (this.lastQuery_ != filter) {
this.lastQuery_ = filter;
chrome.send('updateCookieSearchResults', [filter]);
}
},
/**
* Handles search query changes.
* @param {!Event} e The event object.
* @private
*/
handleSearchQueryChange_: function(e) {
if (this.queryDelayTimerId_)
window.clearTimeout(this.queryDelayTimerId_);
this.queryDelayTimerId_ = window.setTimeout(
this.searchCookie.bind(this), 500);
},
initialized_: false,
/**
* Handler for OptionsPage's visible property change event.
* @param {Event} e Property change event.
* @private
*/
handleVisibleChange_: function(e) {
if (!this.visible)
return;
// Resize the cookies list whenever the options page becomes visible.
this.handleResize_(null);
if (!this.initialized_) {
this.initialized_ = true;
this.searchCookie();
} else {
$('cookies-list').redraw();
}
$('cookies-search-box').focus();
},
/**
* Handler for when the window changes size. Resizes the cookies list to
* match the window height.
* @param {?Event} e Window resize event, or null if called directly.
* @private
*/
handleResize_: function(e) {
if (!this.visible)
return;
var cookiesList = $('cookies-list');
// 25 pixels from the window bottom seems like a visually pleasing amount.
var height = window.innerHeight - cookiesList.offsetTop - 25;
cookiesList.style.height = height + 'px';
},
};
// CookiesViewHandler callbacks.
CookiesView.onTreeItemAdded = function(args) {
$('cookies-list').addByParentId(args[0], args[1], args[2]);
};
CookiesView.onTreeItemRemoved = function(args) {
$('cookies-list').removeByParentId(args[0], args[1], args[2]);
};
CookiesView.loadChildren = function(args) {
$('cookies-list').loadChildren(args[0], args[1]);
};
// Export
return {
CookiesView: CookiesView
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
'use strict';
/**
* A lookup helper function to find the first node that has an id (starting
* at |node| and going up the parent chain).
* @param {Element} node The node to start looking at.
*/
function findIdNode(node) {
while (node && !node.id) {
node = node.parentNode;
}
return node;
}
/**
* Creates a new list of extensions.
* @param {Object=} opt_propertyBag Optional properties.
* @constructor
* @extends {cr.ui.div}
*/
var ExtensionsList = cr.ui.define('div');
var handlersInstalled = false;
/**
* @type {Object.<string, boolean>} A map from extension id to a boolean
* indicating whether its details section is expanded. This persists
* between calls to decorate.
*/
var showingDetails = {};
/**
* @type {Object.<string, boolean>} A map from extension id to a boolean
* indicating whether the incognito warning is showing. This persists
* between calls to decorate.
*/
var showingWarning = {};
ExtensionsList.prototype = {
__proto__: HTMLDivElement.prototype,
/** @inheritDoc */
decorate: function() {
this.initControlsAndHandlers_();
this.deleteExistingExtensionNodes_();
this.showExtensionNodes_();
},
/**
* Initializes the controls (toggle section and button) and installs
* handlers.
* @private
*/
initControlsAndHandlers_: function() {
// Make sure developer mode section is set correctly as per saved setting.
var toggleButton = $('toggle-dev-on');
var toggleSection = $('dev');
if (this.data_.developerMode) {
toggleSection.classList.add('dev-open');
toggleSection.classList.remove('dev-closed');
toggleButton.checked = true;
} else {
toggleSection.classList.remove('dev-open');
toggleSection.classList.add('dev-closed');
}
// Instal global event handlers.
if (!handlersInstalled) {
var searchPage = SearchPage.getInstance();
searchPage.addEventListener('searchChanged',
this.searchChangedHandler_.bind(this));
// Support full keyboard accessibility without making things ugly
// for users who click, by hiding some focus outlines when the user
// clicks anywhere, but showing them when the user presses any key.
this.ownerDocument.body.classList.add('hide-some-focus-outlines');
this.ownerDocument.addEventListener('click', (function(e) {
this.ownerDocument.body.classList.add('hide-some-focus-outlines');
return true;
}).bind(this), true);
this.ownerDocument.addEventListener('keydown', (function(e) {
this.ownerDocument.body.classList.remove('hide-some-focus-outlines');
return true;
}).bind(this), true);
handlersInstalled = true;
}
},
/**
* Deletes the existing Extension nodes from the page to make room for new
* ones.
* @private
*/
deleteExistingExtensionNodes_: function() {
while (this.hasChildNodes()){
this.removeChild(this.firstChild);
}
},
/**
* Handles decorating the details section.
* @param {Element} details The div that the details should be attached to.
* @param {Object} extension The extension we are showing the details for.
* @private
*/
showExtensionNodes_: function() {
// Iterate over the extension data and add each item to the list.
for (var i = 0; i < this.data_.extensions.length; i++) {
var extension = this.data_.extensions[i];
var id = extension.id;
var wrapper = this.ownerDocument.createElement('div');
var expanded = showingDetails[id];
var butterbar = showingWarning[id];
wrapper.classList.add(expanded ? 'extension-list-item-expanded' :
'extension-list-item-collaped');
if (!extension.enabled)
wrapper.classList.add('disabled');
wrapper.id = id;
this.appendChild(wrapper);
var vboxOuter = this.ownerDocument.createElement('div');
vboxOuter.classList.add('vbox');
vboxOuter.classList.add('extension-list-item');
wrapper.appendChild(vboxOuter);
var hbox = this.ownerDocument.createElement('div');
hbox.classList.add('hbox');
vboxOuter.appendChild(hbox);
// Add a container div for the zippy, so we can extend the hit area.
var container = this.ownerDocument.createElement('div');
// Clicking anywhere on the div expands/collapses the details.
container.classList.add('extension-zippy-container');
container.title = expanded ?
localStrings.getString('extensionSettingsHideDetails') :
localStrings.getString('extensionSettingsShowDetails');
container.tabIndex = 0;
container.setAttribute('role', 'button');
container.setAttribute('aria-controls', extension.id + '_details');
container.setAttribute('aria-expanded', expanded);
container.addEventListener('click', this.handleZippyClick_.bind(this));
container.addEventListener('keydown',
this.handleZippyKeyDown_.bind(this));
hbox.appendChild(container);
// On the far left we have the zippy icon.
var div = this.ownerDocument.createElement('div');
div.id = id + '_zippy';
div.classList.add('extension-zippy-default');
div.classList.add(expanded ? 'extension-zippy-expanded' :
'extension-zippy-collapsed');
container.appendChild(div);
// Next to it, we have the extension icon.
var icon = this.ownerDocument.createElement('img');
icon.classList.add('extension-icon');
icon.src = extension.icon;
hbox.appendChild(icon);
// Start a vertical box for showing the details.
var vbox = this.ownerDocument.createElement('div');
vbox.classList.add('vbox');
vbox.classList.add('stretch');
vbox.classList.add('details-view');
hbox.appendChild(vbox);
div = this.ownerDocument.createElement('div');
vbox.appendChild(div);
// Title comes next.
var title = this.ownerDocument.createElement('span');
title.classList.add('extension-title');
title.textContent = extension.name;
vbox.appendChild(title);
// Followed by version.
var version = this.ownerDocument.createElement('span');
version.classList.add('extension-version');
version.textContent = extension.version;
vbox.appendChild(version);
// And the additional info label (unpacked/crashed).
if (extension.terminated || extension.isUnpacked) {
var version = this.ownerDocument.createElement('span');
version.classList.add('extension-version');
version.textContent = extension.terminated ?
localStrings.getString('extensionSettingsCrashMessage') :
localStrings.getString('extensionSettingsInDevelopment');
vbox.appendChild(version);
}
div = this.ownerDocument.createElement('div');
vbox.appendChild(div);
// And below that we have description (if provided).
if (extension.description.length > 0) {
var description = this.ownerDocument.createElement('span');
description.classList.add('extension-description');
description.textContent = extension.description;
vbox.appendChild(description);
}
// Immediately following the description, we have the
// Options link (optional).
if (extension.options_url) {
var link = this.ownerDocument.createElement('a');
link.classList.add('extension-links-trailing');
link.textContent = localStrings.getString('extensionSettingsOptions');
link.href = '#';
link.addEventListener('click', this.handleOptions_.bind(this));
vbox.appendChild(link);
}
// Then the optional Visit Website link.
if (extension.homepageUrl) {
var link = this.ownerDocument.createElement('a');
link.classList.add('extension-links-trailing');
link.textContent =
localStrings.getString('extensionSettingsVisitWebsite');
link.href = extension.homepageUrl;
vbox.appendChild(link);
}
if (extension.warnings.length > 0) {
var warningsDiv = this.ownerDocument.createElement('div');
warningsDiv.classList.add('extension-warnings');
var warningsHeader = this.ownerDocument.createElement('span');
warningsHeader.classList.add('extension-warnings-title');
warningsHeader.textContent =
localStrings.getString('extensionSettingsWarningsTitle');
warningsDiv.appendChild(warningsHeader);
var warningList = this.ownerDocument.createElement('ul');
for (var j = 0; j < extension.warnings.length; ++j) {
var warningEntry = this.ownerDocument.createElement('li');
warningEntry.textContent = extension.warnings[j];
warningList.appendChild(warningEntry);
}
warningsDiv.appendChild(warningList);
vbox.appendChild(warningsDiv);
}
// And now the details section that is normally hidden.
var details = this.ownerDocument.createElement('div');
details.classList.add('vbox');
vbox.appendChild(details);
this.decorateDetailsSection_(details, extension, expanded, butterbar);
// And on the right of the details we have the Enable/Enabled checkbox.
div = this.ownerDocument.createElement('div');
hbox.appendChild(div);
var section = this.ownerDocument.createElement('section');
section.classList.add('extension-enabling');
div.appendChild(section);
if (!extension.terminated) {
// The Enable checkbox.
var input = this.ownerDocument.createElement('input');
input.addEventListener('click', this.handleEnable_.bind(this));
input.type = 'checkbox';
input.name = 'toggle-' + id;
input.disabled = !extension.mayDisable;
if (extension.enabled)
input.checked = true;
input.id = 'toggle-' + id;
section.appendChild(input);
var label = this.ownerDocument.createElement('label');
label.classList.add('extension-enabling-label');
if (extension.enabled)
label.classList.add('extension-enabling-label-bold');
label.htmlFor = 'toggle-' + id;
label.id = 'toggle-' + id + '-label';
if (extension.enabled) {
// Enabled (with a d).
label.textContent =
localStrings.getString('extensionSettingsEnabled');
} else {
// Enable (no d).
label.textContent =
localStrings.getString('extensionSettingsEnable');
}
section.appendChild(label);
} else {
// Extension has been terminated, show a Reload link.
var link = this.ownerDocument.createElement('a');
link.classList.add('extension-links-trailing');
link.id = extension.id;
link.textContent =
localStrings.getString('extensionSettingsReload');
link.href = '#';
link.addEventListener('click', this.handleReload_.bind(this));
section.appendChild(link);
}
// And, on the far right we have the uninstall button.
var button = this.ownerDocument.createElement('button');
button.classList.add('extension-delete');
button.id = id;
if (!extension.mayDisable)
button.disabled = true;
button.textContent = localStrings.getString('extensionSettingsRemove');
button.addEventListener('click', this.handleUninstall_.bind(this));
hbox.appendChild(button);
}
// Do one pass to find what the size of the checkboxes should be.
var minCheckboxWidth = Infinity;
var maxCheckboxWidth = 0;
for (var i = 0; i < this.data_.extensions.length; ++i) {
var label = $('toggle-' + this.data_.extensions[i].id + '-label');
if (label.offsetWidth > maxCheckboxWidth)
maxCheckboxWidth = label.offsetWidth;
if (label.offsetWidth < minCheckboxWidth)
minCheckboxWidth = label.offsetWidth;
}
// Do another pass, making sure checkboxes line up.
var difference = maxCheckboxWidth - minCheckboxWidth;
for (var i = 0; i < this.data_.extensions.length; ++i) {
var label = $('toggle-' + this.data_.extensions[i].id + '-label');
if (label.offsetWidth < maxCheckboxWidth)
label.style.WebkitMarginEnd = difference.toString() + 'px';
}
},
/**
* Handles decorating the details section.
* @param {Element} details The div that the details should be attached to.
* @param {Object} extension The extension we are shoting the details for.
* @param {boolean} expanded Whether to show the details expanded or not.
* @param {boolean} showButterbar Whether to show the incognito warning or
* not.
* @private
*/
decorateDetailsSection_: function(details, extension,
expanded, showButterbar) {
// This container div is needed because vbox display
// overrides display:hidden.
var detailsContents = this.ownerDocument.createElement('div');
detailsContents.classList.add(expanded ? 'extension-details-visible' :
'extension-details-hidden');
detailsContents.id = extension.id + '_details';
details.appendChild(detailsContents);
var div = this.ownerDocument.createElement('div');
div.classList.add('informative-text');
detailsContents.appendChild(div);
// Keep track of how many items we'll show in the details section.
var itemsShown = 0;
if (this.data_.developerMode) {
// First we have the id.
var content = this.ownerDocument.createElement('div');
content.textContent =
localStrings.getString('extensionSettingsExtensionId') +
' ' + extension.id;
div.appendChild(content);
itemsShown++;
// Then, the path, if provided by unpacked extension.
if (extension.isUnpacked) {
content = this.ownerDocument.createElement('div');
content.textContent =
localStrings.getString('extensionSettingsExtensionPath') +
' ' + extension.path;
div.appendChild(content);
itemsShown++;
}
// Then, the 'managed, cannot uninstall/disable' message.
if (!extension.mayDisable) {
content = this.ownerDocument.createElement('div');
content.textContent =
localStrings.getString('extensionSettingsPolicyControlled');
div.appendChild(content);
itemsShown++;
}
// Then active views:
if (extension.views.length > 0) {
var table = this.ownerDocument.createElement('table');
table.classList.add('extension-inspect-table');
div.appendChild(table);
var tr = this.ownerDocument.createElement('tr');
table.appendChild(tr);
var td = this.ownerDocument.createElement('td');
td.classList.add('extension-inspect-left-column');
tr.appendChild(td);
var span = this.ownerDocument.createElement('span');
td.appendChild(span);
span.textContent =
localStrings.getString('extensionSettingsInspectViews');
td = this.ownerDocument.createElement('td');
for (var i = 0; i < extension.views.length; ++i) {
// Then active views:
content = this.ownerDocument.createElement('div');
var link = this.ownerDocument.createElement('a');
link.classList.add('extension-links-view');
link.textContent = extension.views[i].path;
link.id = extension.id;
link.href = '#';
link.addEventListener('click', this.sendInspectMessage_.bind(this));
content.appendChild(link);
if (extension.views[i].incognito) {
var incognito = this.ownerDocument.createElement('span');
incognito.classList.add('extension-links-view');
incognito.textContent =
localStrings.getString('viewIncognito');
content.appendChild(incognito);
}
td.appendChild(content);
tr.appendChild(td);
itemsShown++;
}
}
}
var content = this.ownerDocument.createElement('div');
detailsContents.appendChild(content);
// Then Reload:
if (extension.enabled && extension.allow_reload) {
this.addLinkTo_(content,
localStrings.getString('extensionSettingsReload'),
extension.id,
this.handleReload_.bind(this));
itemsShown++;
}
// Then Show (Browser Action) Button:
if (extension.enabled && extension.enable_show_button) {
this.addLinkTo_(content,
localStrings.getString('extensionSettingsShowButton'),
extension.id,
this.handleShowButton_.bind(this));
itemsShown++;
}
if (extension.enabled) {
// The 'allow in incognito' checkbox.
var label = this.ownerDocument.createElement('label');
label.classList.add('extension-checkbox-label');
content.appendChild(label);
var input = this.ownerDocument.createElement('input');
input.addEventListener('click',
this.handleToggleEnableIncognito_.bind(this));
input.id = extension.id;
input.type = 'checkbox';
if (extension.enabledIncognito)
input.checked = true;
label.appendChild(input);
var span = this.ownerDocument.createElement('span');
span.classList.add('extension-checkbox-span');
span.textContent =
localStrings.getString('extensionSettingsEnableIncognito');
label.appendChild(span);
itemsShown++;
}
if (extension.enabled && extension.wantsFileAccess) {
// The 'allow access to file URLs' checkbox.
label = this.ownerDocument.createElement('label');
label.classList.add('extension-checkbox-label');
content.appendChild(label);
var input = this.ownerDocument.createElement('input');
input.addEventListener('click',
this.handleToggleAllowFileUrls_.bind(this));
input.id = extension.id;
input.type = 'checkbox';
if (extension.allowFileAccess)
input.checked = true;
label.appendChild(input);
var span = this.ownerDocument.createElement('span');
span.classList.add('extension-checkbox-span');
span.textContent =
localStrings.getString('extensionSettingsAllowFileAccess');
label.appendChild(span);
itemsShown++;
}
if (extension.enabled && !extension.is_hosted_app) {
// And add a hidden warning message for allowInIncognito.
content = this.ownerDocument.createElement('div');
content.id = extension.id + '_incognitoWarning';
content.classList.add('butter-bar');
content.hidden = !showButterbar;
detailsContents.appendChild(content);
var span = this.ownerDocument.createElement('span');
span.innerHTML =
localStrings.getString('extensionSettingsIncognitoWarning');
content.appendChild(span);
itemsShown++;
}
var zippy = extension.id + '_zippy';
$(zippy).hidden = !itemsShown;
// If this isn't expanded now, make sure the newly-added controls
// are not part of the tab order.
if (!expanded) {
var detailsControls = details.querySelectorAll('a, input');
for (var i = 0; i < detailsControls.length; i++)
detailsControls[i].tabIndex = -1;
}
},
/**
* A helper function to add contextual actions for extensions (action links)
* to the page.
*/
addLinkTo_: function(parent, linkText, id, handler) {
var link = this.ownerDocument.createElement('a');
link.className = 'extension-links-trailing';
link.textContent = linkText;
link.id = id;
link.href = '#';
link.addEventListener('click', handler);
parent.appendChild(link);
},
/**
* A lookup helper function to find an extension based on an id.
* @param {string} id The |id| of the extension to look up.
* @private
*/
getExtensionWithId_: function(id) {
for (var i = 0; i < this.data_.extensions.length; ++i) {
if (this.data_.extensions[i].id == id)
return this.data_.extensions[i];
}
return null;
},
/**
* Handles a key down on the zippy icon.
* @param {Event} e Key event.
* @private
*/
handleZippyKeyDown_: function(e) {
if (e.keyCode == 13 || e.keyCode == 32) // Enter or Space.
this.handleZippyClick_(e);
},
/**
* Handles the mouseclick on the zippy icon (that expands and collapses the
* details section).
* @param {Event} e Mouse event.
* @private
*/
handleZippyClick_: function(e) {
var node = findIdNode(e.target.parentNode);
var iter = this.firstChild;
while (iter) {
var zippy = $(iter.id + '_zippy');
var details = $(iter.id + '_details');
var container = zippy.parentElement;
if (iter.id == node.id) {
// Toggle visibility.
if (iter.classList.contains('extension-list-item-expanded')) {
// Hide yo kids! Hide yo wife!
showingDetails[iter.id] = false;
zippy.classList.remove('extension-zippy-expanded');
zippy.classList.add('extension-zippy-collapsed');
details.classList.remove('extension-details-visible');
details.classList.add('extension-details-hidden');
iter.classList.remove('extension-list-item-expanded');
iter.classList.add('extension-list-item-collaped');
container.setAttribute('aria-expanded', 'false');
container.title =
localStrings.getString('extensionSettingsShowDetails');
var detailsControls = details.querySelectorAll('a, input');
for (var i = 0; i < detailsControls.length; i++)
detailsControls[i].tabIndex = -1;
// Hide yo incognito warning.
var butterBar =
this.querySelector('#' + iter.id + '_incognitoWarning');
if (butterBar !== null) {
butterBar.hidden = true;
showingWarning[iter.id] = false;
}
} else {
// Show the contents.
showingDetails[iter.id] = true;
zippy.classList.remove('extension-zippy-collapsed');
zippy.classList.add('extension-zippy-expanded');
details.classList.remove('extension-details-hidden');
details.classList.add('extension-details-visible');
iter.classList.remove('extension-list-item-collaped');
iter.classList.add('extension-list-item-expanded');
container.setAttribute('aria-expanded', 'true');
container.title =
localStrings.getString('extensionSettingsHideDetails');
var detailsControls = details.querySelectorAll('a, input');
for (var i = 0; i < detailsControls.length; i++)
detailsControls[i].tabIndex = 0;
}
}
iter = iter.nextSibling;
}
},
/**
* Handles the 'searchChanged' event. This is used to limit the number of
* items to show in the list, when the user is searching for items with the
* search box. Otherwise, if one match is found, the whole list of
* extensions would be shown when we only want the matching items to be
* found.
* @param {Event} e Change event.
* @private
*/
searchChangedHandler_: function(e) {
var searchString = e.searchText;
var child = this.firstChild;
while (child) {
var extension = this.getExtensionWithId_(child.id);
if (searchString.length == 0) {
// Show all.
child.classList.remove('search-suppress');
} else {
// If the search string does not appear within the text of the
// extension, then hide it.
if ((extension.name.toLowerCase().indexOf(searchString) < 0) &&
(extension.version.toLowerCase().indexOf(searchString) < 0) &&
(extension.description.toLowerCase().indexOf(searchString) < 0)) {
// Hide yo extension!
child.classList.add('search-suppress');
} else {
// Show yourself!
child.classList.remove('search-suppress');
}
}
child = child.nextSibling;
}
},
/**
* Handles the Reload Extension functionality.
* @param {Event} e Change event.
* @private
*/
handleReload_: function(e) {
var node = findIdNode(e.target);
chrome.send('extensionSettingsReload', [node.id]);
},
/**
* Handles the Show (Browser Action) Button functionality.
* @param {Event} e Change event.
* @private
*/
handleShowButton_: function(e) {
var node = findIdNode(e.target);
chrome.send('extensionSettingsShowButton', [node.id]);
},
/**
* Handles the Enable/Disable Extension functionality.
* @param {Event} e Change event.
* @private
*/
handleEnable_: function(e) {
var node = findIdNode(e.target.parentNode);
var extension = this.getExtensionWithId_(node.id);
chrome.send('extensionSettingsEnable',
[node.id, extension.enabled ? 'false' : 'true']);
chrome.send('extensionSettingsRequestExtensionsData');
},
/**
* Handles the Uninstall Extension functionality.
* @param {Event} e Change event.
* @private
*/
handleUninstall_: function(e) {
var node = findIdNode(e.target.parentNode);
chrome.send('extensionSettingsUninstall', [node.id]);
chrome.send('extensionSettingsRequestExtensionsData');
},
/**
* Handles the View Options link.
* @param {Event} e Change event.
* @private
*/
handleOptions_: function(e) {
var node = findIdNode(e.target.parentNode);
var extension = this.getExtensionWithId_(node.id);
chrome.send('extensionSettingsOptions', [extension.id]);
e.preventDefault();
},
/**
* Handles the Enable Extension In Incognito functionality.
* @param {Event} e Change event.
* @private
*/
handleToggleEnableIncognito_: function(e) {
var node = findIdNode(e.target);
var butterBar = document.getElementById(node.id + '_incognitoWarning');
butterBar.hidden = !e.target.checked;
showingWarning[node.id] = e.target.checked;
chrome.send('extensionSettingsEnableIncognito',
[node.id, String(e.target.checked)]);
},
/**
* Handles the Allow On File URLs functionality.
* @param {Event} e Change event.
* @private
*/
handleToggleAllowFileUrls_: function(e) {
var node = findIdNode(e.target);
chrome.send('extensionSettingsAllowFileAccess',
[node.id, String(e.target.checked)]);
},
/**
* Tell the C++ ExtensionDOMHandler to inspect the page detailed in
* |viewData|.
* @param {Event} e Change event.
* @private
*/
sendInspectMessage_: function(e) {
var extension = this.getExtensionWithId_(e.srcElement.id);
for (var i = 0; i < extension.views.length; ++i) {
if (extension.views[i].path == e.srcElement.innerText) {
// TODO(aa): This is ghetto, but WebUIBindings doesn't support sending
// anything other than arrays of strings, and this is all going to get
// replaced with V8 extensions soon anyway.
chrome.send('extensionSettingsInspect', [
String(extension.views[i].renderProcessId),
String(extension.views[i].renderViewId)
]);
}
}
},
};
return {
ExtensionsList: ExtensionsList
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Used for observing function of the backend datasource for this page by
// tests.
var webui_responded_ = false;
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
var ExtensionsList = options.ExtensionsList;
/**
* ExtensionSettings class
* Encapsulated handling of the 'Manage Extensions' page.
* @class
*/
function ExtensionSettings() {
OptionsPage.call(this, 'extensions',
templateData.extensionSettingsTabTitle,
'extension-settings');
}
cr.addSingletonGetter(ExtensionSettings);
ExtensionSettings.prototype = {
__proto__: OptionsPage.prototype,
/**
* Initialize the page.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
// This will request the data to show on the page and will get a response
// back in returnExtensionsData.
chrome.send('extensionSettingsRequestExtensionsData');
// Set up the developer mode button.
var toggleDevMode = $('toggle-dev-on');
toggleDevMode.addEventListener('click',
this.handleToggleDevMode_.bind(this));
// Setup the gallery related links and text.
$('suggest-gallery').innerHTML =
localStrings.getString('extensionSettingsSuggestGallery');
$('get-more-extensions').innerHTML =
localStrings.getString('extensionSettingsGetMoreExtensions');
// Set up the three dev mode buttons (load unpacked, pack and update).
$('load-unpacked').addEventListener('click',
this.handleLoadUnpackedExtension_.bind(this));
$('pack-extension').addEventListener('click',
this.handlePackExtension_.bind(this));
$('update-extensions-now').addEventListener('click',
this.handleUpdateExtensionNow_.bind(this));
},
/**
* Utility function which asks the C++ to show a platform-specific file
* select dialog, and fire |callback| with the |filePath| that resulted.
* |selectType| can be either 'file' or 'folder'. |operation| can be 'load',
* 'packRoot', or 'pem' which are signals to the C++ to do some
* operation-specific configuration.
* @private
*/
showFileDialog_: function(selectType, operation, callback) {
handleFilePathSelected = function(filePath) {
callback(filePath);
handleFilePathSelected = function() {};
};
chrome.send('extensionSettingsSelectFilePath', [selectType, operation]);
},
/**
* Handles the Load Unpacked Extension button.
* @param {Event} e Change event.
* @private
*/
handleLoadUnpackedExtension_: function(e) {
this.showFileDialog_('folder', 'load', function(filePath) {
chrome.send('extensionSettingsLoad', [String(filePath)]);
});
chrome.send('coreOptionsUserMetricsAction',
['Options_LoadUnpackedExtension']);
},
/**
* Handles the Pack Extension button.
* @param {Event} e Change event.
* @private
*/
handlePackExtension_: function(e) {
OptionsPage.navigateToPage('packExtensionOverlay');
chrome.send('coreOptionsUserMetricsAction', ['Options_PackExtension']);
},
/**
* Handles the Update Extension Now button.
* @param {Event} e Change event.
* @private
*/
handleUpdateExtensionNow_: function(e) {
chrome.send('extensionSettingsAutoupdate', []);
},
/**
* Handles the Toggle Dev Mode button.
* @param {Event} e Change event.
* @private
*/
handleToggleDevMode_: function(e) {
var dev = $('dev');
if (!dev.classList.contains('dev-open')) {
// Make the Dev section visible.
dev.classList.add('dev-open');
dev.classList.remove('dev-closed');
$('load-unpacked').classList.add('dev-button-visible');
$('load-unpacked').classList.remove('dev-button-hidden');
$('pack-extension').classList.add('dev-button-visible');
$('pack-extension').classList.remove('dev-button-hidden');
$('update-extensions-now').classList.add('dev-button-visible');
$('update-extensions-now').classList.remove('dev-button-hidden');
} else {
// Hide the Dev section.
dev.classList.add('dev-closed');
dev.classList.remove('dev-open');
$('load-unpacked').classList.add('dev-button-hidden');
$('load-unpacked').classList.remove('dev-button-visible');
$('pack-extension').classList.add('dev-button-hidden');
$('pack-extension').classList.remove('dev-button-visible');
$('update-extensions-now').classList.add('dev-button-hidden');
$('update-extensions-now').classList.remove('dev-button-visible');
}
chrome.send('extensionSettingsToggleDeveloperMode', []);
},
};
/**
* Called by the dom_ui_ to re-populate the page with data representing
* the current state of installed extensions.
*/
ExtensionSettings.returnExtensionsData = function(extensionsData) {
webui_responded_ = true;
$('no-extensions').hidden = true;
$('suggest-gallery').hidden = true;
$('get-more-extensions-container').hidden = true;
if (extensionsData.extensions.length > 0) {
// Enforce order specified in the data or (if equal) then sort by
// extension name (case-insensitive).
extensionsData.extensions.sort(function(a, b) {
if (a.order == b.order) {
a = a.name.toLowerCase();
b = b.name.toLowerCase();
return a < b ? -1 : (a > b ? 1 : 0);
} else {
return a.order < b.order ? -1 : 1;
}
});
$('get-more-extensions-container').hidden = false;
} else {
$('no-extensions').hidden = false;
$('suggest-gallery').hidden = false;
}
ExtensionsList.prototype.data_ = extensionsData;
var extensionList = $('extension-settings-list');
ExtensionsList.decorate(extensionList);
}
// Export
return {
ExtensionSettings: ExtensionSettings
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/**
* This is the absolute difference maintained between standard and
* fixed-width font sizes. Refer http://crbug.com/91922.
*/
const SIZE_DIFFERENCE_FIXED_STANDARD = 3;
/**
* FontSettings class
* Encapsulated handling of the 'Fonts and Encoding' page.
* @class
*/
function FontSettings() {
OptionsPage.call(this,
'fonts',
templateData.fontSettingsPageTabTitle,
'font-settings');
}
cr.addSingletonGetter(FontSettings);
FontSettings.prototype = {
__proto__: OptionsPage.prototype,
/**
* Initialize the page.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var standardFontRange = $('standard-font-size');
standardFontRange.valueMap = [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20,
22, 24, 26, 28, 30, 32, 34, 36, 40, 44, 48, 56, 64, 72];
standardFontRange.continuous = false;
standardFontRange.notifyChange = this.standardRangeChanged_.bind(this);
standardFontRange.notifyPrefChange =
this.standardFontSizeChanged_.bind(this);
var minimumFontRange = $('minimum-font-size');
minimumFontRange.valueMap = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 20, 22, 24];
minimumFontRange.continuous = false;
minimumFontRange.notifyChange = this.minimumRangeChanged_.bind(this);
minimumFontRange.notifyPrefChange =
this.minimumFontSizeChanged_.bind(this);
var placeholder = localStrings.getString('fontSettingsPlaceholder');
var elements = [$('standard-font-family'), $('serif-font-family'),
$('sans-serif-font-family'), $('fixed-font-family'),
$('font-encoding')];
elements.forEach(function(el) {
el.appendChild(new Option(placeholder));
el.setDisabled('noFontsAvailable', true);
});
},
/**
* Called by the options page when this page has been shown.
*/
didShowPage: function() {
// The fonts list may be large so we only load it when this page is
// loaded for the first time. This makes opening the options window
// faster and consume less memory if the user never opens the fonts
// dialog.
if (!this.hasShown) {
chrome.send('fetchFontsData');
this.hasShown = true;
}
},
/**
* Called as the user changes the standard font size. This allows for
* reflecting the change in the UI before the preference has been changed.
* @param {Element} el The slider input element.
* @param {number} value The mapped value currently set by the slider.
* @private
*/
standardRangeChanged_: function(el, value) {
var fontSampleEl = $('standard-font-sample');
this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily,
true);
fontSampleEl = $('serif-font-sample');
this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily,
true);
fontSampleEl = $('sans-serif-font-sample');
this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily,
true);
fontSampleEl = $('fixed-font-sample');
this.setUpFontSample_(fontSampleEl,
value - SIZE_DIFFERENCE_FIXED_STANDARD,
fontSampleEl.style.fontFamily, false);
},
/**
* Sets the 'default_fixed_font_size' preference when the standard font
* size has been changed by the user.
* @param {Element} el The slider input element.
* @param {number} value The mapped value that has been saved.
* @private
*/
standardFontSizeChanged_: function(el, value) {
Preferences.setIntegerPref('webkit.webprefs.default_fixed_font_size',
value - SIZE_DIFFERENCE_FIXED_STANDARD, '');
},
/**
* Called as the user changes the miniumum font size. This allows for
* reflecting the change in the UI before the preference has been changed.
* @param {Element} el The slider input element.
* @param {number} value The mapped value currently set by the slider.
* @private
*/
minimumRangeChanged_: function(el, value) {
var fontSampleEl = $('minimum-font-sample');
this.setUpFontSample_(fontSampleEl, value, fontSampleEl.style.fontFamily,
true);
},
/**
* Sets the 'minimum_logical_font_size' preference when the minimum font
* size has been changed by the user.
* @param {Element} el The slider input element.
* @param {number} value The mapped value that has been saved.
* @private
*/
minimumFontSizeChanged_: function(el, value) {
Preferences.setIntegerPref('webkit.webprefs.minimum_logical_font_size',
value, '');
},
/**
* Sets the text, font size and font family of the sample text.
* @param {Element} el The div containing the sample text.
* @param {number} size The font size of the sample text.
* @param {string} font The font family of the sample text.
* @param {bool} showSize True if the font size should appear in the sample.
* @private
*/
setUpFontSample_: function(el, size, font, showSize) {
var prefix = showSize ? (size + ': ') : '';
el.textContent = prefix +
localStrings.getString('fontSettingsLoremIpsum');
el.style.fontSize = size + 'px';
if (font)
el.style.fontFamily = font;
},
/**
* Populates a select list and selects the specified item.
* @param {Element} element The select element to populate.
* @param {Array} items The array of items from which to populate.
* @param {string} selectedValue The selected item.
* @private
*/
populateSelect_: function(element, items, selectedValue) {
// Remove any existing content.
element.textContent = '';
// Insert new child nodes into select element.
var value, text, selected, option;
for (var i = 0; i < items.length; i++) {
value = items[i][0];
text = items[i][1];
if (text) {
selected = value == selectedValue;
element.appendChild(new Option(text, value, false, selected));
} else {
element.appendChild(document.createElement('hr'));
}
}
element.setDisabled('noFontsAvailable', false);
}
};
// Chrome callbacks
FontSettings.setFontsData = function(fonts, encodings, selectedValues) {
FontSettings.getInstance().populateSelect_($('standard-font-family'), fonts,
selectedValues[0]);
FontSettings.getInstance().populateSelect_($('serif-font-family'), fonts,
selectedValues[1]);
FontSettings.getInstance().populateSelect_($('sans-serif-font-family'),
fonts, selectedValues[2]);
FontSettings.getInstance().populateSelect_($('fixed-font-family'), fonts,
selectedValues[3]);
FontSettings.getInstance().populateSelect_($('font-encoding'), encodings,
selectedValues[4]);
};
FontSettings.setUpStandardFontSample = function(font, size) {
FontSettings.getInstance().setUpFontSample_($('standard-font-sample'), size,
font, true);
};
FontSettings.setUpSerifFontSample = function(font, size) {
FontSettings.getInstance().setUpFontSample_($('serif-font-sample'), size,
font, true);
};
FontSettings.setUpSansSerifFontSample = function(font, size) {
FontSettings.getInstance().setUpFontSample_($('sans-serif-font-sample'),
size, font, true);
};
FontSettings.setUpFixedFontSample = function(font, size) {
FontSettings.getInstance().setUpFontSample_($('fixed-font-sample'),
size, font, false);
};
FontSettings.setUpMinimumFontSample = function(size) {
// If size is less than 6, represent it as six in the sample to account
// for the minimum logical font size.
if (size < 6)
size = 6;
FontSettings.getInstance().setUpFontSample_($('minimum-font-sample'), size,
null, true);
};
// Export
return {
FontSettings: FontSettings
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
/////////////////////////////////////////////////////////////////////////////
// HandlerOptions class:
/**
* Encapsulated handling of handler options page.
* @constructor
*/
function HandlerOptions() {
this.activeNavTab = null;
OptionsPage.call(this,
'handlers',
templateData.handlersPageTabTitle,
'handler-options');
}
cr.addSingletonGetter(HandlerOptions);
HandlerOptions.prototype = {
__proto__: OptionsPage.prototype,
/**
* The handlers list.
* @type {DeletableItemList}
* @private
*/
handlersList_: null,
/** @inheritDoc */
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
this.createHandlersList_();
},
/**
* Creates, decorates and initializes the handlers list.
* @private
*/
createHandlersList_: function() {
this.handlersList_ = $('handlers-list');
options.HandlersList.decorate(this.handlersList_);
this.handlersList_.autoExpands = true;
this.ignoredHandlersList_ = $('ignored-handlers-list');
options.IgnoredHandlersList.decorate(this.ignoredHandlersList_);
this.ignoredHandlersList_.autoExpands = true;
},
};
/**
* Sets the list of handlers shown by the view.
* @param handlers to be shown in the view.
*/
HandlerOptions.setHandlers = function(handlers) {
$('handlers-list').setHandlers(handlers);
};
/**
* Sets the list of ignored handlers shown by the view.
* @param handlers to be shown in the view.
*/
HandlerOptions.setIgnoredHandlers = function(handlers) {
$('ignored-handlers-section').hidden = handlers.length == 0;
$('ignored-handlers-list').setHandlers(handlers);
};
return {
HandlerOptions: HandlerOptions
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const ArrayDataModel = cr.ui.ArrayDataModel;
const List = cr.ui.List;
const ListItem = cr.ui.ListItem;
const HandlerOptions = options.HandlerOptions;
const DeletableItem = options.DeletableItem;
const DeletableItemList = options.DeletableItemList;
const localStrings = new LocalStrings();
/**
* Creates a new ignored protocol / content handler list item.
*
* Accepts values in the form
* ['mailto', 'http://www.thesite.com/%s', 'The title of the protocol'],
* @param {Object} entry A dictionary describing the handlers for a given
* protocol.
* @constructor
* @extends {cr.ui.DeletableItemList}
*/
function IgnoredHandlersListItem(entry) {
var el = cr.doc.createElement('div');
el.dataItem = entry;
el.__proto__ = IgnoredHandlersListItem.prototype;
el.decorate();
return el;
}
IgnoredHandlersListItem.prototype = {
__proto__: DeletableItem.prototype,
/** @inheritDoc */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
// Protocol.
var protocolElement = document.createElement('div');
protocolElement.textContent = this.dataItem[0];
protocolElement.className = 'handlers-type-column';
this.contentElement_.appendChild(protocolElement);
// Site title.
var titleElement = document.createElement('div');
titleElement.textContent = this.dataItem[2];
titleElement.className = 'handlers-site-column';
titleElement.title = this.dataItem[1];
this.contentElement_.appendChild(titleElement);
},
};
var IgnoredHandlersList = cr.ui.define('list');
IgnoredHandlersList.prototype = {
__proto__: DeletableItemList.prototype,
createItem: function(entry) {
return new IgnoredHandlersListItem(entry);
},
deleteItemAtIndex: function(index) {
chrome.send('removeIgnoredHandler', [this.dataModel.item(index)]);
},
/**
* The length of the list.
*/
get length() {
return this.dataModel.length;
},
/**
* Set the protocol handlers displayed by this list. See
* IgnoredHandlersListItem for an example of the format the list should
* take.
*
* @param {Object} list A list of ignored protocol handlers.
*/
setHandlers: function(list) {
this.dataModel = new ArrayDataModel(list);
},
};
/**
* Creates a new protocol / content handler list item.
*
* Accepts values in the form
* { protocol: 'mailto',
* handlers: [
* ['mailto', 'http://www.thesite.com/%s', 'The title of the protocol'],
* ...,
* ],
* }
* @param {Object} entry A dictionary describing the handlers for a given
* protocol.
* @constructor
* @extends {cr.ui.ListItem}
*/
function HandlerListItem(entry) {
var el = cr.doc.createElement('div');
el.dataItem = entry;
el.__proto__ = HandlerListItem.prototype;
el.decorate();
return el;
}
HandlerListItem.prototype = {
__proto__: ListItem.prototype,
buildWidget_: function(data, delegate) {
// Protocol.
var protocolElement = document.createElement('div');
protocolElement.textContent = data.protocol;
protocolElement.className = 'handlers-type-column';
this.appendChild(protocolElement);
// Handler selection.
var handlerElement = document.createElement('div');
var selectElement = document.createElement('select');
var defaultOptionElement = document.createElement('option');
defaultOptionElement.selected = data.default_handler == -1;
defaultOptionElement.textContent =
localStrings.getString('handlers_none_handler');
defaultOptionElement.value = -1;
selectElement.appendChild(defaultOptionElement);
for (var i = 0; i < data.handlers.length; ++i) {
var optionElement = document.createElement('option');
optionElement.selected = i == data.default_handler;
optionElement.textContent = data.handlers[i][2];
optionElement.value = i;
selectElement.appendChild(optionElement);
}
selectElement.addEventListener('change', function (e) {
var index = e.target.value;
if (index == -1) {
this.classList.add('none');
delegate.clearDefault(data.protocol);
} else {
handlerElement.classList.remove('none');
delegate.setDefault(data.handlers[index]);
}
});
handlerElement.appendChild(selectElement);
handlerElement.className = 'handlers-site-column';
if (data.default_handler == -1)
this.classList.add('none');
this.appendChild(handlerElement);
// Remove link.
var removeElement = document.createElement('div');
removeElement.textContent =
localStrings.getString('handlers_remove_link');
removeElement.addEventListener('click', function (e) {
var value = selectElement ? selectElement.value : 0;
delegate.removeHandler(value, data.handlers[value]);
});
removeElement.className = 'handlers-remove-column handlers-remove-link';
this.appendChild(removeElement);
},
/** @inheritDoc */
decorate: function() {
ListItem.prototype.decorate.call(this);
var self = this;
var delegate = {
removeHandler: function(index, handler) {
chrome.send('removeHandler', [handler]);
},
setDefault: function(handler) {
chrome.send('setDefault', [handler]);
},
clearDefault: function(protocol) {
chrome.send('clearDefault', [protocol]);
},
};
this.buildWidget_(this.dataItem, delegate);
},
};
/**
* Create a new passwords list.
* @constructor
* @extends {cr.ui.List}
*/
var HandlersList = cr.ui.define('list');
HandlersList.prototype = {
__proto__: List.prototype,
/** @inheritDoc */
createItem: function(entry) {
return new HandlerListItem(entry);
},
/**
* The length of the list.
*/
get length() {
return this.dataModel.length;
},
/**
* Set the protocol handlers displayed by this list.
* See HandlerListItem for an example of the format the list should take.
*
* @param {Object} list A list of protocols with their registered handlers.
*/
setHandlers: function(list) {
this.dataModel = new ArrayDataModel(list);
},
};
return {
IgnoredHandlersListItem: IgnoredHandlersListItem,
IgnoredHandlersList: IgnoredHandlersList,
HandlerListItem: HandlerListItem,
HandlersList: HandlersList,
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
/**
* ImportDataOverlay class
* Encapsulated handling of the 'Import Data' overlay page.
* @class
*/
function ImportDataOverlay() {
OptionsPage.call(this,
'importData',
templateData.importDataOverlayTabTitle,
'import-data-overlay');
}
cr.addSingletonGetter(ImportDataOverlay);
ImportDataOverlay.prototype = {
// Inherit from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to start preference initialization.
OptionsPage.prototype.initializePage.call(this);
var self = this;
var checkboxes =
document.querySelectorAll('#import-checkboxes input[type=checkbox]');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].onchange = function() {
self.validateCommitButton_();
};
}
$('import-browsers').onchange = function() {
self.updateCheckboxes_();
self.validateCommitButton_();
};
$('import-data-commit').onclick = function() {
chrome.send('importData', [
String($('import-browsers').selectedIndex),
String($('import-history').checked),
String($('import-favorites').checked),
String($('import-passwords').checked),
String($('import-search').checked)]);
};
$('import-data-cancel').onclick = function() {
ImportDataOverlay.dismiss();
};
$('import-data-show-bookmarks-bar').onchange = function() {
// Note: The callback 'toggleShowBookmarksBar' is handled within the
// browser options handler -- rather than the import data handler --
// as the implementation is shared by several clients.
chrome.send('toggleShowBookmarksBar');
}
$('import-data-confirm').onclick = function() {
ImportDataOverlay.dismiss();
};
// Form controls are disabled until the profile list has been loaded.
self.setControlsSensitive_(false);
},
/**
* Set enabled and checked state of the commit button.
* @private
*/
validateCommitButton_: function() {
var somethingToImport =
$('import-history').checked || $('import-favorites').checked ||
$('import-passwords').checked || $('import-search').checked;
$('import-data-commit').disabled = !somethingToImport;
},
/**
* Sets the sensitivity of all the checkboxes and the commit button.
* @private
*/
setControlsSensitive_: function(sensitive) {
var checkboxes =
document.querySelectorAll('#import-checkboxes input[type=checkbox]');
for (var i = 0; i < checkboxes.length; i++)
this.setUpCheckboxState_(checkboxes[i], sensitive);
$('import-data-commit').disabled = !sensitive;
},
/**
* Set enabled and checked states a checkbox element.
* @param {Object} checkbox A checkbox element.
* @param {boolean} enabled The enabled state of the chekbox.
* @private
*/
setUpCheckboxState_: function(checkbox, enabled) {
checkbox.setDisabled("noProfileData", !enabled);
},
/**
* Update the enabled and checked states of all checkboxes.
* @private
*/
updateCheckboxes_: function() {
var index = $('import-browsers').selectedIndex;
var browserProfile;
if (this.browserProfiles.length > index)
browserProfile = this.browserProfiles[index];
var importOptions = ['history', 'favorites', 'passwords', 'search'];
for (var i = 0; i < importOptions.length; i++) {
var checkbox = $('import-' + importOptions[i]);
var enable = browserProfile && browserProfile[importOptions[i]];
this.setUpCheckboxState_(checkbox, enable);
}
},
/**
* Update the supported browsers popup with given entries.
* @param {array} browsers List of supported browsers name.
* @private
*/
updateSupportedBrowsers_: function(browsers) {
this.browserProfiles = browsers;
var browserSelect = $('import-browsers');
browserSelect.remove(0); // Remove the 'Loading...' option.
browserSelect.textContent = '';
var browserCount = browsers.length;
if (browserCount == 0) {
var option = new Option(templateData.noProfileFound, 0);
browserSelect.appendChild(option);
this.setControlsSensitive_(false);
} else {
this.setControlsSensitive_(true);
for (var i = 0; i < browserCount; i++) {
var browser = browsers[i]
var option = new Option(browser['name'], browser['index']);
browserSelect.appendChild(option);
}
this.updateCheckboxes_();
this.validateCommitButton_();
}
},
/**
* Clear import prefs set when user checks/unchecks a checkbox so that each
* checkbox goes back to the default "checked" state (or alternatively, to
* the state set by a recommended policy).
* @private
*/
clearUserPrefs_: function() {
var importPrefs = ['import_history',
'import_bookmarks',
'import_saved_passwords',
'import_search_engine'];
for (var i = 0; i < importPrefs.length; i++)
Preferences.clearPref(importPrefs[i], undefined);
},
};
ImportDataOverlay.clearUserPrefs = function() {
ImportDataOverlay.getInstance().clearUserPrefs_();
};
/**
* Update the supported browsers popup with given entries.
* @param {array} list of supported browsers name.
*/
ImportDataOverlay.updateSupportedBrowsers = function(browsers) {
ImportDataOverlay.getInstance().updateSupportedBrowsers_(browsers);
};
/**
* Update the UI to reflect whether an import operation is in progress.
* @param {boolean} state True if an import operation is in progress.
*/
ImportDataOverlay.setImportingState = function(state) {
var checkboxes =
document.querySelectorAll('#import-checkboxes input[type=checkbox]');
for (var i = 0; i < checkboxes.length; i++)
checkboxes[i].setDisabled("Importing", state);
if (!state)
ImportDataOverlay.getInstance().updateCheckboxes_();
$('import-browsers').disabled = state;
$('import-throbber').style.visibility = state ? "visible" : "hidden";
ImportDataOverlay.getInstance().validateCommitButton_();
};
/**
* Remove the import overlay from display.
*/
ImportDataOverlay.dismiss = function() {
ImportDataOverlay.clearUserPrefs();
OptionsPage.closeOverlay();
};
/**
* Show a message confirming the success of the import operation.
*/
ImportDataOverlay.confirmSuccess = function() {
var showBookmarksMessage = $('import-favorites').checked;
ImportDataOverlay.setImportingState(false);
$('import-data-configure').hidden = true;
$('import-data-success').hidden = false;
$('import-find-your-bookmarks').hidden = !showBookmarksMessage;
};
// Export
return {
ImportDataOverlay: ImportDataOverlay
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
function InstantConfirmOverlay() {
OptionsPage.call(this, 'instantConfirm',
templateData.instantConfirmTitle,
'instantConfirmOverlay');
};
cr.addSingletonGetter(InstantConfirmOverlay);
InstantConfirmOverlay.prototype = {
// Inherit from OptionsPage.
__proto__: OptionsPage.prototype,
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
$('instantConfirmCancel').onclick = function() {
OptionsPage.closeOverlay();
$('instantEnabledCheckbox').checked = false;
};
$('instantConfirmOk').onclick = function() {
OptionsPage.closeOverlay();
chrome.send('enableInstant');
};
},
};
// Export
return {
InstantConfirmOverlay: InstantConfirmOverlay
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
///////////////////////////////////////////////////////////////////////////////
// AddLanguageOverlay class:
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
/**
* Encapsulated handling of ChromeOS add language overlay page.
* @constructor
*/
function AddLanguageOverlay() {
OptionsPage.call(this, 'addLanguage',
localStrings.getString('add_button'),
'add-language-overlay-page');
}
cr.addSingletonGetter(AddLanguageOverlay);
AddLanguageOverlay.prototype = {
// Inherit AddLanguageOverlay from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* Initializes AddLanguageOverlay page.
* Calls base class implementation to starts preference initialization.
*/
initializePage: function() {
// Call base class implementation to starts preference initialization.
OptionsPage.prototype.initializePage.call(this);
// Set up the cancel button.
$('add-language-overlay-cancel-button').onclick = function(e) {
OptionsPage.closeOverlay();
};
// Create the language list with which users can add a language.
var addLanguageList = $('add-language-overlay-language-list');
var languageListData = templateData.languageList;
for (var i = 0; i < languageListData.length; i++) {
var language = languageListData[i];
var displayText = language.displayName;
// If the native name is different, add it.
if (language.displayName != language.nativeDisplayName) {
displayText += ' - ' + language.nativeDisplayName;
}
if (cr.isChromeOS) {
var button = document.createElement('button');
button.className = 'link-button';
button.textContent = displayText;
button.languageCode = language.code;
var li = document.createElement('li');
li.languageCode = language.code;
li.appendChild(button);
addLanguageList.appendChild(li);
} else {
var option = document.createElement('option');
option.value = language.code;
option.textContent = displayText;
addLanguageList.appendChild(option);
}
}
},
};
return {
AddLanguageOverlay: AddLanguageOverlay
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const ArrayDataModel = cr.ui.ArrayDataModel;
const DeletableItem = options.DeletableItem;
const DeletableItemList = options.DeletableItemList;
const List = cr.ui.List;
const ListItem = cr.ui.ListItem;
const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
/**
* Creates a new Language list item.
* @param {String} languageCode the languageCode.
* @constructor
* @extends {DeletableItem.ListItem}
*/
function LanguageListItem(languageCode) {
var el = cr.doc.createElement('li');
el.__proto__ = LanguageListItem.prototype;
el.languageCode_ = languageCode;
el.decorate();
return el;
};
LanguageListItem.prototype = {
__proto__: DeletableItem.prototype,
/**
* The language code of this language.
* @type {String}
* @private
*/
languageCode_: null,
/** @inheritDoc */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
var languageCode = this.languageCode_;
var languageOptions = options.LanguageOptions.getInstance();
this.deletable = languageOptions.languageIsDeletable(languageCode);
this.languageCode = languageCode;
this.languageName = cr.doc.createElement('div');
this.languageName.className = 'language-name';
this.languageName.textContent =
LanguageList.getDisplayNameFromLanguageCode(languageCode);
this.contentElement.appendChild(this.languageName);
this.title =
LanguageList.getNativeDisplayNameFromLanguageCode(languageCode);
this.draggable = true;
},
};
/**
* Creates a new language list.
* @param {Object=} opt_propertyBag Optional properties.
* @constructor
* @extends {cr.ui.List}
*/
var LanguageList = cr.ui.define('list');
/**
* Gets display name from the given language code.
* @param {string} languageCode Language code (ex. "fr").
*/
LanguageList.getDisplayNameFromLanguageCode = function(languageCode) {
// Build the language code to display name dictionary at first time.
if (!this.languageCodeToDisplayName_) {
this.languageCodeToDisplayName_ = {};
var languageList = templateData.languageList;
for (var i = 0; i < languageList.length; i++) {
var language = languageList[i];
this.languageCodeToDisplayName_[language.code] = language.displayName;
}
}
return this.languageCodeToDisplayName_[languageCode];
}
/**
* Gets native display name from the given language code.
* @param {string} languageCode Language code (ex. "fr").
*/
LanguageList.getNativeDisplayNameFromLanguageCode = function(languageCode) {
// Build the language code to display name dictionary at first time.
if (!this.languageCodeToNativeDisplayName_) {
this.languageCodeToNativeDisplayName_ = {};
var languageList = templateData.languageList;
for (var i = 0; i < languageList.length; i++) {
var language = languageList[i];
this.languageCodeToNativeDisplayName_[language.code] =
language.nativeDisplayName;
}
}
return this.languageCodeToNativeDisplayName_[languageCode];
}
/**
* Returns true if the given language code is valid.
* @param {string} languageCode Language code (ex. "fr").
*/
LanguageList.isValidLanguageCode = function(languageCode) {
// Having the display name for the language code means that the
// language code is valid.
if (LanguageList.getDisplayNameFromLanguageCode(languageCode)) {
return true;
}
return false;
}
LanguageList.prototype = {
__proto__: DeletableItemList.prototype,
// The list item being dragged.
draggedItem: null,
// The drop position information: "below" or "above".
dropPos: null,
// The preference is a CSV string that describes preferred languages
// in Chrome OS. The language list is used for showing the language
// list in "Language and Input" options page.
preferredLanguagesPref: 'settings.language.preferred_languages',
// The preference is a CSV string that describes accept languages used
// for content negotiation. To be more precise, the list will be used
// in "Accept-Language" header in HTTP requests.
acceptLanguagesPref: 'intl.accept_languages',
/** @inheritDoc */
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
this.selectionModel = new ListSingleSelectionModel;
// HACK(arv): http://crbug.com/40902
window.addEventListener('resize', this.redraw.bind(this));
// Listen to pref change.
if (cr.isChromeOS) {
Preferences.getInstance().addEventListener(this.preferredLanguagesPref,
this.handlePreferredLanguagesPrefChange_.bind(this));
} else {
Preferences.getInstance().addEventListener(this.acceptLanguagesPref,
this.handleAcceptLanguagesPrefChange_.bind(this));
}
// Listen to drag and drop events.
this.addEventListener('dragstart', this.handleDragStart_.bind(this));
this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
this.addEventListener('dragover', this.handleDragOver_.bind(this));
this.addEventListener('drop', this.handleDrop_.bind(this));
this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
},
createItem: function(languageCode) {
return new LanguageListItem(languageCode);
},
/*
* For each item, determines whether it's deletable.
*/
updateDeletable: function() {
var items = this.items;
for (var i = 0; i < items.length; ++i) {
var item = items[i];
var languageCode = item.languageCode;
var languageOptions = options.LanguageOptions.getInstance();
item.deletable = languageOptions.languageIsDeletable(languageCode);
}
},
/*
* Adds a language to the language list.
* @param {string} languageCode language code (ex. "fr").
*/
addLanguage: function(languageCode) {
// It shouldn't happen but ignore the language code if it's
// null/undefined, or already present.
if (!languageCode || this.dataModel.indexOf(languageCode) >= 0) {
return;
}
this.dataModel.push(languageCode);
// Select the last item, which is the language added.
this.selectionModel.selectedIndex = this.dataModel.length - 1;
this.savePreference_();
},
/*
* Gets the language codes of the currently listed languages.
*/
getLanguageCodes: function() {
return this.dataModel.slice();
},
/*
* Gets the language code of the selected language.
*/
getSelectedLanguageCode: function() {
return this.selectedItem;
},
/*
* Selects the language by the given language code.
* @returns {boolean} True if the operation is successful.
*/
selectLanguageByCode: function(languageCode) {
var index = this.dataModel.indexOf(languageCode);
if (index >= 0) {
this.selectionModel.selectedIndex = index;
return true;
}
return false;
},
/** @inheritDoc */
deleteItemAtIndex: function(index) {
if (index >= 0) {
this.dataModel.splice(index, 1);
// Once the selected item is removed, there will be no selected item.
// Select the item pointed by the lead index.
index = this.selectionModel.leadIndex;
this.savePreference_();
}
return index;
},
/*
* Computes the target item of drop event.
* @param {Event} e The drop or dragover event.
* @private
*/
getTargetFromDropEvent_ : function(e) {
var target = e.target;
// e.target may be an inner element of the list item
while (target != null && !(target instanceof ListItem)) {
target = target.parentNode;
}
return target;
},
/*
* Handles the dragstart event.
* @param {Event} e The dragstart event.
* @private
*/
handleDragStart_: function(e) {
var target = e.target;
// ListItem should be the only draggable element type in the page,
// but just in case.
if (target instanceof ListItem) {
this.draggedItem = target;
e.dataTransfer.effectAllowed = 'move';
// We need to put some kind of data in the drag or it will be
// ignored. Use the display name in case the user drags to a text
// field or the desktop.
e.dataTransfer.setData('text/plain', target.title);
}
},
/*
* Handles the dragenter event.
* @param {Event} e The dragenter event.
* @private
*/
handleDragEnter_: function(e) {
e.preventDefault();
},
/*
* Handles the dragover event.
* @param {Event} e The dragover event.
* @private
*/
handleDragOver_: function(e) {
var dropTarget = this.getTargetFromDropEvent_(e);
// Determines whether the drop target is to accept the drop.
// The drop is only successful on another ListItem.
if (!(dropTarget instanceof ListItem) ||
dropTarget == this.draggedItem) {
this.hideDropMarker_();
return;
}
// Compute the drop postion. Should we move the dragged item to
// below or above the drop target?
var rect = dropTarget.getBoundingClientRect();
var dy = e.clientY - rect.top;
var yRatio = dy / rect.height;
var dropPos = yRatio <= .5 ? 'above' : 'below';
this.dropPos = dropPos;
this.showDropMarker_(dropTarget, dropPos);
e.preventDefault();
},
/*
* Handles the drop event.
* @param {Event} e The drop event.
* @private
*/
handleDrop_: function(e) {
var dropTarget = this.getTargetFromDropEvent_(e);
this.hideDropMarker_();
// Delete the language from the original position.
var languageCode = this.draggedItem.languageCode;
var originalIndex = this.dataModel.indexOf(languageCode);
this.dataModel.splice(originalIndex, 1);
// Insert the language to the new position.
var newIndex = this.dataModel.indexOf(dropTarget.languageCode);
if (this.dropPos == 'below')
newIndex += 1;
this.dataModel.splice(newIndex, 0, languageCode);
// The cursor should move to the moved item.
this.selectionModel.selectedIndex = newIndex;
// Save the preference.
this.savePreference_();
},
/*
* Handles the dragleave event.
* @param {Event} e The dragleave event
* @private
*/
handleDragLeave_ : function(e) {
this.hideDropMarker_();
},
/*
* Shows and positions the marker to indicate the drop target.
* @param {HTMLElement} target The current target list item of drop
* @param {string} pos 'below' or 'above'
* @private
*/
showDropMarker_ : function(target, pos) {
window.clearTimeout(this.hideDropMarkerTimer_);
var marker = $('language-options-list-dropmarker');
var rect = target.getBoundingClientRect();
var markerHeight = 8;
if (pos == 'above') {
marker.style.top = (rect.top - markerHeight/2) + 'px';
} else {
marker.style.top = (rect.bottom - markerHeight/2) + 'px';
}
marker.style.width = rect.width + 'px';
marker.style.left = rect.left + 'px';
marker.style.display = 'block';
},
/*
* Hides the drop marker.
* @private
*/
hideDropMarker_ : function() {
// Hide the marker in a timeout to reduce flickering as we move between
// valid drop targets.
window.clearTimeout(this.hideDropMarkerTimer_);
this.hideDropMarkerTimer_ = window.setTimeout(function() {
$('language-options-list-dropmarker').style.display = '';
}, 100);
},
/**
* Handles preferred languages pref change.
* @param {Event} e The change event object.
* @private
*/
handlePreferredLanguagesPrefChange_: function(e) {
var languageCodesInCsv = e.value.value;
var languageCodes = languageCodesInCsv.split(',');
// Add the UI language to the initial list of languages. This is to avoid
// a bug where the UI language would be removed from the preferred
// language list by sync on first login.
// See: crosbug.com/14283
languageCodes.push(navigator.language);
languageCodes = this.filterBadLanguageCodes_(languageCodes);
this.load_(languageCodes);
},
/**
* Handles accept languages pref change.
* @param {Event} e The change event object.
* @private
*/
handleAcceptLanguagesPrefChange_: function(e) {
var languageCodesInCsv = e.value.value;
var languageCodes = this.filterBadLanguageCodes_(
languageCodesInCsv.split(','));
this.load_(languageCodes);
},
/**
* Loads given language list.
* @param {Array} languageCodes List of language codes.
* @private
*/
load_: function(languageCodes) {
// Preserve the original selected index. See comments below.
var originalSelectedIndex = (this.selectionModel ?
this.selectionModel.selectedIndex : -1);
this.dataModel = new ArrayDataModel(languageCodes);
if (originalSelectedIndex >= 0 &&
originalSelectedIndex < this.dataModel.length) {
// Restore the original selected index if the selected index is
// valid after the data model is loaded. This is neeeded to keep
// the selected language after the languge is added or removed.
this.selectionModel.selectedIndex = originalSelectedIndex;
// The lead index should be updated too.
this.selectionModel.leadIndex = originalSelectedIndex;
} else if (this.dataModel.length > 0){
// Otherwise, select the first item if it's not empty.
// Note that ListSingleSelectionModel won't select an item
// automatically, hence we manually select the first item here.
this.selectionModel.selectedIndex = 0;
}
},
/**
* Saves the preference.
*/
savePreference_: function() {
// Encode the language codes into a CSV string.
if (cr.isChromeOS)
Preferences.setStringPref(this.preferredLanguagesPref,
this.dataModel.slice().join(','));
// Save the same language list as accept languages preference as
// well, but we need to expand the language list, to make it more
// acceptable. For instance, some web sites don't understand 'en-US'
// but 'en'. See crosbug.com/9884.
var acceptLanguages = this.expandLanguageCodes(this.dataModel.slice());
Preferences.setStringPref(this.acceptLanguagesPref,
acceptLanguages.join(','));
cr.dispatchSimpleEvent(this, 'save');
},
/**
* Expands language codes to make these more suitable for Accept-Language.
* Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
* 'en' won't appear twice as this function eliminates duplicates.
* @param {Array} languageCodes List of language codes.
* @private
*/
expandLanguageCodes: function(languageCodes) {
var expandedLanguageCodes = [];
var seen = {}; // Used to eliminiate duplicates.
for (var i = 0; i < languageCodes.length; i++) {
var languageCode = languageCodes[i];
if (!(languageCode in seen)) {
expandedLanguageCodes.push(languageCode);
seen[languageCode] = true;
}
var parts = languageCode.split('-');
if (!(parts[0] in seen)) {
expandedLanguageCodes.push(parts[0]);
seen[parts[0]] = true;
}
}
return expandedLanguageCodes;
},
/**
* Filters bad language codes in case bad language codes are
* stored in the preference. Removes duplicates as well.
* @param {Array} languageCodes List of language codes.
* @private
*/
filterBadLanguageCodes_: function(languageCodes) {
var filteredLanguageCodes = [];
var seen = {};
for (var i = 0; i < languageCodes.length; i++) {
// Check if the the language code is valid, and not
// duplicate. Otherwise, skip it.
if (LanguageList.isValidLanguageCode(languageCodes[i]) &&
!(languageCodes[i] in seen)) {
filteredLanguageCodes.push(languageCodes[i]);
seen[languageCodes[i]] = true;
}
}
return filteredLanguageCodes;
},
};
return {
LanguageList: LanguageList,
LanguageListItem: LanguageListItem
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(kochi): Generalize the notification as a component and put it
// in js/cr/ui/notification.js .
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
const LanguageList = options.LanguageList;
// Some input methods like Chinese Pinyin have config pages.
// This is the map of the input method names to their config page names.
const INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME = {
'mozc': 'languageMozc',
'mozc-chewing': 'languageChewing',
'mozc-dv': 'languageMozc',
'mozc-hangul': 'languageHangul',
'mozc-jp': 'languageMozc',
'pinyin': 'languagePinyin',
'pinyin-dv': 'languagePinyin',
};
/////////////////////////////////////////////////////////////////////////////
// LanguageOptions class:
/**
* Encapsulated handling of ChromeOS language options page.
* @constructor
*/
function LanguageOptions(model) {
OptionsPage.call(this, 'languages', templateData.languagePageTabTitle,
'languagePage');
}
cr.addSingletonGetter(LanguageOptions);
// Inherit LanguageOptions from OptionsPage.
LanguageOptions.prototype = {
__proto__: OptionsPage.prototype,
/**
* Initializes LanguageOptions page.
* Calls base class implementation to starts preference initialization.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var languageOptionsList = $('language-options-list');
LanguageList.decorate(languageOptionsList);
languageOptionsList.addEventListener('change',
this.handleLanguageOptionsListChange_.bind(this));
languageOptionsList.addEventListener('save',
this.handleLanguageOptionsListSave_.bind(this));
this.addEventListener('visibleChange',
this.handleVisibleChange_.bind(this));
if (cr.isChromeOS) {
this.initializeInputMethodList_();
this.initializeLanguageCodeToInputMethodIdsMap_();
}
Preferences.getInstance().addEventListener(this.spellCheckDictionaryPref,
this.handleSpellCheckDictionaryPrefChange_.bind(this));
// Set up add button.
$('language-options-add-button').onclick = function(e) {
// Add the language without showing the overlay if it's specified in
// the URL hash (ex. lang_add=ja). Used for automated testing.
var match = document.location.hash.match(/\blang_add=([\w-]+)/);
if (match) {
var addLanguageCode = match[1];
$('language-options-list').addLanguage(addLanguageCode);
} else {
OptionsPage.navigateToPage('addLanguage');
}
};
if (cr.isChromeOS) {
// Listen to user clicks on the add language list.
var addLanguageList = $('add-language-overlay-language-list');
addLanguageList.addEventListener('click',
this.handleAddLanguageListClick_.bind(this));
} else {
// Listen to add language dialog ok button.
var addLanguageOkButton = $('add-language-overlay-ok-button');
addLanguageOkButton.addEventListener('click',
this.handleAddLanguageOkButtonClick_.bind(this));
// Show experimental features if enabled.
if (templateData.experimentalSpellCheckFeatures == 'true')
$('auto-spell-correction-option').hidden = false;
// Handle spell check enable/disable.
Preferences.getInstance().addEventListener(this.enableSpellCheckPref,
this.updateEnableSpellCheck_.bind(this));
}
// Listen to user clicks on the "Change touch keyboard settings..."
// button (if it exists).
var virtualKeyboardButton = $('language-options-virtual-keyboard');
if (virtualKeyboardButton) {
// TODO(yusukes): would be better to hide the button if no virtual
// keyboard is registered.
virtualKeyboardButton.onclick = function(e) {
OptionsPage.navigateToPage('virtualKeyboards');
};
}
},
// The preference is a boolean that enables/disables spell checking.
enableSpellCheckPref: 'browser.enable_spellchecking',
// The preference is a CSV string that describes preload engines
// (i.e. active input methods).
preloadEnginesPref: 'settings.language.preload_engines',
// The list of preload engines, like ['mozc', 'pinyin'].
preloadEngines_: [],
// The preference is a string that describes the spell check
// dictionary language, like "en-US".
spellCheckDictionaryPref: 'spellcheck.dictionary',
spellCheckDictionary_: "",
// The map of language code to input method IDs, like:
// {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
languageCodeToInputMethodIdsMap_: {},
/**
* Initializes the input method list.
*/
initializeInputMethodList_: function() {
var inputMethodList = $('language-options-input-method-list');
var inputMethodListData = templateData.inputMethodList;
// Add all input methods, but make all of them invisible here. We'll
// change the visibility in handleLanguageOptionsListChange_() based
// on the selected language. Note that we only have less than 100
// input methods, so creating DOM nodes at once here should be ok.
for (var i = 0; i < inputMethodListData.length; i++) {
var inputMethod = inputMethodListData[i];
var input = document.createElement('input');
input.type = 'checkbox';
input.inputMethodId = inputMethod.id;
// Listen to user clicks.
input.addEventListener('click',
this.handleCheckboxClick_.bind(this));
var label = document.createElement('label');
label.appendChild(input);
// Adding a space between the checkbox and the text. This is a bit
// dirty, but we rely on a space character for all other checkboxes.
label.appendChild(document.createTextNode(
' ' + inputMethod.displayName));
label.style.display = 'none';
label.languageCodeSet = inputMethod.languageCodeSet;
// Add the configure button if the config page is present for this
// input method.
if (inputMethod.id in INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME) {
var pageName = INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME[inputMethod.id];
var button = this.createConfigureInputMethodButton_(inputMethod.id,
pageName);
label.appendChild(button);
}
inputMethodList.appendChild(label);
}
// Listen to pref change once the input method list is initialized.
Preferences.getInstance().addEventListener(this.preloadEnginesPref,
this.handlePreloadEnginesPrefChange_.bind(this));
},
/**
* Creates a configure button for the given input method ID.
* @param {string} inputMethodId Input method ID (ex. "pinyin").
* @param {string} pageName Name of the config page (ex. "languagePinyin").
* @private
*/
createConfigureInputMethodButton_: function(inputMethodId, pageName) {
var button = document.createElement('button');
button.textContent = localStrings.getString('configure');
button.onclick = function(e) {
// Prevent the default action (i.e. changing the checked property
// of the checkbox). The button click here should not be handled
// as checkbox click.
e.preventDefault();
chrome.send('inputMethodOptionsOpen', [inputMethodId]);
OptionsPage.navigateToPage(pageName);
}
return button;
},
/**
* Handles OptionsPage's visible property change event.
* @param {Event} e Property change event.
* @private
*/
handleVisibleChange_: function(e) {
if (this.visible) {
$('language-options-list').redraw();
chrome.send('languageOptionsOpen');
}
},
/**
* Handles languageOptionsList's change event.
* @param {Event} e Change event.
* @private
*/
handleLanguageOptionsListChange_: function(e) {
var languageOptionsList = $('language-options-list');
var languageCode = languageOptionsList.getSelectedLanguageCode();
// Select the language if it's specified in the URL hash (ex. lang=ja).
// Used for automated testing.
var match = document.location.hash.match(/\blang=([\w-]+)/);
if (match) {
var specifiedLanguageCode = match[1];
if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
languageCode = specifiedLanguageCode;
}
}
this.updateSelectedLanguageName_(languageCode);
if (cr.isWindows || cr.isChromeOS)
this.updateUiLanguageButton_(languageCode);
this.updateSpellCheckLanguageButton_(languageCode);
if (cr.isChromeOS)
this.updateInputMethodList_(languageCode);
this.updateLanguageListInAddLanguageOverlay_();
},
/**
* Handles languageOptionsList's save event.
* @param {Event} e Save event.
* @private
*/
handleLanguageOptionsListSave_: function(e) {
if (cr.isChromeOS) {
// Sort the preload engines per the saved languages before save.
this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
this.savePreloadEnginesPref_();
}
},
/**
* Sorts preloadEngines_ by languageOptionsList's order.
* @param {Array} preloadEngines List of preload engines.
* @return {Array} Returns sorted preloadEngines.
* @private
*/
sortPreloadEngines_: function(preloadEngines) {
// For instance, suppose we have two languages and associated input
// methods:
//
// - Korean: hangul
// - Chinese: pinyin
//
// The preloadEngines preference should look like "hangul,pinyin".
// If the user reverse the order, the preference should be reorderd
// to "pinyin,hangul".
var languageOptionsList = $('language-options-list');
var languageCodes = languageOptionsList.getLanguageCodes();
// Convert the list into a dictonary for simpler lookup.
var preloadEngineSet = {};
for (var i = 0; i < preloadEngines.length; i++) {
preloadEngineSet[preloadEngines[i]] = true;
}
// Create the new preload engine list per the language codes.
var newPreloadEngines = [];
for (var i = 0; i < languageCodes.length; i++) {
var languageCode = languageCodes[i];
var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
languageCode];
// Check if we have active input methods associated with the language.
for (var j = 0; j < inputMethodIds.length; j++) {
var inputMethodId = inputMethodIds[j];
if (inputMethodId in preloadEngineSet) {
// If we have, add it to the new engine list.
newPreloadEngines.push(inputMethodId);
// And delete it from the set. This is necessary as one input
// method can be associated with more than one language thus
// we should avoid having duplicates in the new list.
delete preloadEngineSet[inputMethodId];
}
}
}
return newPreloadEngines;
},
/**
* Initializes the map of language code to input method IDs.
* @private
*/
initializeLanguageCodeToInputMethodIdsMap_: function() {
var inputMethodList = templateData.inputMethodList;
for (var i = 0; i < inputMethodList.length; i++) {
var inputMethod = inputMethodList[i];
for (var languageCode in inputMethod.languageCodeSet) {
if (languageCode in this.languageCodeToInputMethodIdsMap_) {
this.languageCodeToInputMethodIdsMap_[languageCode].push(
inputMethod.id);
} else {
this.languageCodeToInputMethodIdsMap_[languageCode] =
[inputMethod.id];
}
}
}
},
/**
* Updates the currently selected language name.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateSelectedLanguageName_: function(languageCode) {
var languageDisplayName = LanguageList.getDisplayNameFromLanguageCode(
languageCode);
var languageNativeDisplayName =
LanguageList.getNativeDisplayNameFromLanguageCode(languageCode);
// If the native name is different, add it.
if (languageDisplayName != languageNativeDisplayName) {
languageDisplayName += ' - ' + languageNativeDisplayName;
}
// Update the currently selected language name.
var languageName = $('language-options-language-name');
if (languageDisplayName) {
languageName.hidden = false;
languageName.textContent = languageDisplayName;
} else {
languageName.hidden = true;
}
},
/**
* Updates the UI language button.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateUiLanguageButton_: function(languageCode) {
var uiLanguageButton = $('language-options-ui-language-button');
// Check if the language code matches the current UI language.
if (languageCode == templateData.currentUiLanguageCode) {
// If it matches, the button just says that the UI language is
// currently in use.
uiLanguageButton.textContent =
localStrings.getString('is_displayed_in_this_language');
// Make it look like a text label.
uiLanguageButton.className = 'text-button';
// Remove the event listner.
uiLanguageButton.onclick = undefined;
} else if (languageCode in templateData.uiLanguageCodeSet) {
// If the language is supported as UI language, users can click on
// the button to change the UI language.
if (cr.commandLine.options['--bwsi']) {
// In the guest mode for ChromeOS, changing UI language does not make
// sense because it does not take effect after browser restart.
uiLanguageButton.hidden = true;
} else {
uiLanguageButton.textContent =
localStrings.getString('display_in_this_language');
uiLanguageButton.className = '';
// Send the change request to Chrome.
uiLanguageButton.onclick = function(e) {
chrome.send('uiLanguageChange', [languageCode]);
}
}
if (cr.isChromeOS) {
$('language-options-ui-restart-button').onclick = function(e) {
chrome.send('uiLanguageRestart');
}
}
} else {
// If the language is not supported as UI language, the button
// just says that Chromium OS cannot be displayed in this language.
uiLanguageButton.textContent =
localStrings.getString('cannot_be_displayed_in_this_language');
uiLanguageButton.className = 'text-button';
uiLanguageButton.onclick = undefined;
}
uiLanguageButton.style.display = 'block';
$('language-options-ui-notification-bar').style.display = 'none';
},
/**
* Updates the spell check language button.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateSpellCheckLanguageButton_: function(languageCode) {
var display = 'block';
var spellCheckLanguageButton = $(
'language-options-spell-check-language-button');
// Check if the language code matches the current spell check language.
if (languageCode == this.spellCheckDictionary_) {
// If it matches, the button just says that the spell check language is
// currently in use.
spellCheckLanguageButton.textContent =
localStrings.getString('is_used_for_spell_checking');
// Make it look like a text label.
spellCheckLanguageButton.className = 'text-button';
// Remove the event listner.
spellCheckLanguageButton.onclick = undefined;
} else if (languageCode in templateData.spellCheckLanguageCodeSet) {
// If the language is supported as spell check language, users can
// click on the button to change the spell check language.
spellCheckLanguageButton.textContent =
localStrings.getString('use_this_for_spell_checking');
spellCheckLanguageButton.className = '';
spellCheckLanguageButton.languageCode = languageCode;
// Add an event listner to the click event.
spellCheckLanguageButton.addEventListener('click',
this.handleSpellCheckLanguageButtonClick_.bind(this));
} else if (!languageCode) {
display = 'none';
} else {
// If the language is not supported as spell check language, the
// button just says that this language cannot be used for spell
// checking.
spellCheckLanguageButton.textContent =
localStrings.getString('cannot_be_used_for_spell_checking');
spellCheckLanguageButton.className = 'text-button';
spellCheckLanguageButton.onclick = undefined;
}
spellCheckLanguageButton.style.display = display;
$('language-options-ui-notification-bar').style.display = 'none';
},
/**
* Updates the input method list.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateInputMethodList_: function(languageCode) {
// Give one of the checkboxes or buttons focus, if it's specified in the
// URL hash (ex. focus=mozc). Used for automated testing.
var focusInputMethodId = -1;
var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
if (match) {
focusInputMethodId = match[1];
}
// Change the visibility of the input method list. Input methods that
// matches |languageCode| will become visible.
var inputMethodList = $('language-options-input-method-list');
var labels = inputMethodList.querySelectorAll('label');
for (var i = 0; i < labels.length; i++) {
var label = labels[i];
if (languageCode in label.languageCodeSet) {
label.style.display = 'block';
var input = label.childNodes[0];
// Give it focus if the ID matches.
if (input.inputMethodId == focusInputMethodId) {
input.focus();
}
} else {
label.style.display = 'none';
}
}
if (focusInputMethodId == 'add') {
$('language-options-add-button').focus();
}
},
/**
* Updates the language list in the add language overlay.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateLanguageListInAddLanguageOverlay_: function(languageCode) {
// Change the visibility of the language list in the add language
// overlay. Languages that are already active will become invisible,
// so that users don't add the same language twice.
var languageOptionsList = $('language-options-list');
var languageCodes = languageOptionsList.getLanguageCodes();
var languageCodeSet = {};
for (var i = 0; i < languageCodes.length; i++) {
languageCodeSet[languageCodes[i]] = true;
}
var addLanguageList = $('add-language-overlay-language-list');
var lis = addLanguageList.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
// The first child button knows the language code.
var button = lis[i].childNodes[0];
if (button.languageCode in languageCodeSet) {
lis[i].style.display = 'none';
} else {
lis[i].style.display = 'block';
}
}
},
/**
* Handles preloadEnginesPref change.
* @param {Event} e Change event.
* @private
*/
handlePreloadEnginesPrefChange_: function(e) {
var value = e.value.value;
this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
this.updateCheckboxesFromPreloadEngines_();
$('language-options-list').updateDeletable();
},
/**
* Handles input method checkbox's click event.
* @param {Event} e Click event.
* @private
*/
handleCheckboxClick_ : function(e) {
var checkbox = e.target;
if (this.preloadEngines_.length == 1 && !checkbox.checked) {
// Don't allow disabling the last input method.
this.showNotification_(
localStrings.getString('please_add_another_input_method'),
localStrings.getString('ok_button'));
checkbox.checked = true;
return;
}
if (checkbox.checked) {
chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
} else {
chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
}
this.updatePreloadEnginesFromCheckboxes_();
this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
this.savePreloadEnginesPref_();
},
/**
* Handles add language list's click event.
* @param {Event} e Click event.
*/
handleAddLanguageListClick_ : function(e) {
var languageOptionsList = $('language-options-list');
var languageCode = e.target.languageCode;
// languageCode can be undefined, if click was made on some random
// place in the overlay, rather than a button. Ignore it.
if (!languageCode) {
return;
}
languageOptionsList.addLanguage(languageCode);
var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
// Enable the first input method for the language added.
if (inputMethodIds && inputMethodIds[0] &&
// Don't add the input method it's already present. This can
// happen if the same input method is shared among multiple
// languages (ex. English US keyboard is used for English US and
// Filipino).
this.preloadEngines_.indexOf(inputMethodIds[0]) == -1) {
this.preloadEngines_.push(inputMethodIds[0]);
this.updateCheckboxesFromPreloadEngines_();
this.savePreloadEnginesPref_();
}
OptionsPage.closeOverlay();
},
/**
* Handles add language dialog ok button.
*/
handleAddLanguageOkButtonClick_ : function() {
var languagesSelect = $('add-language-overlay-language-list');
var selectedIndex = languagesSelect.selectedIndex;
if (selectedIndex >= 0) {
var selection = languagesSelect.options[selectedIndex];
$('language-options-list').addLanguage(String(selection.value));
OptionsPage.closeOverlay();
}
},
/**
* Checks if languageCode is deletable or not.
* @param {String} languageCode the languageCode to check for deletability.
*/
languageIsDeletable: function(languageCode) {
// Don't allow removing the language if it's as UI language.
if (languageCode == templateData.currentUiLanguageCode)
return false;
return (!cr.isChromeOS ||
this.canDeleteLanguage_(languageCode));
},
/**
* Handles browse.enable_spellchecking change.
* @param {Event} e Change event.
* @private
*/
updateEnableSpellCheck_: function() {
var value = !$('enable-spell-check').checked;
$('language-options-spell-check-language-button').disabled = value;
},
/**
* Handles spellCheckDictionaryPref change.
* @param {Event} e Change event.
* @private
*/
handleSpellCheckDictionaryPrefChange_: function(e) {
var languageCode = e.value.value
this.spellCheckDictionary_ = languageCode;
var languageOptionsList = $('language-options-list');
var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
this.updateSpellCheckLanguageButton_(selectedLanguageCode);
},
/**
* Handles spellCheckLanguageButton click.
* @param {Event} e Click event.
* @private
*/
handleSpellCheckLanguageButtonClick_: function(e) {
var languageCode = e.target.languageCode;
// Save the preference.
Preferences.setStringPref(this.spellCheckDictionaryPref,
languageCode);
chrome.send('spellCheckLanguageChange', [languageCode]);
},
/**
* Checks whether it's possible to remove the language specified by
* languageCode and returns true if possible. This function returns false
* if the removal causes the number of preload engines to be zero.
*
* @param {string} languageCode Language code (ex. "fr").
* @return {boolean} Returns true on success.
* @private
*/
canDeleteLanguage_: function(languageCode) {
// First create the set of engines to be removed from input methods
// associated with the language code.
var enginesToBeRemovedSet = {};
var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
for (var i = 0; i < inputMethodIds.length; i++) {
enginesToBeRemovedSet[inputMethodIds[i]] = true;
}
// Then eliminate engines that are also used for other active languages.
// For instance, if "xkb:us::eng" is used for both English and Filipino.
var languageCodes = $('language-options-list').getLanguageCodes();
for (var i = 0; i < languageCodes.length; i++) {
// Skip the target language code.
if (languageCodes[i] == languageCode) {
continue;
}
// Check if input methods used in this language are included in
// enginesToBeRemovedSet. If so, eliminate these from the set, so
// we don't remove this time.
var inputMethodIdsForAnotherLanguage =
this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
var inputMethodId = inputMethodIdsForAnotherLanguage[j];
if (inputMethodId in enginesToBeRemovedSet) {
delete enginesToBeRemovedSet[inputMethodId];
}
}
}
// Update the preload engine list with the to-be-removed set.
var newPreloadEngines = [];
for (var i = 0; i < this.preloadEngines_.length; i++) {
if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
newPreloadEngines.push(this.preloadEngines_[i]);
}
}
// Don't allow this operation if it causes the number of preload
// engines to be zero.
return (newPreloadEngines.length > 0);
},
/**
* Saves the preload engines preference.
* @private
*/
savePreloadEnginesPref_: function() {
Preferences.setStringPref(this.preloadEnginesPref,
this.preloadEngines_.join(','));
},
/**
* Updates the checkboxes in the input method list from the preload
* engines preference.
* @private
*/
updateCheckboxesFromPreloadEngines_: function() {
// Convert the list into a dictonary for simpler lookup.
var dictionary = {};
for (var i = 0; i < this.preloadEngines_.length; i++) {
dictionary[this.preloadEngines_[i]] = true;
}
var inputMethodList = $('language-options-input-method-list');
var checkboxes = inputMethodList.querySelectorAll('input');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
}
},
/**
* Updates the preload engines preference from the checkboxes in the
* input method list.
* @private
*/
updatePreloadEnginesFromCheckboxes_: function() {
this.preloadEngines_ = [];
var inputMethodList = $('language-options-input-method-list');
var checkboxes = inputMethodList.querySelectorAll('input');
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) {
this.preloadEngines_.push(checkboxes[i].inputMethodId);
}
}
var languageOptionsList = $('language-options-list');
languageOptionsList.updateDeletable();
},
/**
* Filters bad preload engines in case bad preload engines are
* stored in the preference. Removes duplicates as well.
* @param {Array} preloadEngines List of preload engines.
* @private
*/
filterBadPreloadEngines_: function(preloadEngines) {
// Convert the list into a dictonary for simpler lookup.
var dictionary = {};
for (var i = 0; i < templateData.inputMethodList.length; i++) {
dictionary[templateData.inputMethodList[i].id] = true;
}
var filteredPreloadEngines = [];
var seen = {};
for (var i = 0; i < preloadEngines.length; i++) {
// Check if the preload engine is present in the
// dictionary, and not duplicate. Otherwise, skip it.
if (preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) {
filteredPreloadEngines.push(preloadEngines[i]);
seen[preloadEngines[i]] = true;
}
}
return filteredPreloadEngines;
},
// TODO(kochi): This is an adapted copy from new_tab.js.
// If this will go as final UI, refactor this to share the component with
// new new tab page.
/**
* Shows notification
* @private
*/
notificationTimeout_: null,
showNotification_ : function(text, actionText, opt_delay) {
var notificationElement = $('notification');
var actionLink = notificationElement.querySelector('.link-color');
var delay = opt_delay || 10000;
function show() {
window.clearTimeout(this.notificationTimeout_);
notificationElement.classList.add('show');
document.body.classList.add('notification-shown');
}
function hide() {
window.clearTimeout(this.notificationTimeout_);
notificationElement.classList.remove('show');
document.body.classList.remove('notification-shown');
// Prevent tabbing to the hidden link.
actionLink.tabIndex = -1;
// Setting tabIndex to -1 only prevents future tabbing to it. If,
// however, the user switches window or a tab and then moves back to
// this tab the element may gain focus. We therefore make sure that we
// blur the element so that the element focus is not restored when
// coming back to this window.
actionLink.blur();
}
function delayedHide() {
this.notificationTimeout_ = window.setTimeout(hide, delay);
}
notificationElement.firstElementChild.textContent = text;
actionLink.textContent = actionText;
actionLink.onclick = hide;
actionLink.onkeydown = function(e) {
if (e.keyIdentifier == 'Enter') {
hide();
}
};
notificationElement.onmouseover = show;
notificationElement.onmouseout = delayedHide;
actionLink.onfocus = show;
actionLink.onblur = delayedHide;
// Enable tabbing to the link now that it is shown.
actionLink.tabIndex = 0;
show();
delayedHide();
}
};
/**
* Chrome callback for when the UI language preference is saved.
*/
LanguageOptions.uiLanguageSaved = function() {
$('language-options-ui-language-button').style.display = 'none';
$('language-options-ui-notification-bar').style.display = 'block';
};
// Export
return {
LanguageOptions: LanguageOptions
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
var ArrayDataModel = cr.ui.ArrayDataModel;
const localStrings = new LocalStrings();
/**
* ManageProfileOverlay class
* Encapsulated handling of the 'Manage profile...' overlay page.
* @constructor
* @class
*/
function ManageProfileOverlay() {
OptionsPage.call(this,
'manageProfile',
templateData.manageProfileOverlayTabTitle,
'manage-profile-overlay');
};
cr.addSingletonGetter(ManageProfileOverlay);
ManageProfileOverlay.prototype = {
// Inherit from OptionsPage.
__proto__: OptionsPage.prototype,
// Info about the currently managed/deleted profile.
profileInfo_: null,
// An object containing all known profile names.
profileNames_: {},
// The currently selected icon in the icon grid.
iconGridSelectedURL_: null,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to start preference initialization.
OptionsPage.prototype.initializePage.call(this);
var self = this;
var iconGrid = $('manage-profile-icon-grid');
options.ProfilesIconGrid.decorate(iconGrid);
iconGrid.addEventListener('change', function(e) {
self.onIconGridSelectionChanged_();
});
$('manage-profile-name').oninput = this.onNameChanged_.bind(this);
$('manage-profile-cancel').onclick =
$('delete-profile-cancel').onclick = function(event) {
OptionsPage.closeOverlay();
};
$('manage-profile-ok').onclick = function(event) {
OptionsPage.closeOverlay();
self.submitManageChanges_();
};
$('delete-profile-ok').onclick = function(event) {
OptionsPage.closeOverlay();
chrome.send('deleteProfile', [self.profileInfo_.filePath]);
};
},
/** @inheritDoc */
didShowPage: function() {
chrome.send('requestDefaultProfileIcons');
// Use the hash to specify the profile index.
var hash = location.hash;
if (hash) {
$('manage-profile-overlay-manage').hidden = false;
$('manage-profile-overlay-delete').hidden = true;
ManageProfileOverlay.getInstance().hideErrorBubble_();
chrome.send('requestProfileInfo', [parseInt(hash.slice(1), 10)]);
}
$('manage-profile-name').focus();
},
/**
* Set the profile info used in the dialog.
* @param {Object} profileInfo An object of the form:
* profileInfo = {
* name: "Profile Name",
* iconURL: "chrome://path/to/icon/image",
* filePath: "/path/to/profile/data/on/disk"
* isCurrentProfile: false,
* };
* @private
*/
setProfileInfo_: function(profileInfo) {
this.iconGridSelectedURL_ = profileInfo.iconURL;
this.profileInfo_ = profileInfo;
$('manage-profile-name').value = profileInfo.name;
$('manage-profile-icon-grid').selectedItem = profileInfo.iconURL;
},
/**
* Sets the name of the currently edited profile.
* @private
*/
setProfileName_: function(name) {
if (this.profileInfo_)
this.profileInfo_.name = name;
$('manage-profile-name').value = name;
},
/**
* Set an array of default icon URLs. These will be added to the grid that
* the user will use to choose their profile icon.
* @param {Array.<string>} iconURLs An array of icon URLs.
* @private
*/
receiveDefaultProfileIcons_: function(iconURLs) {
$('manage-profile-icon-grid').dataModel = new ArrayDataModel(iconURLs);
// Changing the dataModel resets the selectedItem. Re-select it, if there
// is one.
if (this.profileInfo_)
$('manage-profile-icon-grid').selectedItem = this.profileInfo_.iconURL;
var grid = $('manage-profile-icon-grid');
// Recalculate the measured item size.
grid.measured_ = null;
grid.columns = 0;
grid.redraw();
},
/**
* Set a dictionary of all profile names. These are used to prevent the
* user from naming two profiles the same.
* @param {Object} profileNames A dictionary of profile names.
* @private
*/
receiveProfileNames_: function(profileNames) {
this.profileNames_ = profileNames;
},
/**
* Display the error bubble, with |errorText| in the bubble.
* @param {string} errorText The localized string id to display as an error.
* @private
*/
showErrorBubble_: function(errorText) {
var nameErrorEl = $('manage-profile-error-bubble');
nameErrorEl.hidden = false;
nameErrorEl.textContent = localStrings.getString(errorText);
$('manage-profile-ok').disabled = true;
},
/**
* Hide the error bubble.
* @private
*/
hideErrorBubble_: function() {
$('manage-profile-error-bubble').hidden = true;
$('manage-profile-ok').disabled = false;
},
/**
* oninput callback for <input> field.
* @param event The event object
* @private
*/
onNameChanged_: function(event) {
var newName = event.target.value;
var oldName = this.profileInfo_.name;
if (newName == oldName) {
this.hideErrorBubble_();
} else if (this.profileNames_[newName] != undefined) {
this.showErrorBubble_('manageProfilesDuplicateNameError');
} else {
this.hideErrorBubble_();
var nameIsValid = $('manage-profile-name').validity.valid;
$('manage-profile-ok').disabled = !nameIsValid;
}
},
/**
* Called when the user clicks "OK". Saves the newly changed profile info.
* @private
*/
submitManageChanges_: function() {
var name = $('manage-profile-name').value;
var iconURL = $('manage-profile-icon-grid').selectedItem;
chrome.send('setProfileNameAndIcon',
[this.profileInfo_.filePath, name, iconURL]);
},
/**
* Called when the selected icon in the icon grid changes.
* @private
*/
onIconGridSelectionChanged_: function() {
var iconURL = $('manage-profile-icon-grid').selectedItem;
if (!iconURL || iconURL == this.iconGridSelectedURL_)
return;
this.iconGridSelectedURL_ = iconURL;
chrome.send('profileIconSelectionChanged',
[this.profileInfo_.filePath, iconURL]);
},
/**
* Display the "Manage Profile" dialog.
* @param {Object} profileInfo The profile object of the profile to manage.
* @private
*/
showManageDialog_: function(profileInfo) {
ManageProfileOverlay.setProfileInfo(profileInfo);
$('manage-profile-overlay-manage').hidden = false;
$('manage-profile-overlay-delete').hidden = true;
ManageProfileOverlay.getInstance().hideErrorBubble_();
// Intentionally don't show the URL in the location bar as we don't want
// people trying to navigate here by hand.
OptionsPage.showPageByName('manageProfile', false);
},
/**
* Display the "Delete Profile" dialog.
* @param {Object} profileInfo The profile object of the profile to delete.
* @private
*/
showDeleteDialog_: function(profileInfo) {
ManageProfileOverlay.setProfileInfo(profileInfo);
$('manage-profile-overlay-manage').hidden = true;
$('manage-profile-overlay-delete').hidden = false;
$('delete-profile-message').textContent =
localStrings.getStringF('deleteProfileMessage', profileInfo.name);
$('delete-profile-message').style.backgroundImage = 'url("' +
profileInfo.iconURL + '")';
// Intentionally don't show the URL in the location bar as we don't want
// people trying to navigate here by hand.
OptionsPage.showPageByName('manageProfile', false);
},
};
// Forward public APIs to private implementations.
[
'receiveDefaultProfileIcons',
'receiveProfileNames',
'setProfileInfo',
'setProfileName',
'showManageDialog',
'showDeleteDialog',
].forEach(function(name) {
ManageProfileOverlay[name] = function(value) {
ManageProfileOverlay.getInstance()[name + '_'](value);
};
});
// Export
return {
ManageProfileOverlay: ManageProfileOverlay
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
/**
* PackExtensionOverlay class
* Encapsulated handling of the 'Pack Extension' overlay page.
* @constructor
*/
function PackExtensionOverlay() {
OptionsPage.call(this, 'packExtensionOverlay',
templateData.packExtensionOverlayTabTitle,
'packExtensionOverlay');
}
cr.addSingletonGetter(PackExtensionOverlay);
PackExtensionOverlay.prototype = {
// Inherit PackExtensionOverlay from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to starts preference initialization.
OptionsPage.prototype.initializePage.call(this);
$('packExtensionDismiss').onclick = function(event) {
OptionsPage.closeOverlay();
};
$('packExtensionCommit').onclick = function(event) {
var extensionPath = $('extensionRootDir').value;
var privateKeyPath = $('extensionPrivateKey').value;
chrome.send('pack', [extensionPath, privateKeyPath]);
};
$('browseExtensionDir').addEventListener('click',
this.handleBrowseExtensionDir_.bind(this));
$('browsePrivateKey').addEventListener('click',
this.handleBrowsePrivateKey_.bind(this));
},
/**
* Utility function which asks the C++ to show a platform-specific file
* select dialog, and fire |callback| with the |filePath| that resulted.
* |selectType| can be either 'file' or 'folder'. |operation| can be 'load',
* 'packRoot', or 'pem' which are signals to the C++ to do some
* operation-specific configuration.
* @private
*/
showFileDialog_: function(selectType, operation, callback) {
handleFilePathSelected = function(filePath) {
callback(filePath);
handleFilePathSelected = function() {};
};
chrome.send('extensionSettingsSelectFilePath', [selectType, operation]);
},
/**
* Handles the showing of the extension directory browser.
* @param {Event} e Change event.
* @private
*/
handleBrowseExtensionDir_: function(e) {
this.showFileDialog_('folder', 'load', function(filePath) {
$('extensionRootDir').value = filePath;
});
},
/**
* Handles the showing of the extension private key file.
* @param {Event} e Change event.
* @private
*/
handleBrowsePrivateKey_: function(e) {
this.showFileDialog_('file', 'load', function(filePath) {
$('extensionPrivateKey').value = filePath;
});
},
};
// Export
return {
PackExtensionOverlay: PackExtensionOverlay
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
const ArrayDataModel = cr.ui.ArrayDataModel;
/////////////////////////////////////////////////////////////////////////////
// PasswordManager class:
/**
* Encapsulated handling of password and exceptions page.
* @constructor
*/
function PasswordManager() {
this.activeNavTab = null;
OptionsPage.call(this,
'passwords',
templateData.passwordsPageTabTitle,
'password-manager');
}
cr.addSingletonGetter(PasswordManager);
PasswordManager.prototype = {
__proto__: OptionsPage.prototype,
/**
* The saved passwords list.
* @type {DeletableItemList}
* @private
*/
savedPasswordsList_: null,
/**
* The password exceptions list.
* @type {DeletableItemList}
* @private
*/
passwordExceptionsList_: null,
/**
* The timer id of the timer set on search query change events.
* @type {number}
* @private
*/
queryDelayTimerId_: 0,
/**
* The most recent search query, or null if the query is empty.
* @type {?string}
* @private
*/
lastQuery_: null,
/** @inheritDoc */
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
$('password-search-box').addEventListener('search',
this.handleSearchQueryChange_.bind(this));
this.createSavedPasswordsList_();
this.createPasswordExceptionsList_();
},
/** @inheritDoc */
canShowPage: function() {
return !PersonalOptions.disablePasswordManagement();
},
/** @inheritDoc */
didShowPage: function() {
// Updating the password lists may cause a blocking platform dialog pop up
// (Mac, Linux), so we delay this operation until the page is shown.
chrome.send('updatePasswordLists');
$('password-search-box').focus();
},
/**
* Creates, decorates and initializes the saved passwords list.
* @private
*/
createSavedPasswordsList_: function() {
this.savedPasswordsList_ = $('saved-passwords-list');
options.passwordManager.PasswordsList.decorate(this.savedPasswordsList_);
this.savedPasswordsList_.autoExpands = true;
},
/**
* Creates, decorates and initializes the password exceptions list.
* @private
*/
createPasswordExceptionsList_: function() {
this.passwordExceptionsList_ = $('password-exceptions-list');
options.passwordManager.PasswordExceptionsList.decorate(
this.passwordExceptionsList_);
this.passwordExceptionsList_.autoExpands = true;
},
/**
* Handles search query changes.
* @param {!Event} e The event object.
* @private
*/
handleSearchQueryChange_: function(e) {
if (this.queryDelayTimerId_)
window.clearTimeout(this.queryDelayTimerId_);
// Searching cookies uses a timeout of 500ms. We use a shorter timeout
// because there are probably fewer passwords and we want the UI to be
// snappier since users will expect that it's "less work."
this.queryDelayTimerId_ = window.setTimeout(
this.searchPasswords_.bind(this), 250);
},
/**
* Search passwords using text in |password-search-box|.
* @private
*/
searchPasswords_: function() {
this.queryDelayTimerId_ = 0;
var filter = $('password-search-box').value;
filter = (filter == '') ? null : filter;
if (this.lastQuery_ != filter) {
this.lastQuery_ = filter;
// Searching for passwords has the side effect of requerying the
// underlying password store. This is done intentionally, as on OS X and
// Linux they can change from outside and we won't be notified of it.
chrome.send('updatePasswordLists');
}
},
/**
* Updates the visibility of the list and empty list placeholder.
* @param {!List} list The list to toggle visilibility for.
*/
updateListVisibility_: function(list) {
var empty = list.dataModel.length == 0;
var listPlaceHolderID = list.id + '-empty-placeholder';
list.hidden = empty;
$(listPlaceHolderID).hidden = !empty;
},
/**
* Updates the data model for the saved passwords list with the values from
* |entries|.
* @param {Array} entries The list of saved password data.
*/
setSavedPasswordsList_: function(entries) {
if (this.lastQuery_) {
// Implement password searching here in javascript, rather than in C++.
// The number of saved passwords shouldn't be too big for us to handle.
var query = this.lastQuery_;
var filter = function(entry, index, list) {
// Search both URL and username.
if (entry[0].indexOf(query) >= 0 || entry[1].indexOf(query) >= 0) {
// Keep the original index so we can delete correctly. See also
// deleteItemAtIndex() in password_manager_list.js that uses this.
entry[3] = index;
return true;
}
return false;
};
entries = entries.filter(filter);
}
this.savedPasswordsList_.dataModel = new ArrayDataModel(entries);
this.updateListVisibility_(this.savedPasswordsList_);
},
/**
* Updates the data model for the password exceptions list with the values
* from |entries|.
* @param {Array} entries The list of password exception data.
*/
setPasswordExceptionsList_: function(entries) {
this.passwordExceptionsList_.dataModel = new ArrayDataModel(entries);
this.updateListVisibility_(this.passwordExceptionsList_);
},
};
/**
* Call to remove a saved password.
* @param rowIndex indicating the row to remove.
*/
PasswordManager.removeSavedPassword = function(rowIndex) {
chrome.send('removeSavedPassword', [String(rowIndex)]);
};
/**
* Call to remove a password exception.
* @param rowIndex indicating the row to remove.
*/
PasswordManager.removePasswordException = function(rowIndex) {
chrome.send('removePasswordException', [String(rowIndex)]);
};
/**
* Call to remove all saved passwords.
* @param tab contentType of the tab currently on.
*/
PasswordManager.removeAllPasswords = function() {
chrome.send('removeAllSavedPasswords');
};
/**
* Call to remove all saved passwords.
* @param tab contentType of the tab currently on.
*/
PasswordManager.removeAllPasswordExceptions = function() {
chrome.send('removeAllPasswordExceptions');
};
PasswordManager.setSavedPasswordsList = function(entries) {
PasswordManager.getInstance().setSavedPasswordsList_(entries);
};
PasswordManager.setPasswordExceptionsList = function(entries) {
PasswordManager.getInstance().setPasswordExceptionsList_(entries);
};
// Export
return {
PasswordManager: PasswordManager
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.passwordManager', function() {
const ArrayDataModel = cr.ui.ArrayDataModel;
const DeletableItemList = options.DeletableItemList;
const DeletableItem = options.DeletableItem;
const List = cr.ui.List;
/**
* Creates a new passwords list item.
* @param {Array} entry An array of the form [url, username, password]. When
* the list has been filtered, a fourth element [index] may be present.
* @constructor
* @extends {cr.ui.ListItem}
*/
function PasswordListItem(entry, showPasswords) {
var el = cr.doc.createElement('div');
el.dataItem = entry;
el.__proto__ = PasswordListItem.prototype;
el.decorate(showPasswords);
return el;
}
PasswordListItem.prototype = {
__proto__: DeletableItem.prototype,
/** @inheritDoc */
decorate: function(showPasswords) {
DeletableItem.prototype.decorate.call(this);
// The URL of the site.
var urlLabel = this.ownerDocument.createElement('div');
urlLabel.classList.add('favicon-cell');
urlLabel.classList.add('weakrtl');
urlLabel.classList.add('url');
urlLabel.setAttribute('title', this.url);
urlLabel.textContent = this.url;
urlLabel.style.backgroundImage = url('chrome://favicon/' + this.url);
this.contentElement.appendChild(urlLabel);
// The stored username.
var usernameLabel = this.ownerDocument.createElement('div');
usernameLabel.className = 'name';
usernameLabel.textContent = this.username;
this.contentElement.appendChild(usernameLabel);
// The stored password.
var passwordInputDiv = this.ownerDocument.createElement('div');
passwordInputDiv.className = 'password';
// The password input field.
var passwordInput = this.ownerDocument.createElement('input');
passwordInput.type = 'password';
passwordInput.className = 'inactive-password';
passwordInput.readOnly = true;
passwordInput.value = showPasswords ? this.password : '********';
passwordInputDiv.appendChild(passwordInput);
// The show/hide button.
if (showPasswords) {
var button = this.ownerDocument.createElement('button');
button.hidden = true;
button.classList.add('password-button');
button.textContent = localStrings.getString('passwordShowButton');
button.addEventListener('click', this.onClick_, true);
passwordInputDiv.appendChild(button);
}
this.contentElement.appendChild(passwordInputDiv);
},
/** @inheritDoc */
selectionChanged: function() {
var passwordInput = this.querySelector('input[type=password]');
var textInput = this.querySelector('input[type=text]');
var input = passwordInput || textInput;
var button = input.nextSibling;
// |button| doesn't exist when passwords can't be shown.
if (!button)
return;
if (this.selected) {
input.classList.remove('inactive-password');
button.hidden = false;
} else {
input.classList.add('inactive-password');
button.hidden = true;
}
},
/**
* On-click event handler. Swaps the type of the input field from password
* to text and back.
* @private
*/
onClick_: function(event) {
// The password is the input element previous to the button.
var button = event.currentTarget;
var passwordInput = button.previousSibling;
if (passwordInput.type == 'password') {
passwordInput.type = 'text';
button.textContent = localStrings.getString('passwordHideButton');
} else {
passwordInput.type = 'password';
button.textContent = localStrings.getString('passwordShowButton');
}
},
/**
* Get and set the URL for the entry.
* @type {string}
*/
get url() {
return this.dataItem[0];
},
set url(url) {
this.dataItem[0] = url;
},
/**
* Get and set the username for the entry.
* @type {string}
*/
get username() {
return this.dataItem[1];
},
set username(username) {
this.dataItem[1] = username;
},
/**
* Get and set the password for the entry.
* @type {string}
*/
get password() {
return this.dataItem[2];
},
set password(password) {
this.dataItem[2] = password;
},
};
/**
* Creates a new PasswordExceptions list item.
* @param {Array} entry A pair of the form [url, username].
* @constructor
* @extends {Deletable.ListItem}
*/
function PasswordExceptionsListItem(entry) {
var el = cr.doc.createElement('div');
el.dataItem = entry;
el.__proto__ = PasswordExceptionsListItem.prototype;
el.decorate();
return el;
}
PasswordExceptionsListItem.prototype = {
__proto__: DeletableItem.prototype,
/**
* Call when an element is decorated as a list item.
*/
decorate: function() {
DeletableItem.prototype.decorate.call(this);
// The URL of the site.
var urlLabel = this.ownerDocument.createElement('div');
urlLabel.className = 'url';
urlLabel.classList.add('favicon-cell');
urlLabel.classList.add('weakrtl');
urlLabel.textContent = this.url;
urlLabel.style.backgroundImage = url('chrome://favicon/' + this.url);
this.contentElement.appendChild(urlLabel);
},
/**
* Get the url for the entry.
* @type {string}
*/
get url() {
return this.dataItem;
},
set url(url) {
this.dataItem = url;
},
};
/**
* Create a new passwords list.
* @constructor
* @extends {cr.ui.List}
*/
var PasswordsList = cr.ui.define('list');
PasswordsList.prototype = {
__proto__: DeletableItemList.prototype,
/**
* Whether passwords can be revealed or not.
* @type {boolean}
* @private
*/
showPasswords_: true,
/** @inheritDoc */
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
Preferences.getInstance().addEventListener(
"profile.password_manager_allow_show_passwords",
this.onPreferenceChanged_.bind(this));
},
/**
* Listener for changes on the preference.
* @param {Event} event The preference update event.
* @private
*/
onPreferenceChanged_: function(event) {
this.showPasswords_ = event.value.value;
this.redraw();
},
/** @inheritDoc */
createItem: function(entry) {
return new PasswordListItem(entry, this.showPasswords_);
},
/** @inheritDoc */
deleteItemAtIndex: function(index) {
var item = this.dataModel.item(index);
if (item && item.length > 3) {
// The fourth element, if present, is the original index to delete.
index = item[3];
}
PasswordManager.removeSavedPassword(index);
},
/**
* The length of the list.
*/
get length() {
return this.dataModel.length;
},
};
/**
* Create a new passwords list.
* @constructor
* @extends {cr.ui.List}
*/
var PasswordExceptionsList = cr.ui.define('list');
PasswordExceptionsList.prototype = {
__proto__: DeletableItemList.prototype,
/** @inheritDoc */
createItem: function(entry) {
return new PasswordExceptionsListItem(entry);
},
/** @inheritDoc */
deleteItemAtIndex: function(index) {
PasswordManager.removePasswordException(index);
},
/**
* The length of the list.
*/
get length() {
return this.dataModel.length;
},
};
return {
PasswordListItem: PasswordListItem,
PasswordExceptionsListItem: PasswordExceptionsListItem,
PasswordsList: PasswordsList,
PasswordExceptionsList: PasswordExceptionsList,
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
var OptionsPage = options.OptionsPage;
var ArrayDataModel = cr.ui.ArrayDataModel;
/**
* Encapsulated handling of personal options page.
* @constructor
*/
function PersonalOptions() {
OptionsPage.call(this, 'personal',
templateData.personalPageTabTitle,
'personal-page');
if (cr.isChromeOS) {
// Username (canonical email) of the currently logged in user or
// |kGuestUser| if a guest session is active.
this.username_ = localStrings.getString('username');
}
}
cr.addSingletonGetter(PersonalOptions);
PersonalOptions.prototype = {
// Inherit PersonalOptions from OptionsPage.
__proto__: options.OptionsPage.prototype,
// State variables.
syncEnabled: false,
syncSetupCompleted: false,
// Initialize PersonalOptions page.
initializePage: function() {
// Call base class implementation to start preference initialization.
OptionsPage.prototype.initializePage.call(this);
var self = this;
// Sync.
$('sync-action-link').onclick = function(event) {
SyncSetupOverlay.showErrorUI();
};
$('start-stop-sync').onclick = function(event) {
if (self.syncSetupCompleted)
SyncSetupOverlay.showStopSyncingUI();
else
SyncSetupOverlay.showSetupUI();
};
$('customize-sync').onclick = function(event) {
SyncSetupOverlay.showSetupUI();
};
// Profiles.
var profilesList = $('profiles-list');
options.personal_options.ProfileList.decorate(profilesList);
profilesList.autoExpands = true;
profilesList.onchange = self.setProfileViewButtonsStatus_;
$('profiles-create').onclick = function(event) {
chrome.send('createProfile');
};
$('profiles-manage').onclick = function(event) {
var selectedProfile = self.getSelectedProfileItem_();
if (selectedProfile)
ManageProfileOverlay.showManageDialog(selectedProfile);
};
$('profiles-delete').onclick = function(event) {
var selectedProfile = self.getSelectedProfileItem_();
if (selectedProfile)
ManageProfileOverlay.showDeleteDialog(selectedProfile);
};
// Passwords.
$('manage-passwords').onclick = function(event) {
OptionsPage.navigateToPage('passwords');
OptionsPage.showTab($('passwords-nav-tab'));
chrome.send('coreOptionsUserMetricsAction',
['Options_ShowPasswordManager']);
};
// Autofill.
$('autofill-settings').onclick = function(event) {
OptionsPage.navigateToPage('autofill');
chrome.send('coreOptionsUserMetricsAction',
['Options_ShowAutofillSettings']);
};
if (cr.isChromeOS && cr.commandLine.options['--bwsi']) {
// Hide Autofill options for the guest user.
$('autofill-section').hidden = true;
}
// Appearance.
$('themes-reset').onclick = function(event) {
chrome.send('themesReset');
};
if (!cr.isChromeOS) {
$('import-data').onclick = function(event) {
// Make sure that any previous import success message is hidden, and
// we're showing the UI to import further data.
$('import-data-configure').hidden = false;
$('import-data-success').hidden = true;
OptionsPage.navigateToPage('importData');
chrome.send('coreOptionsUserMetricsAction', ['Import_ShowDlg']);
};
if ($('themes-GTK-button')) {
$('themes-GTK-button').onclick = function(event) {
chrome.send('themesSetGTK');
};
}
} else {
$('change-picture-button').onclick = function(event) {
OptionsPage.navigateToPage('changePicture');
};
this.updateAccountPicture_();
if (cr.commandLine.options['--bwsi']) {
// Disable the screen lock checkbox and change-picture-button in
// guest mode.
$('enable-screen-lock').disabled = true;
$('change-picture-button').disabled = true;
}
}
if (PersonalOptions.disablePasswordManagement()) {
// Disable the Password Manager in guest mode.
$('passwords-offersave').disabled = true;
$('passwords-neversave').disabled = true;
$('passwords-offersave').value = false;
$('passwords-neversave').value = true;
$('manage-passwords').disabled = true;
}
$('mac-passwords-warning').hidden =
!(localStrings.getString('macPasswordsWarning'));
if (PersonalOptions.disableAutofillManagement()) {
$('autofill-settings').disabled = true;
// Disable and turn off autofill.
var autofillEnabled = $('autofill-enabled');
autofillEnabled.disabled = true;
autofillEnabled.checked = false;
cr.dispatchSimpleEvent(autofillEnabled, 'change');
}
},
setSyncEnabled_: function(enabled) {
this.syncEnabled = enabled;
},
setAutoLoginVisible_ : function(visible) {
$('enable-auto-login-checkbox').hidden = !visible;
},
setSyncSetupCompleted_: function(completed) {
this.syncSetupCompleted = completed;
$('customize-sync').hidden = !completed;
},
setSyncStatus_: function(status) {
var statusSet = status != '';
$('sync-overview').hidden = statusSet;
$('sync-status').hidden = !statusSet;
$('sync-status-text').innerHTML = status;
},
setSyncStatusErrorVisible_: function(visible) {
visible ? $('sync-status').classList.add('sync-error') :
$('sync-status').classList.remove('sync-error');
},
setCustomizeSyncButtonEnabled_: function(enabled) {
$('customize-sync').disabled = !enabled;
},
setSyncActionLinkEnabled_: function(enabled) {
$('sync-action-link').disabled = !enabled;
},
setSyncActionLinkLabel_: function(status) {
$('sync-action-link').textContent = status;
// link-button does is not zero-area when the contents of the button are
// empty, so explicitly hide the element.
$('sync-action-link').hidden = !status.length;
},
/**
* Display or hide the profiles section of the page. This is used for
* multi-profile settings.
* @param {boolean} visible True to show the section.
* @private
*/
setProfilesSectionVisible_: function(visible) {
$('profiles-section').hidden = !visible;
},
/**
* Get the selected profile item from the profile list. This also works
* correctly if the list is not displayed.
* @return {Object} the profile item object, or null if nothing is selected.
* @private
*/
getSelectedProfileItem_: function() {
var profilesList = $('profiles-list');
if (profilesList.hidden) {
if (profilesList.dataModel.length > 0)
return profilesList.dataModel.item(0);
} else {
return profilesList.selectedItem;
}
return null;
},
/**
* Helper function to set the status of profile view buttons to disabled or
* enabled, depending on the number of profiles and selection status of the
* profiles list.
*/
setProfileViewButtonsStatus_: function() {
var profilesList = $('profiles-list');
var selectedProfile = profilesList.selectedItem;
var hasSelection = selectedProfile != null;
var hasSingleProfile = profilesList.dataModel.length == 1;
$('profiles-manage').disabled = !hasSelection ||
!selectedProfile.isCurrentProfile;
$('profiles-delete').disabled = !hasSelection && !hasSingleProfile;
},
/**
* Display the correct dialog layout, depending on how many profiles are
* available.
* @param {number} numProfiles The number of profiles to display.
*/
setProfileViewSingle_: function(numProfiles) {
var hasSingleProfile = numProfiles == 1;
$('profiles-list').hidden = hasSingleProfile;
$('profiles-single-message').hidden = !hasSingleProfile;
$('profiles-manage').hidden = hasSingleProfile;
$('profiles-delete').textContent = hasSingleProfile ?
templateData.profilesDeleteSingle :
templateData.profilesDelete;
},
/**
* Adds all |profiles| to the list.
* @param {Array.<Object>} An array of profile info objects.
* each object is of the form:
* profileInfo = {
* name: "Profile Name",
* iconURL: "chrome://path/to/icon/image",
* filePath: "/path/to/profile/data/on/disk",
* isCurrentProfile: false
* };
*/
setProfilesInfo_: function(profiles) {
this.setProfileViewSingle_(profiles.length);
// add it to the list, even if the list is hidden so we can access it
// later.
$('profiles-list').dataModel = new ArrayDataModel(profiles);
this.setProfileViewButtonsStatus_();
},
setStartStopButtonVisible_: function(visible) {
$('start-stop-sync').hidden = !visible;
},
setStartStopButtonEnabled_: function(enabled) {
$('start-stop-sync').disabled = !enabled;
},
setStartStopButtonLabel_: function(label) {
$('start-stop-sync').textContent = label;
},
setGtkThemeButtonEnabled_: function(enabled) {
if (!cr.isChromeOS && navigator.platform.match(/linux|BSD/i)) {
$('themes-GTK-button').disabled = !enabled;
}
},
setThemesResetButtonEnabled_: function(enabled) {
$('themes-reset').disabled = !enabled;
},
hideSyncSection_: function() {
$('sync-section').hidden = true;
},
/**
* Get the start/stop sync button DOM element.
* @return {DOMElement} The start/stop sync button.
* @private
*/
getStartStopSyncButton_: function() {
return $('start-stop-sync');
},
/**
* (Re)loads IMG element with current user account picture.
*/
updateAccountPicture_: function() {
$('account-picture').src =
'chrome://userimage/' + this.username_ +
'?id=' + (new Date()).getTime();
},
};
/**
* Returns whether the user should be able to manage (view and edit) their
* stored passwords. Password management is disabled in guest mode.
* @return {boolean} True if password management should be disabled.
*/
PersonalOptions.disablePasswordManagement = function() {
return cr.commandLine.options['--bwsi'];
};
/**
* Returns whether the user should be able to manage autofill settings.
* @return {boolean} True if password management should be disabled.
*/
PersonalOptions.disableAutofillManagement = function() {
return cr.commandLine.options['--bwsi'];
};
if (cr.isChromeOS) {
/**
* Returns username (canonical email) of the user logged in (ChromeOS only).
* @return {string} user email.
*/
PersonalOptions.getLoggedInUsername = function() {
return PersonalOptions.getInstance().username_;
};
}
// Forward public APIs to private implementations.
[
'getStartStopSyncButton',
'hideSyncSection',
'setAutoLoginVisible',
'setCustomizeSyncButtonEnabled',
'setGtkThemeButtonEnabled',
'setProfilesInfo',
'setProfilesSectionVisible',
'setStartStopButtonEnabled',
'setStartStopButtonLabel',
'setStartStopButtonVisible',
'setSyncActionLinkEnabled',
'setSyncActionLinkLabel',
'setSyncEnabled',
'setSyncSetupCompleted',
'setSyncStatus',
'setSyncStatusErrorVisible',
'setThemesResetButtonEnabled',
'updateAccountPicture',
].forEach(function(name) {
PersonalOptions[name] = function(value) {
return PersonalOptions.getInstance()[name + '_'](value);
};
});
// Export
return {
PersonalOptions: PersonalOptions
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.personal_options', function() {
const DeletableItem = options.DeletableItem;
const DeletableItemList = options.DeletableItemList;
const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
var localStrings = new LocalStrings();
/**
* Creates a new profile list item.
* @param {Object} profileInfo The profile this item respresents.
* @constructor
* @extends {cr.ui.DeletableItem}
*/
function ProfileListItem(profileInfo) {
var el = cr.doc.createElement('div');
el.profileInfo_ = profileInfo;
ProfileListItem.decorate(el);
return el;
}
/**
* Decorates an element as a profile list item.
* @param {!HTMLElement} el The element to decorate.
*/
ProfileListItem.decorate = function(el) {
el.__proto__ = ProfileListItem.prototype;
el.decorate();
};
ProfileListItem.prototype = {
__proto__: DeletableItem.prototype,
/**
* Get the filepath for this profile list item.
* @return the file path of this item.
*/
get profilePath() {
return this.profileInfo_.filePath;
},
/** @inheritDoc */
decorate: function() {
DeletableItem.prototype.decorate.call(this);
var profileInfo = this.profileInfo_;
var iconEl = this.ownerDocument.createElement('img');
iconEl.className = 'profile-img';
iconEl.src = profileInfo.iconURL;
this.contentElement.appendChild(iconEl);
var nameEl = this.ownerDocument.createElement('div');
if (profileInfo.isCurrentProfile)
nameEl.classList.add('profile-item-current');
this.contentElement.appendChild(nameEl);
var displayName = profileInfo.name;
if (profileInfo.isCurrentProfile)
displayName = localStrings.getStringF(
'profilesListItemCurrent',
profileInfo.name)
nameEl.textContent = displayName;
},
};
var ProfileList = cr.ui.define('list');
ProfileList.prototype = {
__proto__: DeletableItemList.prototype,
/** @inheritDoc */
decorate: function() {
DeletableItemList.prototype.decorate.call(this);
this.selectionModel = new ListSingleSelectionModel();
},
/** @inheritDoc */
createItem: function(pageInfo) {
var item = new ProfileListItem(pageInfo);
return item;
},
/** @inheritDoc */
deleteItemAtIndex: function(index) {
ManageProfileOverlay.showDeleteDialog(this.dataModel.item(index));
},
/** @inheritDoc */
activateItemAtIndex: function(index) {
// Don't allow the user to edit a profile that is not current.
var profileInfo = this.dataModel.item(index);
if (profileInfo.isCurrentProfile)
ManageProfileOverlay.showManageDialog(profileInfo);
},
};
return {
ProfileList: ProfileList
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const ListItem = cr.ui.ListItem;
const Grid = cr.ui.Grid;
const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
/**
* Creates a new profile icon grid item.
* @param {Object} iconURL The profile icon URL.
* @constructor
* @extends {cr.ui.GridItem}
*/
function ProfilesIconGridItem(iconURL) {
var el = cr.doc.createElement('span');
el.iconURL_ = iconURL;
ProfilesIconGridItem.decorate(el);
return el;
}
/**
* Decorates an element as a profile grid item.
* @param {!HTMLElement} el The element to decorate.
*/
ProfilesIconGridItem.decorate = function(el) {
el.__proto__ = ProfilesIconGridItem.prototype;
el.decorate();
};
ProfilesIconGridItem.prototype = {
__proto__: ListItem.prototype,
/** @inheritDoc */
decorate: function() {
ListItem.prototype.decorate.call(this);
var imageEl = cr.doc.createElement('img');
imageEl.className = 'profile-icon';
imageEl.src = this.iconURL_;
this.appendChild(imageEl);
this.className = 'profile-icon-grid-item';
},
};
var ProfilesIconGrid = cr.ui.define('grid');
ProfilesIconGrid.prototype = {
__proto__: Grid.prototype,
/** @inheritDoc */
decorate: function() {
Grid.prototype.decorate.call(this);
this.selectionModel = new ListSingleSelectionModel();
},
/** @inheritDoc */
createItem: function(iconURL) {
return new ProfilesIconGridItem(iconURL);
},
};
return {
ProfilesIconGrid: ProfilesIconGrid
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
const ArrayDataModel = cr.ui.ArrayDataModel;
/**
* Encapsulated handling of search engine management page.
* @constructor
*/
function SearchEngineManager() {
this.activeNavTab = null;
OptionsPage.call(this, 'searchEngines',
templateData.searchEngineManagerPageTabTitle,
'search-engine-manager-page');
}
cr.addSingletonGetter(SearchEngineManager);
SearchEngineManager.prototype = {
__proto__: OptionsPage.prototype,
/**
* List for default search engine options.
* @private
*/
defaultsList_: null,
/**
* List for other search engine options.
* @private
*/
othersList_: null,
/**
* List for extension keywords.
* @private
extensionList_ : null,
/** inheritDoc */
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
this.defaultsList_ = $('default-search-engine-list');
this.setUpList_(this.defaultsList_);
this.othersList_ = $('other-search-engine-list');
this.setUpList_(this.othersList_);
this.extensionList_ = $('extension-keyword-list');
this.setUpList_(this.extensionList_);
},
/**
* Sets up the given list as a search engine list
* @param {List} list The list to set up.
* @private
*/
setUpList_: function(list) {
options.search_engines.SearchEngineList.decorate(list);
list.autoExpands = true;
},
/**
* Updates the search engine list with the given entries.
* @private
* @param {Array} defaultEngines List of possible default search engines.
* @param {Array} otherEngines List of other search engines.
* @param {Array} keywords List of keywords from extensions.
*/
updateSearchEngineList_: function(defaultEngines, otherEngines, keywords) {
this.defaultsList_.dataModel = new ArrayDataModel(defaultEngines);
otherEngines = otherEngines.map(function(x) {
return [x, x['name'].toLocaleLowerCase()];
}).sort(function(a,b){
return a[1].localeCompare(b[1]);
}).map(function(x){
return x[0];
});
var othersModel = new ArrayDataModel(otherEngines);
// Add a "new engine" row.
othersModel.push({
'modelIndex': '-1',
'canBeEdited': true
});
this.othersList_.dataModel = othersModel;
if (keywords.length > 0) {
$('extension-keyword-div').hidden = false;
var extensionsModel = new ArrayDataModel(keywords);
this.extensionList_.dataModel = extensionsModel;
} else {
$('extension-keyword-div').hidden = true;
}
},
};
SearchEngineManager.updateSearchEngineList = function(defaultEngines,
otherEngines,
keywords) {
SearchEngineManager.getInstance().updateSearchEngineList_(defaultEngines,
otherEngines,
keywords);
};
SearchEngineManager.validityCheckCallback = function(validity, modelIndex) {
// Forward to both lists; the one without a matching modelIndex will ignore
// it.
SearchEngineManager.getInstance().defaultsList_.validationComplete(
validity, modelIndex);
SearchEngineManager.getInstance().othersList_.validationComplete(
validity, modelIndex);
};
// Export
return {
SearchEngineManager: SearchEngineManager
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.search_engines', function() {
const InlineEditableItemList = options.InlineEditableItemList;
const InlineEditableItem = options.InlineEditableItem;
const ListSelectionController = cr.ui.ListSelectionController;
/**
* Creates a new search engine list item.
* @param {Object} searchEnigne The search engine this represents.
* @constructor
* @extends {cr.ui.ListItem}
*/
function SearchEngineListItem(searchEngine) {
var el = cr.doc.createElement('div');
el.searchEngine_ = searchEngine;
SearchEngineListItem.decorate(el);
return el;
}
/**
* Decorates an element as a search engine list item.
* @param {!HTMLElement} el The element to decorate.
*/
SearchEngineListItem.decorate = function(el) {
el.__proto__ = SearchEngineListItem.prototype;
el.decorate();
};
SearchEngineListItem.prototype = {
__proto__: InlineEditableItem.prototype,
/**
* Input field for editing the engine name.
* @type {HTMLElement}
* @private
*/
nameField_: null,
/**
* Input field for editing the engine keyword.
* @type {HTMLElement}
* @private
*/
keywordField_: null,
/**
* Input field for editing the engine url.
* @type {HTMLElement}
* @private
*/
urlField_: null,
/**
* Whether or not an input validation request is currently outstanding.
* @type {boolean}
* @private
*/
waitingForValidation_: false,
/**
* Whether or not the current set of input is known to be valid.
* @type {boolean}
* @private
*/
currentlyValid_: false,
/** @inheritDoc */
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
var engine = this.searchEngine_;
if (engine['modelIndex'] == '-1') {
this.isPlaceholder = true;
engine['name'] = '';
engine['keyword'] = '';
engine['url'] = '';
}
this.currentlyValid_ = !this.isPlaceholder;
if (engine['default'])
this.classList.add('default');
this.deletable = engine['canBeRemoved'];
// Construct the name column.
var nameColEl = this.ownerDocument.createElement('div');
nameColEl.className = 'name-column';
nameColEl.classList.add('weakrtl');
this.contentElement.appendChild(nameColEl);
// Add the favicon.
var faviconDivEl = this.ownerDocument.createElement('div');
faviconDivEl.className = 'favicon';
var imgEl = this.ownerDocument.createElement('img');
imgEl.src = 'chrome://favicon/iconurl/' + engine['iconURL'];
faviconDivEl.appendChild(imgEl);
nameColEl.appendChild(faviconDivEl);
var nameEl = this.createEditableTextCell(engine['displayName']);
nameEl.classList.add('weakrtl');
nameColEl.appendChild(nameEl);
// Then the keyword column.
var keywordEl = this.createEditableTextCell(engine['keyword']);
keywordEl.className = 'keyword-column';
keywordEl.classList.add('weakrtl');
this.contentElement.appendChild(keywordEl);
// And the URL column.
var urlEl = this.createEditableTextCell(engine['url']);
var urlWithButtonEl = this.ownerDocument.createElement('div');
urlWithButtonEl.appendChild(urlEl);
urlWithButtonEl.className = 'url-column';
urlWithButtonEl.classList.add('weakrtl');
this.contentElement.appendChild(urlWithButtonEl);
// Add the Make Default button. Temporary until drag-and-drop re-ordering
// is implemented. When this is removed, remove the extra div above.
if (engine['canBeDefault']) {
var makeDefaultButtonEl = this.ownerDocument.createElement('button');
makeDefaultButtonEl.className = 'raw-button custom-appearance';
makeDefaultButtonEl.textContent =
templateData.makeDefaultSearchEngineButton;
makeDefaultButtonEl.onclick = function(e) {
chrome.send('managerSetDefaultSearchEngine', [engine['modelIndex']]);
};
// Don't select the row when clicking the button.
makeDefaultButtonEl.onmousedown = function(e) {
e.stopPropagation();
};
urlWithButtonEl.appendChild(makeDefaultButtonEl);
}
// Do final adjustment to the input fields.
this.nameField_ = nameEl.querySelector('input');
// The editable field uses the raw name, not the display name.
this.nameField_.value = engine['name'];
this.keywordField_ = keywordEl.querySelector('input');
this.urlField_ = urlEl.querySelector('input');
if (engine['urlLocked'])
this.urlField_.disabled = true;
if (this.isPlaceholder) {
this.nameField_.placeholder =
localStrings.getString('searchEngineTableNamePlaceholder');
this.keywordField_.placeholder =
localStrings.getString('searchEngineTableKeywordPlaceholder');
this.urlField_.placeholder =
localStrings.getString('searchEngineTableURLPlaceholder');
}
var fields = [ this.nameField_, this.keywordField_, this.urlField_ ];
for (var i = 0; i < fields.length; i++) {
fields[i].oninput = this.startFieldValidation_.bind(this);
}
// Listen for edit events.
if (engine['canBeEdited']) {
this.addEventListener('edit', this.onEditStarted_.bind(this));
this.addEventListener('canceledit', this.onEditCancelled_.bind(this));
this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
} else {
this.editable = false;
}
},
/** @inheritDoc */
get currentInputIsValid() {
return !this.waitingForValidation_ && this.currentlyValid_;
},
/** @inheritDoc */
get hasBeenEdited() {
var engine = this.searchEngine_;
return this.nameField_.value != engine['name'] ||
this.keywordField_.value != engine['keyword'] ||
this.urlField_.value != engine['url'];
},
/**
* Called when entering edit mode; starts an edit session in the model.
* @param {Event} e The edit event.
* @private
*/
onEditStarted_: function(e) {
var editIndex = this.searchEngine_['modelIndex'];
chrome.send('editSearchEngine', [String(editIndex)]);
this.startFieldValidation_();
},
/**
* Called when committing an edit; updates the model.
* @param {Event} e The end event.
* @private
*/
onEditCommitted_: function(e) {
chrome.send('searchEngineEditCompleted', this.getInputFieldValues_());
},
/**
* Called when cancelling an edit; informs the model and resets the control
* states.
* @param {Event} e The cancel event.
* @private
*/
onEditCancelled_: function() {
chrome.send('searchEngineEditCancelled');
// The name field has been automatically set to match the display name,
// but it should use the raw name instead.
this.nameField_.value = this.searchEngine_['name'];
this.currentlyValid_ = !this.isPlaceholder;
},
/**
* Returns the input field values as an array suitable for passing to
* chrome.send. The order of the array is important.
* @private
* @return {array} The current input field values.
*/
getInputFieldValues_: function() {
return [ this.nameField_.value,
this.keywordField_.value,
this.urlField_.value ];
},
/**
* Begins the process of asynchronously validing the input fields.
* @private
*/
startFieldValidation_: function() {
this.waitingForValidation_ = true;
var args = this.getInputFieldValues_();
args.push(this.searchEngine_['modelIndex']);
chrome.send('checkSearchEngineInfoValidity', args);
},
/**
* Callback for the completion of an input validition check.
* @param {Object} validity A dictionary of validitation results.
*/
validationComplete: function(validity) {
this.waitingForValidation_ = false;
// TODO(stuartmorgan): Implement the full validation UI with
// checkmark/exclamation mark icons and tooltips showing the errors.
if (validity['name']) {
this.nameField_.setCustomValidity('');
} else {
this.nameField_.setCustomValidity(
templateData.editSearchEngineInvalidTitleToolTip);
}
if (validity['keyword']) {
this.keywordField_.setCustomValidity('');
} else {
this.keywordField_.setCustomValidity(
templateData.editSearchEngineInvalidKeywordToolTip);
}
if (validity['url']) {
this.urlField_.setCustomValidity('');
} else {
this.urlField_.setCustomValidity(
templateData.editSearchEngineInvalidURLToolTip);
}
this.currentlyValid_ = validity['name'] && validity['keyword'] &&
validity['url'];
},
};
var SearchEngineList = cr.ui.define('list');
SearchEngineList.prototype = {
__proto__: InlineEditableItemList.prototype,
/** @inheritDoc */
createItem: function(searchEngine) {
return new SearchEngineListItem(searchEngine);
},
/** @inheritDoc */
deleteItemAtIndex: function(index) {
var modelIndex = this.dataModel.item(index)['modelIndex']
chrome.send('removeSearchEngine', [String(modelIndex)]);
},
/**
* Passes the results of an input validation check to the requesting row
* if it's still being edited.
* @param {number} modelIndex The model index of the item that was checked.
* @param {Object} validity A dictionary of validitation results.
*/
validationComplete: function(validity, modelIndex) {
// If it's not still being edited, it no longer matters.
var currentSelection = this.selectedItem;
if (!currentSelection)
return;
var listItem = this.getListItem(currentSelection);
if (listItem.editing && currentSelection['modelIndex'] == modelIndex)
listItem.validationComplete(validity);
},
};
// Export
return {
SearchEngineList: SearchEngineList
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
/**
* Encapsulated handling of a search bubble.
* @constructor
*/
function SearchBubble(text) {
var el = cr.doc.createElement('div');
SearchBubble.decorate(el);
el.textContent = text;
return el;
}
SearchBubble.decorate = function(el) {
el.__proto__ = SearchBubble.prototype;
el.decorate();
};
SearchBubble.prototype = {
__proto__: HTMLDivElement.prototype,
decorate: function() {
this.className = 'search-bubble';
// We create a timer to periodically update the position of the bubbles.
// While this isn't all that desirable, it's the only sure-fire way of
// making sure the bubbles stay in the correct location as sections
// may dynamically change size at any time.
var self = this;
this.intervalId = setInterval(this.updatePosition.bind(this), 250);
},
/**
* Attach the bubble to the element.
*/
attachTo: function(element) {
var parent = element.parentElement;
if (!parent)
return;
if (parent.tagName == 'TD') {
// To make absolute positioning work inside a table cell we need
// to wrap the bubble div into another div with position:relative.
// This only works properly if the element is the first child of the
// table cell which is true for all options pages.
this.wrapper = cr.doc.createElement('div');
this.wrapper.className = 'search-bubble-wrapper';
this.wrapper.appendChild(this);
parent.insertBefore(this.wrapper, element);
} else {
parent.insertBefore(this, element);
}
},
/**
* Clear the interval timer and remove the element from the page.
*/
dispose: function() {
clearInterval(this.intervalId);
var child = this.wrapper || this;
var parent = child.parentNode;
if (parent)
parent.removeChild(child);
},
/**
* Update the position of the bubble. Called at creation time and then
* periodically while the bubble remains visible.
*/
updatePosition: function() {
// This bubble is 'owned' by the next sibling.
var owner = (this.wrapper || this).nextSibling;
// If there isn't an offset parent, we have nothing to do.
if (!owner.offsetParent)
return;
// Position the bubble below the location of the owner.
var left = owner.offsetLeft + owner.offsetWidth / 2 -
this.offsetWidth / 2;
var top = owner.offsetTop + owner.offsetHeight;
// Update the position in the CSS. Cache the last values for
// best performance.
if (left != this.lastLeft) {
this.style.left = left + 'px';
this.lastLeft = left;
}
if (top != this.lastTop) {
this.style.top = top + 'px';
this.lastTop = top;
}
}
}
/**
* Encapsulated handling of the search page.
* @constructor
*/
function SearchPage() {
OptionsPage.call(this, 'search', templateData.searchPageTabTitle,
'searchPage');
}
cr.addSingletonGetter(SearchPage);
SearchPage.prototype = {
// Inherit SearchPage from OptionsPage.
__proto__: OptionsPage.prototype,
/**
* A boolean to prevent recursion. Used by setSearchText_().
* @type {Boolean}
* @private
*/
insideSetSearchText_: false,
/**
* Initialize the page.
*/
initializePage: function() {
// Call base class implementation to start preference initialization.
OptionsPage.prototype.initializePage.call(this);
var self = this;
// Create a search field element.
var searchField = document.createElement('input');
searchField.id = 'search-field';
searchField.type = 'search';
searchField.incremental = true;
searchField.placeholder = localStrings.getString('searchPlaceholder');
searchField.setAttribute('aria-label', searchField.placeholder);
this.searchField = searchField;
// Replace the contents of the navigation tab with the search field.
self.tab.textContent = '';
self.tab.appendChild(searchField);
self.tab.onclick = self.tab.onkeydown = self.tab.onkeypress = undefined;
self.tab.tabIndex = -1;
self.tab.setAttribute('role', '');
// Don't allow the focus on the search navbar. http://crbug.com/77989
self.tab.onfocus = self.tab.blur;
// Handle search events. (No need to throttle, WebKit's search field
// will do that automatically.)
searchField.onsearch = function(e) {
self.setSearchText_(this.value);
};
// We update the history stack every time the search field blurs. This way
// we get a history entry for each search, roughly, but not each letter
// typed.
searchField.onblur = function(e) {
var query = SearchPage.canonicalizeQuery(searchField.value);
if (!query)
return;
// Don't push the same page onto the history stack more than once (if
// the user clicks in the search field and away several times).
var currentHash = location.hash;
var newHash = '#' + escape(query);
if (currentHash == newHash)
return;
// If there is no hash on the current URL, the history entry has no
// search query. Replace the history entry with no search with an entry
// that does have a search. Otherwise, add it onto the history stack.
var historyFunction = currentHash ? window.history.pushState :
window.history.replaceState;
historyFunction.call(
window.history,
{pageName: self.name},
self.title,
'/' + self.name + newHash);
};
// Install handler for key presses.
document.addEventListener('keydown',
this.keyDownEventHandler_.bind(this));
// Focus the search field by default.
searchField.focus();
},
/**
* @inheritDoc
*/
get sticky() {
return true;
},
/**
* Called after this page has shown.
*/
didShowPage: function() {
// This method is called by the Options page after all pages have
// had their visibilty attribute set. At this point we can perform the
// search specific DOM manipulation.
this.setSearchActive_(true);
},
/**
* Called before this page will be hidden.
*/
willHidePage: function() {
// This method is called by the Options page before all pages have
// their visibilty attribute set. Before that happens, we need to
// undo the search specific DOM manipulation that was performed in
// didShowPage.
this.setSearchActive_(false);
},
/**
* Update the UI to reflect whether we are in a search state.
* @param {boolean} active True if we are on the search page.
* @private
*/
setSearchActive_: function(active) {
// It's fine to exit if search wasn't active and we're not going to
// activate it now.
if (!this.searchActive_ && !active)
return;
this.searchActive_ = active;
if (active) {
var hash = location.hash;
if (hash)
this.searchField.value = unescape(hash.slice(1));
} else {
// Just wipe out any active search text since it's no longer relevant.
this.searchField.value = '';
}
var pagesToSearch = this.getSearchablePages_();
for (var key in pagesToSearch) {
var page = pagesToSearch[key];
if (!active)
page.visible = false;
// Update the visible state of all top-level elements that are not
// sections (ie titles, button strips). We do this before changing
// the page visibility to avoid excessive re-draw.
for (var i = 0, childDiv; childDiv = page.pageDiv.children[i]; i++) {
if (childDiv.classList.contains('displaytable')) {
childDiv.setAttribute('searching', active ? 'true' : 'false');
for (var j = 0, subDiv; subDiv = childDiv.children[j]; j++) {
if (active) {
if (subDiv.tagName != 'SECTION')
subDiv.classList.add('search-hidden');
} else {
subDiv.classList.remove('search-hidden');
}
}
} else {
if (active)
childDiv.classList.add('search-hidden');
else
childDiv.classList.remove('search-hidden');
}
}
if (active) {
// When search is active, remove the 'hidden' tag. This tag may have
// been added by the OptionsPage.
page.pageDiv.hidden = false;
}
}
if (active) {
this.setSearchText_(this.searchField.value);
} else {
// After hiding all page content, remove any search results.
this.unhighlightMatches_();
this.removeSearchBubbles_();
}
},
/**
* Set the current search criteria.
* @param {string} text Search text.
* @private
*/
setSearchText_: function(text) {
// Prevent recursive execution of this method.
if (this.insideSetSearchText_) return;
this.insideSetSearchText_ = true;
// Cleanup the search query string.
text = SearchPage.canonicalizeQuery(text);
// Notify listeners about the new search query, some pages may wish to
// show/hide elements based on the query.
var event = new cr.Event('searchChanged');
event.searchText = text;
this.dispatchEvent(event);
// Toggle the search page if necessary.
if (text.length) {
if (!this.searchActive_)
OptionsPage.navigateToPage(this.name);
} else {
if (this.searchActive_)
OptionsPage.showDefaultPage();
this.insideSetSearchText_ = false;
return;
}
var foundMatches = false;
var bubbleControls = [];
// Remove any prior search results.
this.unhighlightMatches_();
this.removeSearchBubbles_();
// Generate search text by applying lowercase and escaping any characters
// that would be problematic for regular expressions.
var searchText =
text.toLowerCase().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
// Generate a regular expression and replace string for hilighting
// search terms.
var regEx = new RegExp('(' + searchText + ')', 'ig');
var replaceString = '<span class="search-highlighted">$1</span>';
// Initialize all sections. If the search string matches a title page,
// show sections for that page.
var page, pageMatch, childDiv, length;
var pagesToSearch = this.getSearchablePages_();
for (var key in pagesToSearch) {
page = pagesToSearch[key];
pageMatch = false;
if (searchText.length) {
pageMatch = this.performReplace_(regEx, replaceString, page.tab);
}
if (pageMatch)
foundMatches = true;
var elements = page.pageDiv.querySelectorAll('.displaytable > section');
for (var i = 0, node; node = elements[i]; i++) {
if (pageMatch)
node.classList.remove('search-hidden');
else
node.classList.add('search-hidden');
}
}
if (searchText.length) {
// Search all top-level sections for anchored string matches.
for (var key in pagesToSearch) {
page = pagesToSearch[key];
var elements =
page.pageDiv.querySelectorAll('.displaytable > section');
for (var i = 0, node; node = elements[i]; i++) {
if (this.performReplace_(regEx, replaceString, node)) {
node.classList.remove('search-hidden');
foundMatches = true;
}
}
}
// Search all sub-pages, generating an array of top-level sections that
// we need to make visible.
var subPagesToSearch = this.getSearchableSubPages_();
var control, node;
for (var key in subPagesToSearch) {
page = subPagesToSearch[key];
if (this.performReplace_(regEx, replaceString, page.pageDiv)) {
// Reveal the section for this search result.
section = page.associatedSection;
if (section)
section.classList.remove('search-hidden');
// Identify any controls that should have bubbles.
var controls = page.associatedControls;
if (controls) {
length = controls.length;
for (var i = 0; i < length; i++)
bubbleControls.push(controls[i]);
}
foundMatches = true;
}
}
}
// Configure elements on the search results page based on search results.
if (foundMatches)
$('searchPageNoMatches').classList.add('search-hidden');
else
$('searchPageNoMatches').classList.remove('search-hidden');
// Create search balloons for sub-page results.
length = bubbleControls.length;
for (var i = 0; i < length; i++)
this.createSearchBubble_(bubbleControls[i], text);
// Cleanup the recursion-prevention variable.
this.insideSetSearchText_ = false;
},
/**
* Performs a string replacement based on a regex and replace string.
* @param {RegEx} regex A regular expression for finding search matches.
* @param {String} replace A string to apply the replace operation.
* @param {Element} element An HTML container element.
* @returns {Boolean} true if the element was changed.
* @private
*/
performReplace_: function(regex, replace, element) {
var found = false;
var div, child, tmp;
// Walk the tree, searching each TEXT node.
var walker = document.createTreeWalker(element,
NodeFilter.SHOW_TEXT,
null,
false);
var node = walker.nextNode();
while (node) {
// Perform a search and replace on the text node value.
var newValue = node.nodeValue.replace(regex, replace);
if (newValue != node.nodeValue) {
// The text node has changed so that means we found at least one
// match.
found = true;
// Create a temporary div element and set the innerHTML to the new
// value.
div = document.createElement('div');
div.innerHTML = newValue;
// Insert all the child nodes of the temporary div element into the
// document, before the original node.
child = div.firstChild;
while (child = div.firstChild) {
node.parentNode.insertBefore(child, node);
};
// Delete the old text node and advance the walker to the next
// node.
tmp = node;
node = walker.nextNode();
tmp.parentNode.removeChild(tmp);
} else {
node = walker.nextNode();
}
}
return found;
},
/**
* Removes all search highlight tags from the document.
* @private
*/
unhighlightMatches_: function() {
// Find all search highlight elements.
var elements = document.querySelectorAll('.search-highlighted');
// For each element, remove the highlighting.
var parent, i;
for (var i = 0, node; node = elements[i]; i++) {
parent = node.parentNode;
// Replace the highlight element with the first child (the text node).
parent.replaceChild(node.firstChild, node);
// Normalize the parent so that multiple text nodes will be combined.
parent.normalize();
}
},
/**
* Creates a search result bubble attached to an element.
* @param {Element} element An HTML element, usually a button.
* @param {string} text A string to show in the bubble.
* @private
*/
createSearchBubble_: function(element, text) {
// avoid appending multiple bubbles to a button.
var sibling = element.previousElementSibling;
if (sibling && (sibling.classList.contains('search-bubble') ||
sibling.classList.contains('search-bubble-wrapper')))
return;
var parent = element.parentElement;
if (parent) {
var bubble = new SearchBubble(text);
bubble.attachTo(element);
bubble.updatePosition();
}
},
/**
* Removes all search match bubbles.
* @private
*/
removeSearchBubbles_: function() {
var elements = document.querySelectorAll('.search-bubble');
var length = elements.length;
for (var i = 0; i < length; i++)
elements[i].dispose();
},
/**
* Builds a list of top-level pages to search. Omits the search page and
* all sub-pages.
* @returns {Array} An array of pages to search.
* @private
*/
getSearchablePages_: function() {
var name, page, pages = [];
for (name in OptionsPage.registeredPages) {
if (name != this.name) {
page = OptionsPage.registeredPages[name];
if (!page.parentPage)
pages.push(page);
}
}
return pages;
},
/**
* Builds a list of sub-pages (and overlay pages) to search. Ignore pages
* that have no associated controls.
* @returns {Array} An array of pages to search.
* @private
*/
getSearchableSubPages_: function() {
var name, pageInfo, page, pages = [];
for (name in OptionsPage.registeredPages) {
page = OptionsPage.registeredPages[name];
if (page.parentPage && page.associatedSection)
pages.push(page);
}
for (name in OptionsPage.registeredOverlayPages) {
page = OptionsPage.registeredOverlayPages[name];
if (page.associatedSection && page.pageDiv != undefined)
pages.push(page);
}
return pages;
},
/**
* A function to handle key press events.
* @return {Event} a keydown event.
* @private
*/
keyDownEventHandler_: function(event) {
const ESCAPE_KEY_CODE = 27;
const FORWARD_SLASH_KEY_CODE = 191;
switch(event.keyCode) {
case ESCAPE_KEY_CODE:
if (event.target == this.searchField) {
this.setSearchText_('');
this.searchField.blur();
event.stopPropagation();
event.preventDefault();
}
break;
case FORWARD_SLASH_KEY_CODE:
if (!/INPUT|SELECT|BUTTON|TEXTAREA/.test(event.target.tagName) &&
!event.ctrlKey && !event.altKey) {
this.searchField.focus();
event.stopPropagation();
event.preventDefault();
}
break;
}
},
};
/**
* Standardizes a user-entered text query by removing extra whitespace.
* @param {string} The user-entered text.
* @return {string} The trimmed query.
*/
SearchPage.canonicalizeQuery = function(text) {
// Trim beginning and ending whitespace.
return text.replace(/^\s+|\s+$/g, '');
};
// Export
return {
SearchPage: SearchPage
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options', function() {
const OptionsPage = options.OptionsPage;
// Variable to track if a captcha challenge was issued. If this gets set to
// true, it stays that way until we are told about successful login from
// the browser. This means subsequent errors (like invalid password) are
// rendered in the captcha state, which is basically identical except we
// don't show the top error blurb "Error Signing in" or the "Create
// account" link.
var captchaChallengeActive_ = false;
// True if the synced account uses a custom passphrase.
var usePassphrase_ = false;
// True if the synced account uses 'encrypt everything'.
var useEncryptEverything_ = false;
/**
* SyncSetupOverlay class
* Encapsulated handling of the 'Sync Setup' overlay page.
* @class
*/
function SyncSetupOverlay() {
OptionsPage.call(this, 'syncSetup',
templateData.syncSetupOverlayTitle,
'sync-setup-overlay');
}
cr.addSingletonGetter(SyncSetupOverlay);
SyncSetupOverlay.prototype = {
__proto__: OptionsPage.prototype,
/**
* Initializes the page.
*/
initializePage: function() {
OptionsPage.prototype.initializePage.call(this);
var self = this;
$('gaia-login-form').onsubmit = function() {
self.sendCredentialsAndClose_();
return false;
};
$('google-option').onchange = $('explicit-option').onchange = function() {
self.onPassphraseRadioChanged_();
};
$('choose-datatypes-cancel').onclick =
$('sync-setup-cancel').onclick =
$('confirm-everything-cancel').onclick =
$('stop-syncing-cancel').onclick = function() {
self.closeOverlay_();
};
$('confirm-everything-ok').onclick = function() {
self.sendConfiguration_();
};
$('stop-syncing-ok').onclick = function() {
chrome.send('stopSyncing');
self.closeOverlay_();
};
},
showOverlay_: function() {
OptionsPage.navigateToPage('syncSetup');
},
closeOverlay_: function() {
OptionsPage.closeOverlay();
},
/** @inheritDoc */
didShowPage: function() {
chrome.send('SyncSetupAttachHandler');
},
/** @inheritDoc */
didClosePage: function() {
chrome.send('SyncSetupDidClosePage');
},
getEncryptionRadioCheckedValue_: function() {
var f = $('choose-data-types-form');
for (var i = 0; i < f.encrypt.length; ++i) {
if (f.encrypt[i].checked) {
return f.encrypt[i].value;
}
}
return undefined;
},
getPassphraseRadioCheckedValue_: function() {
var f = $('choose-data-types-form');
for (var i = 0; i < f.option.length; ++i) {
if (f.option[i].checked) {
return f.option[i].value;
}
}
return undefined;
},
disableEncryptionRadioGroup_: function() {
var f = $('choose-data-types-form');
for (var i = 0; i < f.encrypt.length; ++i)
f.encrypt[i].disabled = true;
},
onPassphraseRadioChanged_: function() {
var visible = this.getPassphraseRadioCheckedValue_() == "explicit";
$('sync-custom-passphrase').hidden = !visible;
},
checkAllDataTypeCheckboxes_: function() {
var checkboxes = document.getElementsByName("dataTypeCheckbox");
for (var i = 0; i < checkboxes.length; i++) {
// Only check the visible ones (since there's no way to uncheck
// the invisible ones).
if (checkboxes[i].parentElement.className == "sync-item-show") {
checkboxes[i].checked = true;
}
}
},
setDataTypeCheckboxesEnabled_: function(enabled) {
var checkboxes = document.getElementsByName("dataTypeCheckbox");
var labels = document.getElementsByName("dataTypeLabel");
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].disabled = !enabled;
if (checkboxes[i].disabled) {
labels[i].className = "sync-label-inactive";
} else {
labels[i].className = "sync-label-active";
}
}
},
setCheckboxesToKeepEverythingSynced_: function(value) {
this.setDataTypeCheckboxesEnabled_(!value);
if (value)
this.checkAllDataTypeCheckboxes_();
},
// Returns true if at least one data type is enabled and no data types are
// checked. (If all data type checkboxes are disabled, it's because "keep
// everything synced" is checked.)
noDataTypesChecked_: function() {
var checkboxes = document.getElementsByName("dataTypeCheckbox");
var atLeastOneChecked = false;
var atLeastOneEnabled = false;
for (var i = 0; i < checkboxes.length; i++) {
if (!checkboxes[i].disabled &&
checkboxes[i].parentElement.className == "sync-item-show") {
atLeastOneEnabled = true;
if (checkboxes[i].checked) {
atLeastOneChecked = true;
}
}
}
return atLeastOneEnabled && !atLeastOneChecked;
},
checkPassphraseMatch_: function() {
var emptyError = $('empty-error');
var mismatchError = $('mismatch-error');
emptyError.hidden = true;
mismatchError.hidden = true;
var f = $('choose-data-types-form');
if (this.getPassphraseRadioCheckedValue_() != "explicit" ||
$('google-option').disabled)
return true;
var customPassphrase = $('custom-passphrase');
if (customPassphrase.value.length == 0) {
emptyError.hidden = false;
return false;
}
var confirmPassphrase = $('confirm-passphrase');
if (confirmPassphrase.value != customPassphrase.value) {
mismatchError.hidden = false;
return false;
}
return true;
},
sendConfiguration_: function() {
// Trying to submit, so hide previous errors.
$('error-text').hidden = true;
if (this.noDataTypesChecked_()) {
$('error-text').hidden = false;
return;
}
var f = $('choose-data-types-form');
var syncAll = $('sync-select-datatypes').selectedIndex == 0;
var encryptAllData = this.getEncryptionRadioCheckedValue_() == 'all';
var usePassphrase;
var customPassphrase;
var googlePassphrase = false;
if (!$('sync-existing-passphrase-container').hidden) {
// If we were prompted for an existing passphrase, use it.
customPassphrase = f.passphrase.value;
usePassphrase = true;
// If we were displaying the "enter your old google password" prompt,
// then that means this is the user's google password.
googlePassphrase = !$('google-passphrase-needed-body').hidden;
// We allow an empty passphrase, in case the user has disabled
// all their encrypted datatypes. In that case, the PSS will accept
// the passphrase and finish configuration. If the user has enabled
// encrypted datatypes, the PSS will prompt again specifying that the
// passphrase failed.
} else if (!$('google-option').disabled &&
this.getPassphraseRadioCheckedValue_() == 'explicit') {
// The user is setting a custom passphrase for the first time.
if (!this.checkPassphraseMatch_())
return;
customPassphrase = $('custom-passphrase').value;
usePassphrase = true;
} else {
// The user is not setting a custom passphrase.
usePassphrase = false;
}
// Don't allow the user to tweak the settings once we send the
// configuration to the backend.
this.setInputElementsDisabledState_(true);
this.animateDisableLink_($('use-default-link'), true, null);
// These values need to be kept in sync with where they are read in
// SyncSetupFlow::GetDataTypeChoiceData().
var result = JSON.stringify({
"syncAllDataTypes": syncAll,
"syncBookmarks": syncAll || $('bookmarks-checkbox').checked,
"syncPreferences": syncAll || $('preferences-checkbox').checked,
"syncThemes": syncAll || $('themes-checkbox').checked,
"syncPasswords": syncAll || $('passwords-checkbox').checked,
"syncAutofill": syncAll || $('autofill-checkbox').checked,
"syncExtensions": syncAll || $('extensions-checkbox').checked,
"syncTypedUrls": syncAll || $('typed-urls-checkbox').checked,
"syncApps": syncAll || $('apps-checkbox').checked,
"syncSessions": syncAll || $('sessions-checkbox').checked,
"encryptAllData": encryptAllData,
"usePassphrase": usePassphrase,
"isGooglePassphrase": googlePassphrase,
"passphrase": customPassphrase
});
chrome.send('SyncSetupConfigure', [result]);
},
/**
* Sets the disabled property of all input elements within the 'Customize
* Sync Preferences' screen. This is used to prohibit the user from changing
* the inputs after confirming the customized sync preferences, or resetting
* the state when re-showing the dialog.
* @param disabled True if controls should be set to disabled.
* @private
*/
setInputElementsDisabledState_: function(disabled) {
var configureElements =
$('customize-sync-preferences').querySelectorAll('input');
for (var i = 0; i < configureElements.length; i++)
configureElements[i].disabled = disabled;
$('sync-select-datatypes').disabled = disabled;
var self = this;
this.animateDisableLink_($('customize-link'), disabled, function() {
self.showCustomizePage_(null, true);
});
},
/**
* Animate a link being enabled/disabled. The link is hidden by animating
* its opacity, but to ensure the user doesn't click it during that time,
* its onclick handler is changed to null as well.
* @param elt The anchor element to enable/disable.
* @param disabled True if the link should be disabled.
* @param enabledFunction The onclick handler when the link is enabled.
* @private
*/
animateDisableLink_: function(elt, disabled, enabledFunction) {
if (disabled) {
elt.classList.add('transparent');
elt.onclick = null;
elt.addEventListener('webkitTransitionEnd', function f(e) {
if (e.propertyName != 'opacity')
return;
elt.removeEventListener('webkitTransitionEnd', f);
elt.classList.remove('transparent');
elt.hidden = true;
});
} else {
elt.hidden = false;
elt.onclick = enabledFunction;
}
},
setChooseDataTypesCheckboxes_: function(args) {
var datatypeSelect = document.getElementById('sync-select-datatypes');
datatypeSelect.selectedIndex = args.syncAllDataTypes ? 0 : 1;
$('bookmarks-checkbox').checked = args.syncBookmarks;
$('preferences-checkbox').checked = args.syncPreferences;
$('themes-checkbox').checked = args.syncThemes;
if (args.passwordsRegistered) {
$('passwords-checkbox').checked = args.syncPasswords;
$('passwords-item').className = "sync-item-show";
} else {
$('passwords-item').className = "sync-item-hide";
}
if (args.autofillRegistered) {
$('autofill-checkbox').checked = args.syncAutofill;
$('autofill-item').className = "sync-item-show";
} else {
$('autofill-item').className = "sync-item-hide";
}
if (args.extensionsRegistered) {
$('extensions-checkbox').checked = args.syncExtensions;
$('extensions-item').className = "sync-item-show";
} else {
$('extensions-item').className = "sync-item-hide";
}
if (args.typedUrlsRegistered) {
$('typed-urls-checkbox').checked = args.syncTypedUrls;
$('omnibox-item').className = "sync-item-show";
} else {
$('omnibox-item').className = "sync-item-hide";
}
if (args.appsRegistered) {
$('apps-checkbox').checked = args.syncApps;
$('apps-item').className = "sync-item-show";
} else {
$('apps-item').className = "sync-item-hide";
}
if (args.sessionsRegistered) {
$('sessions-checkbox').checked = args.syncSessions;
$('sessions-item').className = "sync-item-show";
} else {
$('sessions-item').className = "sync-item-hide";
}
this.setCheckboxesToKeepEverythingSynced_(args.syncAllDataTypes);
},
setEncryptionRadios_: function(args) {
if (args['encryptAllData']) {
$('encrypt-all-option').checked = true;
this.disableEncryptionRadioGroup_();
} else {
$('encrypt-sensitive-option').checked = true;
}
},
setPassphraseRadios_: function(args) {
if (args['usePassphrase']) {
$('explicit-option').checked = true;
// The passphrase, once set, cannot be unset, but we show a reset link.
$('explicit-option').disabled = true;
$('google-option').disabled = true;
$('sync-custom-passphrase').hidden = true;
} else {
$('google-option').checked = true;
}
},
setCheckboxesAndErrors_: function(args) {
this.setChooseDataTypesCheckboxes_(args);
this.setEncryptionRadios_(args);
this.setPassphraseRadios_(args);
},
showConfigure_: function(args) {
var datatypeSelect = document.getElementById('sync-select-datatypes');
var self = this;
datatypeSelect.onchange = function() {
var syncAll = this.selectedIndex == 0;
self.setCheckboxesToKeepEverythingSynced_(syncAll);
};
this.resetPage_('sync-setup-configure');
$('sync-setup-configure').hidden = false;
// onsubmit is changed when submitting a passphrase. Reset it to its
// default.
$('choose-data-types-form').onsubmit = function() {
self.sendConfiguration_();
return false;
};
if (args) {
if (!args['encryptionEnabled'])
$('customize-sync-encryption').hidden = true;
this.setCheckboxesAndErrors_(args);
this.useEncryptEverything_ = args['encryptAllData'];
// Whether to display the 'Sync everything' confirmation page or the
// customize data types page.
var syncAllDataTypes = args['syncAllDataTypes'];
this.usePassphrase_ = args['usePassphrase'];
if (args['showSyncEverythingPage'] == false || this.usePassphrase_ ||
syncAllDataTypes == false || args['show_passphrase']) {
this.showCustomizePage_(args, syncAllDataTypes);
} else {
this.showSyncEverythingPage_();
}
}
},
showSyncEverythingPage_: function() {
$('confirm-sync-preferences').hidden = false;
$('customize-sync-preferences').hidden = true;
// Reset the selection to 'Sync everything'.
$('sync-select-datatypes').selectedIndex = 0;
// The default state is to sync everything.
this.setCheckboxesToKeepEverythingSynced_(true);
// Encrypt passwords is the default, but don't set it if the previously
// synced account is already set to encrypt everything.
if (!this.useEncryptEverything_)
$('encrypt-sensitive-option').checked = true;
// If the account is not synced with a custom passphrase, reset the
// passphrase radio when switching to the 'Sync everything' page.
if (!this.usePassphrase_) {
$('google-option').checked = true;
$('sync-custom-passphrase').hidden = true;
}
$('confirm-everything-ok').focus();
},
/**
* Reveals the UI for entering a custom passphrase during initial setup.
* This happens if the user has previously enabled a custom passphrase on a
* different machine.
* @param {Array} args The args that contain the passphrase UI
* configuration.
* @private
*/
showPassphraseContainer_: function(args) {
// Once we require a passphrase, we prevent the user from returning to
// the Sync Everything pane.
$('use-default-link').hidden = true;
$('sync-custom-passphrase-container').hidden = true;
$('sync-existing-passphrase-container').hidden = false;
$('passphrase-rejected-body').hidden = true;
$('normal-body').hidden = true;
$('google-passphrase-needed-body').hidden = true;
// Display the correct prompt to the user depending on what type of
// passphrase is needed.
if (args["need_google_passphrase"])
$('google-passphrase-needed-body').hidden = false;
else if (args["passphrase_creation_rejected"])
$('passphrase-rejected-body').hidden = false;
else
$('normal-body').hidden = false;
$('incorrect-passphrase').hidden = !args["passphrase_setting_rejected"];
$('sync-passphrase-warning').hidden = false;
$('passphrase').focus();
},
showCustomizePage_: function(args, syncEverything) {
$('confirm-sync-preferences').hidden = true;
$('customize-sync-preferences').hidden = false;
$('sync-custom-passphrase-container').hidden = false;
$('sync-existing-passphrase-container').hidden = true;
// If the user has selected the 'Customize' page on initial set up, it's
// likely he intends to change the data types. Select the
// 'Choose data types' option in this case.
var index = syncEverything ? 0 : 1;
document.getElementById('sync-select-datatypes').selectedIndex = index;
this.setDataTypeCheckboxesEnabled_(!syncEverything);
// The passphrase input may need to take over focus from the OK button, so
// set focus before that logic.
$('choose-datatypes-ok').focus();
if (args && args['show_passphrase']) {
this.showPassphraseContainer_(args);
} else {
// We only show the "Use Default" link if we're not prompting for an
// existing passphrase.
var self = this;
this.animateDisableLink_($('use-default-link'), false, function() {
self.showSyncEverythingPage_();
});
}
},
attach_: function() {
chrome.send('SyncSetupAttachHandler');
},
showSyncSetupPage_: function(page, args) {
if (page == 'settingUp') {
this.setThrobbersVisible_(true);
return;
} else {
this.setThrobbersVisible_(false);
}
// Hide an existing visible overlay.
var overlay = $('sync-setup-overlay');
for (var i = 0; i < overlay.children.length; i++)
overlay.children[i].hidden = true;
this.setInputElementsDisabledState_(false);
if (page == 'login')
this.showGaiaLogin_(args);
else if (page == 'configure' || page == 'passphrase')
this.showConfigure_(args);
if (page == 'done')
this.closeOverlay_();
else
this.showOverlay_();
},
/**
* Changes the visibility of throbbers on this page.
* @param {boolean} visible Whether or not to set all throbber nodes
* visible.
*/
setThrobbersVisible_: function(visible) {
var throbbers = document.getElementsByClassName("throbber");
for (var i = 0; i < throbbers.length; i++)
throbbers[i].style.visibility = visible ? "visible" : "hidden";
},
loginSetFocus_: function() {
var email = $('gaia-email');
var passwd = $('gaia-passwd');
if (email && (email.value == null || email.value == "")) {
email.focus();
} else if (passwd) {
passwd.focus();
}
},
/**
* Get the login email text input DOM element.
* @return {DOMElement} The login email text input.
* @private
*/
getLoginEmail_: function() {
return $('gaia-email');
},
/**
* Get the login password text input DOM element.
* @return {DOMElement} The login password text input.
* @private
*/
getLoginPasswd_: function() {
return $('gaia-passwd');
},
/**
* Get the sign in button DOM element.
* @return {DOMElement} The sign in button.
* @private
*/
getSignInButton_: function() {
return $('sign-in');
},
showAccessCodeRequired_: function() {
$('password-row').hidden = true;
$('email-row').hidden = true;
$('access-code-input-row').hidden = false;
$('access-code').disabled = false;
$('access-code').focus();
},
showCaptcha_: function(args) {
this.captchaChallengeActive_ = true;
// The captcha takes up lots of space, so make room.
$('top-blurb-error').hidden = true;
$('create-account-div').hidden = true;
// It's showtime for the captcha now.
$('captcha-div').hidden = false;
$('gaia-email').disabled = true;
$('gaia-passwd').disabled = false;
$('captcha-value').disabled = false;
$('captcha-wrapper').style.backgroundImage = url(args.captchaUrl);
},
/**
* Reset the state of all descendant elements of a root element to their
* initial state.
* The initial state is specified by adding a class to the descendant
* element in sync_setup_overlay.html.
* @param pageElementId The root page element id.
* @private
*/
resetPage_: function(pageElementId) {
var page = $(pageElementId);
var forEach = function(arr, fn) {
var length = arr.length;
for (var i = 0; i < length; i++) {
fn(arr[i]);
}
};
forEach(page.getElementsByClassName('reset-hidden'),
function(elt) { elt.hidden = true; });
forEach(page.getElementsByClassName('reset-shown'),
function(elt) { elt.hidden = false; });
forEach(page.getElementsByClassName('reset-disabled'),
function(elt) { elt.disabled = true; });
forEach(page.getElementsByClassName('reset-enabled'),
function(elt) { elt.disabled = false; });
forEach(page.getElementsByClassName('reset-value'),
function(elt) { elt.value = ''; });
forEach(page.getElementsByClassName('reset-opaque'),
function(elt) { elt.classList.remove('transparent'); });
},
showGaiaLogin_: function(args) {
this.resetPage_('sync-setup-login');
$('sync-setup-login').hidden = false;
var f = $('gaia-login-form');
var email = $('gaia-email');
var passwd = $('gaia-passwd');
if (f) {
if (args.user != undefined) {
if (email.value != args.user)
passwd.value = ""; // Reset the password field
email.value = args.user;
}
if (!args.editable_user) {
email.hidden = true;
var span = $('email-readonly');
span.textContent = email.value;
span.hidden = false;
$('create-account-div').hidden = true;
}
f.accessCode.disabled = true;
}
if (1 == args.error) {
var access_code = document.getElementById('access-code');
if (access_code.value && access_code.value != "") {
$('errormsg-0-access-code').hidden = false;
this.showAccessCodeRequired_();
} else {
$('errormsg-1-password').hidden = false;
}
this.setBlurbError_(args.error_message);
} else if (3 == args.error) {
$('errormsg-0-connection').hidden = false;
this.setBlurbError_(args.error_message);
} else if (4 == args.error) {
this.showCaptcha_(args);
} else if (7 == args.error) {
this.setBlurbError_(localStrings.getString('serviceUnavailableError'));
} else if (8 == args.error) {
this.showAccessCodeRequired_();
} else if (args.error_message) {
this.setBlurbError_(args.error_message);
}
if (args.fatalError) {
$('errormsg-fatal').hidden = false;
$('sign-in').disabled = true;
return;
}
$('sign-in').disabled = false;
$('sign-in').value = templateData['signin'];
this.loginSetFocus_();
},
resetErrorVisibility_: function() {
$("errormsg-0-email").hidden = true;
$("errormsg-0-password").hidden = true;
$("errormsg-1-password").hidden = true;
$("errormsg-0-connection").hidden = true;
$("errormsg-0-access-code").hidden = true;
},
setBlurbError_: function(error_message) {
if (this.captchaChallengeActive_)
return; // No blurb in captcha challenge mode.
if (error_message) {
$('error-signing-in').hidden = true;
$('error-custom').hidden = false;
$('error-custom').textContent = error_message;
} else {
$('error-signing-in').hidden = false;
$('error-custom').hidden = true;
}
$('top-blurb-error').hidden = false;
$('gaia-email').disabled = false;
$('gaia-passwd').disabled = false;
},
matchesASPRegex_: function(toMatch) {
var noSpaces = /[a-z]{16}/;
var withSpaces = /([a-z]{4}\s){3}[a-z]{4}/;
if (toMatch.match(noSpaces) || toMatch.match(withSpaces))
return true;
return false;
},
setErrorVisibility_: function() {
this.resetErrorVisibility_();
var f = $('gaia-login-form');
var email = $('gaia-email');
var passwd = $('gaia-passwd');
if (null == email.value || "" == email.value) {
$('errormsg-0-email').hidden = false;
this.setBlurbError_();
return false;
}
// Don't enforce password being non-blank when checking access code (it
// will have been cleared when the page was displayed).
if (f.accessCode.disabled && (null == passwd.value ||
"" == passwd.value)) {
$('errormsg-0-password').hidden = false;
this.setBlurbError_();
return false;
}
if (!f.accessCode.disabled && (null == f.accessCode.value ||
"" == f.accessCode.value)) {
$('errormsg-0-password').hidden = false;
return false;
}
if (f.accessCode.disabled && this.matchesASPRegex_(passwd.value) &&
$('asp-warning-div').hidden) {
$('asp-warning-div').hidden = false;
$('gaia-passwd').value = "";
return false;
}
return true;
},
sendCredentialsAndClose_: function() {
if (!this.setErrorVisibility_()) {
return false;
}
$('gaia-email').disabled = true;
$('gaia-passwd').disabled = true;
$('captcha-value').disabled = true;
$('access-code').disabled = true;
this.setThrobbersVisible_(true);
var f = $('gaia-login-form');
var email = $('gaia-email');
var passwd = $('gaia-passwd');
var result = JSON.stringify({"user" : email.value,
"pass" : passwd.value,
"captcha" : f.captchaValue.value,
"access_code" : f.accessCode.value});
$('sign-in').disabled = true;
chrome.send('SyncSetupSubmitAuth', [result]);
},
showSuccessAndClose_: function() {
$('sign-in').value = localStrings.getString('loginSuccess');
setTimeout(this.closeOverlay_, 1600);
},
showSuccessAndSettingUp_: function() {
$('sign-in').value = localStrings.getString('settingUp');
$('top-blurb-error').hidden = true;
},
/**
* Displays the stop syncing dialog.
* @private
*/
showStopSyncingUI_: function() {
// Hide any visible children of the overlay.
var overlay = $('sync-setup-overlay');
for (var i = 0; i < overlay.children.length; i++)
overlay.children[i].hidden = true;
// Bypass OptionsPage.navigateToPage because it will call didShowPage
// which will set its own visible page, based on the flow state.
this.visible = true;
$('sync-setup-stop-syncing').hidden = false;
$('stop-syncing-cancel').focus();
},
/**
* Steps into the appropriate Sync Setup error UI.
* @private
*/
showErrorUI_: function() {
chrome.send('SyncSetupShowErrorUI');
},
/**
* Determines the appropriate page to show in the Sync Setup UI based on
* the state of the Sync backend.
* @private
*/
showSetupUI_: function() {
chrome.send('SyncSetupShowSetupUI');
},
/**
* Hides the outer elements of the login UI. This is used by the sync promo
* to customize the look of the login box.
*/
hideOuterLoginUI_: function() {
$('sync-setup-overlay-title').hidden = true;
$('sync-setup-cancel').hidden = true;
}
};
// These get methods should only be called by the WebUI tests.
SyncSetupOverlay.getLoginEmail = function() {
return SyncSetupOverlay.getInstance().getLoginEmail_();
};
SyncSetupOverlay.getLoginPasswd = function() {
return SyncSetupOverlay.getInstance().getLoginPasswd_();
};
SyncSetupOverlay.getSignInButton = function() {
return SyncSetupOverlay.getInstance().getSignInButton_();
};
// These methods are for general consumption.
SyncSetupOverlay.showErrorUI = function() {
SyncSetupOverlay.getInstance().showErrorUI_();
};
SyncSetupOverlay.showSetupUI = function() {
SyncSetupOverlay.getInstance().showSetupUI_();
};
SyncSetupOverlay.showSyncSetupPage = function(page, args) {
SyncSetupOverlay.getInstance().showSyncSetupPage_(page, args);
};
SyncSetupOverlay.showSuccessAndClose = function() {
SyncSetupOverlay.getInstance().showSuccessAndClose_();
};
SyncSetupOverlay.showSuccessAndSettingUp = function() {
SyncSetupOverlay.getInstance().showSuccessAndSettingUp_();
};
SyncSetupOverlay.showStopSyncingUI = function() {
SyncSetupOverlay.getInstance().showStopSyncingUI_();
};
// Export
return {
SyncSetupOverlay: SyncSetupOverlay
};
});
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var AddLanguageOverlay = options.AddLanguageOverlay;
var AdvancedOptions = options.AdvancedOptions;
var AlertOverlay = options.AlertOverlay;
var AutofillEditAddressOverlay = options.AutofillEditAddressOverlay;
var AutofillEditCreditCardOverlay = options.AutofillEditCreditCardOverlay;
var AutofillOptions = options.AutofillOptions;
var BrowserOptions = options.BrowserOptions;
var ClearBrowserDataOverlay = options.ClearBrowserDataOverlay;
var ContentSettings = options.ContentSettings;
var ContentSettingsExceptionsArea =
options.contentSettings.ContentSettingsExceptionsArea;
var CookiesView = options.CookiesView;
var ExtensionSettings = options.ExtensionSettings;
var FontSettings = options.FontSettings;
var HandlerOptions = options.HandlerOptions;
var ImportDataOverlay = options.ImportDataOverlay;
var IntentsView = options.IntentsView;
var InstantConfirmOverlay = options.InstantConfirmOverlay;
var LanguageOptions = options.LanguageOptions;
var OptionsPage = options.OptionsPage;
var PackExtensionOverlay = options.PackExtensionOverlay;
var PasswordManager = options.PasswordManager;
var PersonalOptions = options.PersonalOptions;
var Preferences = options.Preferences;
var ManageProfileOverlay = options.ManageProfileOverlay;
var ProxyOptions = options.ProxyOptions;
var SearchEngineManager = options.SearchEngineManager;
var SearchPage = options.SearchPage;
var SyncSetupOverlay = options.SyncSetupOverlay;
var VirtualKeyboardManager = options.VirtualKeyboardManager;
/**
* DOMContentLoaded handler, sets up the page.
*/
function load() {
// Decorate the existing elements in the document.
cr.ui.decorate('input[pref][type=checkbox]', options.PrefCheckbox);
cr.ui.decorate('input[pref][type=number]', options.PrefNumber);
cr.ui.decorate('input[pref][type=radio]', options.PrefRadio);
cr.ui.decorate('input[pref][type=range]', options.PrefRange);
cr.ui.decorate('select[pref]', options.PrefSelect);
cr.ui.decorate('input[pref][type=text]', options.PrefTextField);
cr.ui.decorate('input[pref][type=url]', options.PrefTextField);
cr.ui.decorate('button[pref]', options.PrefButton);
cr.ui.decorate('#content-settings-page input[type=radio]:not(.handler-radio)',
options.ContentSettingsRadio);
cr.ui.decorate('#content-settings-page input[type=radio].handler-radio',
options.HandlersEnabledRadio);
cr.ui.decorate('span.controlled-setting-indicator',
options.ControlledSettingIndicator);
var menuOffPattern = /(^\?|&)menu=off($|&)/;
var menuDisabled = menuOffPattern.test(window.location.search);
// document.documentElement.setAttribute('hide-menu', menuDisabled);
// We can't use an attribute on the html element because of webkit bug
// 12519. Instead, we add a class.
if (menuDisabled)
document.documentElement.classList.add('hide-menu');
localStrings = new LocalStrings();
OptionsPage.register(SearchPage.getInstance());
OptionsPage.register(BrowserOptions.getInstance());
OptionsPage.registerSubPage(SearchEngineManager.getInstance(),
BrowserOptions.getInstance(),
[$('defaultSearchManageEnginesButton')]);
OptionsPage.register(PersonalOptions.getInstance());
OptionsPage.registerSubPage(AutofillOptions.getInstance(),
PersonalOptions.getInstance(),
[$('autofill-settings')]);
OptionsPage.registerSubPage(PasswordManager.getInstance(),
PersonalOptions.getInstance(),
[$('manage-passwords')]);
if (cr.isChromeOS) {
OptionsPage.register(SystemOptions.getInstance());
OptionsPage.registerSubPage(AboutPage.getInstance(),
SystemOptions.getInstance());
OptionsPage.registerSubPage(LanguageOptions.getInstance(),
SystemOptions.getInstance(),
[$('language-button')]);
OptionsPage.registerSubPage(
new OptionsPage('languageChewing',
templateData.languageChewingPageTabTitle,
'languageChewingPage'),
LanguageOptions.getInstance());
OptionsPage.registerSubPage(
new OptionsPage('languageHangul',
templateData.languageHangulPageTabTitle,
'languageHangulPage'),
LanguageOptions.getInstance());
OptionsPage.registerSubPage(
new OptionsPage('languageMozc',
templateData.languageMozcPageTabTitle,
'languageMozcPage'),
LanguageOptions.getInstance());
OptionsPage.registerSubPage(
new OptionsPage('languagePinyin',
templateData.languagePinyinPageTabTitle,
'languagePinyinPage'),
LanguageOptions.getInstance());
// Only use the VirtualKeyboardManager if the keyboard DOM elements (which
// it will assume exists) are present (i.e. if we were built with
// USE_VIRTUAL_KEYBOARD).
if ($('language-options-virtual-keyboard')) {
OptionsPage.registerSubPage(VirtualKeyboardManager.getInstance(),
LanguageOptions.getInstance());
}
OptionsPage.register(InternetOptions.getInstance());
}
OptionsPage.register(AdvancedOptions.getInstance());
OptionsPage.registerSubPage(ContentSettings.getInstance(),
AdvancedOptions.getInstance(),
[$('privacyContentSettingsButton')]);
OptionsPage.registerSubPage(ContentSettingsExceptionsArea.getInstance(),
ContentSettings.getInstance());
OptionsPage.registerSubPage(CookiesView.getInstance(),
ContentSettings.getInstance(),
[$('privacyContentSettingsButton'),
$('show-cookies-button')]);
// If HandlerOptions is null it means it got compiled out.
if (HandlerOptions) {
OptionsPage.registerSubPage(HandlerOptions.getInstance(),
ContentSettings.getInstance(),
[$('manage-handlers-button')]);
}
if (IntentsView && $('manage-intents-button')) {
OptionsPage.registerSubPage(IntentsView.getInstance(),
ContentSettings.getInstance(),
[$('manage-intents-button')]);
}
OptionsPage.registerSubPage(FontSettings.getInstance(),
AdvancedOptions.getInstance(),
[$('fontSettingsCustomizeFontsButton')]);
if (!cr.isChromeOS) {
OptionsPage.registerSubPage(LanguageOptions.getInstance(),
AdvancedOptions.getInstance(),
[$('language-button')]);
}
if (!cr.isWindows && !cr.isMac) {
OptionsPage.registerSubPage(CertificateManager.getInstance(),
AdvancedOptions.getInstance(),
[$('certificatesManageButton')]);
OptionsPage.registerOverlay(CertificateRestoreOverlay.getInstance(),
CertificateManager.getInstance());
OptionsPage.registerOverlay(CertificateBackupOverlay.getInstance(),
CertificateManager.getInstance());
OptionsPage.registerOverlay(CertificateEditCaTrustOverlay.getInstance(),
CertificateManager.getInstance());
OptionsPage.registerOverlay(CertificateImportErrorOverlay.getInstance(),
CertificateManager.getInstance());
}
OptionsPage.registerOverlay(AddLanguageOverlay.getInstance(),
LanguageOptions.getInstance());
OptionsPage.registerOverlay(AlertOverlay.getInstance());
OptionsPage.registerOverlay(AutofillEditAddressOverlay.getInstance(),
AutofillOptions.getInstance());
OptionsPage.registerOverlay(AutofillEditCreditCardOverlay.getInstance(),
AutofillOptions.getInstance());
OptionsPage.registerOverlay(ClearBrowserDataOverlay.getInstance(),
AdvancedOptions.getInstance(),
[$('privacyClearDataButton')]);
OptionsPage.registerOverlay(ImportDataOverlay.getInstance(),
PersonalOptions.getInstance());
OptionsPage.registerOverlay(InstantConfirmOverlay.getInstance(),
BrowserOptions.getInstance());
OptionsPage.registerOverlay(SyncSetupOverlay.getInstance(),
PersonalOptions.getInstance());
OptionsPage.registerOverlay(ManageProfileOverlay.getInstance(),
PersonalOptions.getInstance());
OptionsPage.register(ExtensionSettings.getInstance());
OptionsPage.registerOverlay(PackExtensionOverlay.getInstance(),
ExtensionSettings.getInstance());
if (cr.isChromeOS) {
OptionsPage.register(AccountsOptions.getInstance());
OptionsPage.registerSubPage(ProxyOptions.getInstance(),
InternetOptions.getInstance());
OptionsPage.registerSubPage(ChangePictureOptions.getInstance(),
PersonalOptions.getInstance(),
[$('change-picture-button')]);
OptionsPage.registerOverlay(DetailsInternetPage.getInstance(),
InternetOptions.getInstance());
var languageModifierKeysOverlay = new OptionsPage(
'languageCustomizeModifierKeysOverlay',
localStrings.getString('languageCustomizeModifierKeysOverlay'),
'languageCustomizeModifierKeysOverlay')
$('languageCustomizeModifierKeysOverleyDismissButton').onclick =
function() {
OptionsPage.closeOverlay();
};
OptionsPage.registerOverlay(languageModifierKeysOverlay,
SystemOptions.getInstance(),
[$('modifier-keys-button')]);
}
Preferences.getInstance().initialize();
OptionsPage.initialize();
var path = document.location.pathname;
if (path.length > 1) {
// Skip starting slash and remove trailing slash (if any).
var pageName = path.slice(1).replace(/\/$/, '');
// Proxy page is now per network and only reachable from internet details.
if (pageName != 'proxy') {
// Show page, but don't update history (there's already an entry for it).
OptionsPage.showPageByName(pageName, false);
}
} else {
OptionsPage.showDefaultPage();
}
var subpagesNavTabs = document.querySelectorAll('.subpages-nav-tabs');
for(var i = 0; i < subpagesNavTabs.length; i++) {
subpagesNavTabs[i].onclick = function(event) {
OptionsPage.showTab(event.srcElement);
}
}
// Allow platform specific CSS rules.
cr.enablePlatformSpecificCSSRules();
if (navigator.plugins['Shockwave Flash'])
document.documentElement.setAttribute('hasFlashPlugin', '');
// Clicking on the Settings title brings up the 'Basics' page.
$('navbar-content-title').onclick = function() {
OptionsPage.navigateToPage(BrowserOptions.getInstance().name);
};
}
document.addEventListener('DOMContentLoaded', load);
window.onpopstate = function(e) {
options.OptionsPage.setState(e.state);
};
window.onbeforeunload = function() {
options.OptionsPage.willClose();
};