diff options
Diffstat (limited to 'chromium/ui/webui/resources')
94 files changed, 4996 insertions, 333 deletions
diff --git a/chromium/ui/webui/resources/PRESUBMIT.py b/chromium/ui/webui/resources/PRESUBMIT.py index 2b24710521a..26bc5ec895e 100644 --- a/chromium/ui/webui/resources/PRESUBMIT.py +++ b/chromium/ui/webui/resources/PRESUBMIT.py @@ -28,9 +28,12 @@ def _CheckForTranslations(input_api, output_api): for f in input_api.AffectedFiles(): local_path = f.LocalPath() + # Allow translation in i18n_behavior.js. if local_path.endswith('i18n_behavior.js'): continue - + # Allow translation in the cr_components directory. + if 'cr_components' in local_path: + continue keywords = None if local_path.endswith('.js'): keywords = js_keywords diff --git a/chromium/ui/webui/resources/cr_components/OWNERS b/chromium/ui/webui/resources/cr_components/OWNERS new file mode 100644 index 00000000000..ae9d7fbf6f8 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/OWNERS @@ -0,0 +1,2 @@ +stevenjb@chromium.org +dpapad@chromium.org diff --git a/chromium/ui/webui/resources/cr_components/README.md b/chromium/ui/webui/resources/cr_components/README.md new file mode 100644 index 00000000000..a4ea0138a7d --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/README.md @@ -0,0 +1,12 @@ +This directory contains complex Polymer web components for Web UI. They may be +shared between Settings, login, stand alone dialogs, etc. + +These components are allowed to use I18nBehavior. The Web UI hosting these +components is expected to provide loadTimeData with any necessary strings. +TODO(stevenjb/dschuyler): Add support for i18n{} substitution. + +These components may also use chrome and extension APIs, e.g. chrome.send +(through a browser proxy) or chrome.settingsPrivate. The C++ code hosting the +component is expected to handle these calls. + +For simpler components with no I18n or chrome dependencies, see cr_elements. diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.html b/chromium/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.html new file mode 100644 index 00000000000..82be73ab885 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.html @@ -0,0 +1,57 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> +<link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html"> +<link rel="import" href="chrome://resources/cr_elements/paper_checkbox_style_css.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-checkbox/paper-checkbox.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner.html"> +<link rel="import" href="certificate_shared_css.html"> +<link rel="import" href="certificates_browser_proxy.html"> + +<dom-module id="ca-trust-edit-dialog"> + <template> + <style include="certificate-shared paper-button-style paper-checkbox-style"> + paper-checkbox { + display: block; + } + + paper-checkbox, + #description { + margin: 15px 0; + } + </style> + + <dialog is="cr-dialog" id="dialog" close-text="[[i18n('close')]]"> + <div slot="title"> + [[i18n('certificateManagerCaTrustEditDialogTitle')]] + </div> + <div slot="body"> + <div>[[explanationText_]]</div> + <div id="description"> + [[i18n('certificateManagerCaTrustEditDialogDescription')]] + </div> + <paper-checkbox id="ssl" checked="[[trustInfo_.ssl]]"> + [[i18n('certificateManagerCaTrustEditDialogSsl')]] + </paper-checkbox> + <paper-checkbox id="email" checked="[[trustInfo_.email]]"> + [[i18n('certificateManagerCaTrustEditDialogEmail')]] + </paper-checkbox> + <paper-checkbox id="objSign" checked="[[trustInfo_.objSign]]"> + [[i18n('certificateManagerCaTrustEditDialogObjSign')]] + </paper-checkbox> + </div> + <div slot="button-container"> + <paper-spinner id="spinner"></paper-spinner> + <paper-button class="cancel-button" on-tap="onCancelTap_"> + [[i18n('cancel')]] + </paper-button> + <paper-button id="ok" class="action-button" on-tap="onOkTap_"> + [[i18n('ok')]] + </paper-button> + </div> + </dialog> + </template> + <script src="ca_trust_edit_dialog.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.js b/chromium/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.js new file mode 100644 index 00000000000..c33d5558efa --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/ca_trust_edit_dialog.js @@ -0,0 +1,80 @@ +// Copyright 2016 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. + +/** + * @fileoverview 'ca-trust-edit-dialog' allows the user to: + * - specify the trust level of a certificate authority that is being + * imported. + * - edit the trust level of an already existing certificate authority. + */ +Polymer({ + is: 'ca-trust-edit-dialog', + + behaviors: [I18nBehavior], + + properties: { + /** @type {!CertificateSubnode|!NewCertificateSubNode} */ + model: Object, + + /** @private {?CaTrustInfo} */ + trustInfo_: Object, + + /** @private {string} */ + explanationText_: String, + }, + + /** @private {?certificate_manager.CertificatesBrowserProxy} */ + browserProxy_: null, + + /** @override */ + ready: function() { + this.browserProxy_ = + certificate_manager.CertificatesBrowserProxyImpl.getInstance(); + }, + + /** @override */ + attached: function() { + this.explanationText_ = loadTimeData.getStringF( + 'certificateManagerCaTrustEditDialogExplanation', this.model.name); + + // A non existing |model.id| indicates that a new certificate is being + // imported, otherwise an existing certificate is being edited. + if (this.model.id) { + this.browserProxy_.getCaCertificateTrust(this.model.id) + .then(trustInfo => { + this.trustInfo_ = trustInfo; + this.$.dialog.showModal(); + }); + } else { + /** @type {!CrDialogElement} */ (this.$.dialog).showModal(); + } + }, + + /** @private */ + onCancelTap_: function() { + /** @type {!CrDialogElement} */ (this.$.dialog).close(); + }, + + /** @private */ + onOkTap_: function() { + this.$.spinner.active = true; + + var whenDone = this.model.id ? + this.browserProxy_.editCaCertificateTrust( + this.model.id, this.$.ssl.checked, this.$.email.checked, + this.$.objSign.checked) : + this.browserProxy_.importCaCertificateTrustSelected( + this.$.ssl.checked, this.$.email.checked, this.$.objSign.checked); + + whenDone.then( + () => { + this.$.spinner.active = false; + /** @type {!CrDialogElement} */ (this.$.dialog).close(); + }, + error => { + /** @type {!CrDialogElement} */ (this.$.dialog).close(); + this.fire('certificates-error', {error: error, anchor: null}); + }); + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_delete_confirmation_dialog.html b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_delete_confirmation_dialog.html new file mode 100644 index 00000000000..fbf253c3a5d --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_delete_confirmation_dialog.html @@ -0,0 +1,30 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="certificate_shared_css.html"> +<link rel="import" href="certificates_browser_proxy.html"> + +<dom-module id="certificate-delete-confirmation-dialog"> + <template> + <style include="certificate-shared"></style> + <dialog is="cr-dialog" id="dialog" close-text="[[i18n('close')]]"> + <div slot="title"> + [[getTitleText_(model, certificateType)]] + </div> + <div slot="body"> + <div>[[getDescriptionText_(model, certificateType)]]</div> + </div> + <div slot="button-container"> + <paper-button class="cancel-button" on-tap="onCancelTap_"> + [[i18n('cancel')]] + </paper-button> + <paper-button id="ok" class="action-button" on-tap="onOkTap_"> + [[i18n('ok')]] + </paper-button> + </div> + </dialog> + </template> + <script src="certificate_delete_confirmation_dialog.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_delete_confirmation_dialog.js b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_delete_confirmation_dialog.js new file mode 100644 index 00000000000..f914b0865ad --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_delete_confirmation_dialog.js @@ -0,0 +1,97 @@ +// Copyright 2016 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. + +/** + * @fileoverview A confirmation dialog allowing the user to delete various types + * of certificates. + */ +Polymer({ + is: 'certificate-delete-confirmation-dialog', + + behaviors: [I18nBehavior], + + properties: { + /** @type {!CertificateSubnode} */ + model: Object, + + /** @type {!CertificateType} */ + certificateType: String, + }, + + /** @private {?certificate_manager.CertificatesBrowserProxy} */ + browserProxy_: null, + + /** @override */ + ready: function() { + this.browserProxy_ = + certificate_manager.CertificatesBrowserProxyImpl.getInstance(); + }, + + /** @override */ + attached: function() { + /** @type {!CrDialogElement} */ (this.$.dialog).showModal(); + }, + + /** + * @private + * @return {string} + */ + getTitleText_: function() { + /** + * @param {string} localizedMessageId + * @return {string} + */ + var getString = localizedMessageId => + loadTimeData.getStringF(localizedMessageId, this.model.name); + + switch (this.certificateType) { + case CertificateType.PERSONAL: + return getString('certificateManagerDeleteUserTitle'); + case CertificateType.SERVER: + return getString('certificateManagerDeleteServerTitle'); + case CertificateType.CA: + return getString('certificateManagerDeleteCaTitle'); + case CertificateType.OTHER: + return getString('certificateManagerDeleteOtherTitle'); + } + assertNotReached(); + }, + + /** + * @private + * @return {string} + */ + getDescriptionText_: function() { + var getString = loadTimeData.getString.bind(loadTimeData); + switch (this.certificateType) { + case CertificateType.PERSONAL: + return getString('certificateManagerDeleteUserDescription'); + case CertificateType.SERVER: + return getString('certificateManagerDeleteServerDescription'); + case CertificateType.CA: + return getString('certificateManagerDeleteCaDescription'); + case CertificateType.OTHER: + return ''; + } + assertNotReached(); + }, + + /** @private */ + onCancelTap_: function() { + /** @type {!CrDialogElement} */ (this.$.dialog).close(); + }, + + /** @private */ + onOkTap_: function() { + this.browserProxy_.deleteCertificate(this.model.id) + .then( + () => { + /** @type {!CrDialogElement} */ (this.$.dialog).close(); + }, + error => { + /** @type {!CrDialogElement} */ (this.$.dialog).close(); + this.fire('certificates-error', {error: error, anchor: null}); + }); + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_entry.html b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_entry.html new file mode 100644 index 00000000000..8dcb44091d0 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_entry.html @@ -0,0 +1,39 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> +<link rel="import" href="certificate_shared_css.html"> +<link rel="import" href="certificate_subentry.html"> +<link rel="import" href="certificates_browser_proxy.html"> + +<dom-module id="certificate-entry"> + <template> + <style include="certificate-shared iron-flex"> + .expand-box { + align-items: center; + border-top: var(--cr-separator-line); + display: flex; + min-height: 48px; + padding: 0 20px; + } + </style> + <div class="expand-box"> + <div class="flex">[[model.id]]</div> + <cr-expand-button expanded="{{expanded_}}" + alt="[[i18n('certificateManagerExpandA11yLabel')]]"> + </cr-expand-button> + </div> + <template is="dom-if" if="[[expanded_]]"> + <div class="list-frame"> + <template is="dom-repeat" items="[[model.subnodes]]"> + <certificate-subentry model="[[item]]" + certificate-type="[[certificateType]]" + is-last$="[[isLast_(index, model)]]"> + </certificate-subentry> + </template> + </div> + </template> + </template> + <script src="certificate_entry.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_entry.js b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_entry.js new file mode 100644 index 00000000000..bcd900e48c1 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_entry.js @@ -0,0 +1,29 @@ +// Copyright 2016 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. + +/** + * @fileoverview An element that represents an SSL certificate entry. + */ +Polymer({ + is: 'certificate-entry', + + behaviors: [I18nBehavior], + + properties: { + /** @type {!Certificate} */ + model: Object, + + /** @type {!CertificateType} */ + certificateType: String, + }, + + /** + * @param {number} index + * @return {boolean} Whether the given index corresponds to the last sub-node. + * @private + */ + isLast_: function(index) { + return index == this.model.subnodes.length - 1; + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_list.html b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_list.html new file mode 100644 index 00000000000..55396b5f371 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_list.html @@ -0,0 +1,41 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/html/assert.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> +<link rel="import" href="certificate_entry.html"> +<link rel="import" href="certificate_manager_types.html"> +<link rel="import" href="certificate_shared_css.html"> +<link rel="import" href="certificates_browser_proxy.html"> + +<dom-module id="certificate-list"> + <template> + <style include="certificate-shared iron-flex"> + .button-box { + align-items: center; + display: flex; + margin-bottom: 24px; + min-height: 48px; + padding: 0 20px; + } + </style> + <div class="button-box"> + <span class="flex"> + [[getDescription_(certificateType, certificates)]]</span> + <paper-button id="import" on-tap="onImportTap_" + hidden="[[!canImport_(certificateType)]]"> + [[i18n('certificateManagerImport')]]</paper-button> +<if expr="chromeos"> + <paper-button id="importAndBind" on-tap="onImportAndBindTap_" + hidden="[[!canImportAndBind_(certificateType, isGuest_)]]"> + [[i18n('certificateManagerImportAndBind')]]</paper-button> +</if> + </div> + <template is="dom-repeat" items="[[certificates]]"> + <certificate-entry model="[[item]]" + certificate-type="[[certificateType]]"> + </certificate-entry> + </template> + </template> + <script src="certificate_list.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_list.js b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_list.js new file mode 100644 index 00000000000..d24361fdeee --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_list.js @@ -0,0 +1,158 @@ +// Copyright 2016 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. + +/** + * @fileoverview 'certificate-list' is an element that displays a list of + * certificates. + */ +Polymer({ + is: 'certificate-list', + + properties: { + /** @type {!Array<!Certificate>} */ + certificates: { + type: Array, + value: function() { + return []; + }, + }, + + /** @type {!CertificateType} */ + certificateType: String, + + // 'if expr="chromeos"' here is breaking vulcanize. TODO(stevenjb/dpapad): + // Restore after migrating to polymer-bundler, crbug.com/731881. + /** @private */ + isGuest_: { + type: Boolean, + value: function() { + return loadTimeData.valueExists('isGuest') && + loadTimeData.getBoolean('isGuest'); + }, + }, + }, + + behaviors: [I18nBehavior], + + /** + * @return {string} + * @private + */ + getDescription_: function() { + if (this.certificates.length == 0) + return this.i18n('certificateManagerNoCertificates'); + + switch (this.certificateType) { + case CertificateType.PERSONAL: + return this.i18n('certificateManagerYourCertificatesDescription'); + case CertificateType.SERVER: + return this.i18n('certificateManagerServersDescription'); + case CertificateType.CA: + return this.i18n('certificateManagerAuthoritiesDescription'); + case CertificateType.OTHER: + return this.i18n('certificateManagerOthersDescription'); + } + + assertNotReached(); + }, + + /** + * @return {boolean} + * @private + */ + canImport_: function() { + return this.certificateType != CertificateType.OTHER; + }, + + // <if expr="chromeos"> + /** + * @return {boolean} + * @private + */ + canImportAndBind_: function() { + return !this.isGuest_ && this.certificateType == CertificateType.PERSONAL; + }, + // </if> + + /** + * Handles a rejected Promise returned from |browserProxy_|. + * @param {!HTMLElement} anchor + * @param {*} error Expects {!CertificatesError|!CertificatesImportError}. + * @private + */ + onRejected_: function(anchor, error) { + if (error === null) { + // Nothing to do here. Null indicates that the user clicked "cancel" on + // a native file chooser dialog. + return; + } + + // Otherwise propagate the error to the parents, such that a dialog + // displaying the error will be shown. + this.fire('certificates-error', {error: error, anchor: anchor}); + }, + + + /** + * @param {?NewCertificateSubNode} subnode + * @param {!HTMLElement} anchor + * @private + */ + dispatchImportActionEvent_: function(subnode, anchor) { + this.fire( + CertificateActionEvent, + /** @type {!CertificateActionEventDetail} */ ({ + action: CertificateAction.IMPORT, + subnode: subnode, + certificateType: this.certificateType, + anchor: anchor, + })); + }, + + /** + * @param {!Event} e + * @private + */ + onImportTap_: function(e) { + this.handleImport_( + false, /** @type {!HTMLElement} */ (Polymer.dom(e).localTarget)); + }, + + // <if expr="chromeos"> + /** + * @private + * @param {!Event} e + */ + onImportAndBindTap_: function(e) { + this.handleImport_( + true, /** @type {!HTMLElement} */ (Polymer.dom(e).localTarget)); + }, + // </if> + + /** + * @param {boolean} useHardwareBacked + * @param {!HTMLElement} anchor + * @private + */ + handleImport_: function(useHardwareBacked, anchor) { + var browserProxy = + certificate_manager.CertificatesBrowserProxyImpl.getInstance(); + if (this.certificateType == CertificateType.PERSONAL) { + browserProxy.importPersonalCertificate(useHardwareBacked) + .then(showPasswordPrompt => { + if (showPasswordPrompt) + this.dispatchImportActionEvent_(null, anchor); + }, this.onRejected_.bind(this, anchor)); + } else if (this.certificateType == CertificateType.CA) { + browserProxy.importCaCertificate().then(certificateName => { + this.dispatchImportActionEvent_({name: certificateName}, anchor); + }, this.onRejected_.bind(this, anchor)); + } else if (this.certificateType == CertificateType.SERVER) { + browserProxy.importServerCertificate().catch( + this.onRejected_.bind(this, anchor)); + } else { + assertNotReached(); + } + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_manager.html b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_manager.html new file mode 100644 index 00000000000..9c9ebcb86c3 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_manager.html @@ -0,0 +1,106 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/html/assert.html"> +<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-pages/iron-pages.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-tabs/paper-tabs.html"> +<link rel="import" href="ca_trust_edit_dialog.html"> +<link rel="import" href="certificate_delete_confirmation_dialog.html"> +<link rel="import" href="certificate_list.html"> +<link rel="import" href="certificate_manager_types.html"> +<link rel="import" href="certificate_password_decryption_dialog.html"> +<link rel="import" href="certificate_password_encryption_dialog.html"> +<link rel="import" href="certificates_browser_proxy.html"> +<link rel="import" href="certificates_error_dialog.html"> + +<dom-module id="certificate-manager"> + <template> + <style> + :host { + --paper-tabs-selection-bar-color: var(--paper-blue-500); + } + + paper-tabs { + font-size: inherit; + height: 40px; + margin-bottom: 24px; + } + + paper-tab { + --paper-tab-content: { + color: var(--paper-grey-800); + }; + --paper-tab-content-unselected: { + color: var(--paper-grey-600); + }; + text-transform: uppercase; + } + </style> + + <template is="dom-if" if="[[showCaTrustEditDialog_]]" restamp> + <ca-trust-edit-dialog model="[[dialogModel_]]"> + </ca-trust-edit-dialog> + </template> + <template is="dom-if" if="[[showDeleteConfirmationDialog_]]" restamp> + <certificate-delete-confirmation-dialog + model="[[dialogModel_]]" + certificate-type="[[dialogModelCertificateType_]]"> + </certificate-delete-confirmation-dialog> + </template> + <template is="dom-if" if="[[showPasswordEncryptionDialog_]]" restamp> + <certificate-password-encryption-dialog + model="[[dialogModel_]]"> + </certificate-password-encryption-dialog> + </template> + <template is="dom-if" if="[[showPasswordDecryptionDialog_]]" restamp> + <certificate-password-decryption-dialog> + </certificate-password-decryption-dialog> + </template> + <template is="dom-if" if="[[showErrorDialog_]]" restamp> + <certificates-error-dialog model="[[errorDialogModel_]]"> + </certificates-error-dialog> + </template> + + <paper-tabs noink selected="{{selected}}"> + <paper-tab>[[i18n('certificateManagerYourCertificates')]]</paper-tab> + <paper-tab>[[i18n('certificateManagerServers')]]</paper-tab> + <paper-tab>[[i18n('certificateManagerAuthorities')]]</paper-tab> + <paper-tab>[[i18n('certificateManagerOthers')]]</paper-tab> + </paper-tabs> + <iron-pages selected="[[selected]]"> + <div> + <certificate-list id="personalCerts" + certificates="[[personalCerts]]" + certificate-type="[[certificateTypeEnum_.PERSONAL]]"> + </certificate-list> + </div> + <div> + <template is="dom-if" if="[[isTabSelected_(selected, 1)]]"> + <certificate-list id="serverCerts" + certificates="[[serverCerts]]" + certificate-type="[[certificateTypeEnum_.SERVER]]"> + </certificate-list> + </template> + </div> + <div> + <template is="dom-if" if="[[isTabSelected_(selected, 2)]]"> + <certificate-list id="caCerts" + certificates="[[caCerts]]" + certificate-type="[[certificateTypeEnum_.CA]]"> + </certificate-list> + </template> + </div> + <div> + <template is="dom-if" if="[[isTabSelected_(selected, 3)]]"> + <certificate-list id="otherCerts" + certificates="[[otherCerts]]" + certificate-type="[[certificateTypeEnum_.OTHER]]"> + </certificate-list> + </template> + </div> + </iron-pages> + </template> + <script src="certificate_manager.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_manager.js b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_manager.js new file mode 100644 index 00000000000..0b1049d56df --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_manager.js @@ -0,0 +1,189 @@ +// Copyright 2015 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. + +/** + * @fileoverview The 'certificate-manager' component manages SSL certificates. + */ +Polymer({ + is: 'certificate-manager', + + behaviors: [I18nBehavior, WebUIListenerBehavior], + + properties: { + /** @type {number} */ + selected: { + type: Number, + value: 0, + }, + + /** @type {!Array<!Certificate>} */ + personalCerts: { + type: Array, + value: function() { + return []; + }, + }, + + /** @type {!Array<!Certificate>} */ + serverCerts: { + type: Array, + value: function() { + return []; + }, + }, + + /** @type {!Array<!Certificate>} */ + caCerts: { + type: Array, + value: function() { + return []; + }, + }, + + /** @type {!Array<!Certificate>} */ + otherCerts: { + type: Array, + value: function() { + return []; + }, + }, + + /** @private */ + certificateTypeEnum_: { + type: Object, + value: CertificateType, + readOnly: true, + }, + + /** @private */ + showCaTrustEditDialog_: Boolean, + + /** @private */ + showDeleteConfirmationDialog_: Boolean, + + /** @private */ + showPasswordEncryptionDialog_: Boolean, + + /** @private */ + showPasswordDecryptionDialog_: Boolean, + + /** @private */ + showErrorDialog_: Boolean, + + /** + * The model to be passed to dialogs that refer to a given certificate. + * @private {?CertificateSubnode} + */ + dialogModel_: Object, + + /** + * The certificate type to be passed to dialogs that refer to a given + * certificate. + * @private {?CertificateType} + */ + dialogModelCertificateType_: String, + + /** + * The model to be passed to the error dialog. + * @private {null|!CertificatesError|!CertificatesImportError} + */ + errorDialogModel_: Object, + + /** + * The element to return focus to, when the currently shown dialog is + * closed. + * @private {?HTMLElement} + */ + activeDialogAnchor_: Object, + }, + + /** @override */ + attached: function() { + this.addWebUIListener('certificates-changed', this.set.bind(this)); + certificate_manager.CertificatesBrowserProxyImpl.getInstance() + .refreshCertificates(); + }, + + /** + * @param {number} selectedIndex + * @param {number} tabIndex + * @return {boolean} Whether to show tab at |tabIndex|. + * @private + */ + isTabSelected_: function(selectedIndex, tabIndex) { + return selectedIndex == tabIndex; + }, + + /** @override */ + ready: function() { + this.addEventListener(CertificateActionEvent, event => { + this.dialogModel_ = event.detail.subnode; + this.dialogModelCertificateType_ = event.detail.certificateType; + + if (event.detail.action == CertificateAction.IMPORT) { + if (event.detail.certificateType == CertificateType.PERSONAL) { + this.openDialog_( + 'certificate-password-decryption-dialog', + 'showPasswordDecryptionDialog_', event.detail.anchor); + } else if (event.detail.certificateType == CertificateType.CA) { + this.openDialog_( + 'ca-trust-edit-dialog', 'showCaTrustEditDialog_', + event.detail.anchor); + } + } else { + if (event.detail.action == CertificateAction.EDIT) { + this.openDialog_( + 'ca-trust-edit-dialog', 'showCaTrustEditDialog_', + event.detail.anchor); + } else if (event.detail.action == CertificateAction.DELETE) { + this.openDialog_( + 'certificate-delete-confirmation-dialog', + 'showDeleteConfirmationDialog_', event.detail.anchor); + } else if (event.detail.action == CertificateAction.EXPORT_PERSONAL) { + this.openDialog_( + 'certificate-password-encryption-dialog', + 'showPasswordEncryptionDialog_', event.detail.anchor); + } + } + + event.stopPropagation(); + }); + + this.addEventListener('certificates-error', event => { + var detail = /** @type {!CertificatesErrorEventDetail} */ (event.detail); + this.errorDialogModel_ = detail.error; + this.openDialog_( + 'certificates-error-dialog', 'showErrorDialog_', detail.anchor); + event.stopPropagation(); + }); + }, + + /** + * Opens a dialog and registers a listener for removing the dialog from the + * DOM once is closed. The listener is destroyed when the dialog is removed + * (because of 'restamp'). + * + * @param {string} dialogTagName The tag name of the dialog to be shown. + * @param {string} domIfBooleanName The name of the boolean variable + * corresponding to the dialog. + * @param {?HTMLElement} anchor The element to focus when the dialog is + * closed. If null, the previous anchor element should be reused. This + * happens when a 'certificates-error-dialog' is opened, which when closed + * should focus the anchor of the previous dialog (the one that generated + * the error). + * @private + */ + openDialog_: function(dialogTagName, domIfBooleanName, anchor) { + if (anchor) + this.activeDialogAnchor_ = anchor; + this.set(domIfBooleanName, true); + this.async(() => { + var dialog = this.$$(dialogTagName); + dialog.addEventListener('close', () => { + this.set(domIfBooleanName, false); + cr.ui.focusWithoutInk(assert(this.activeDialogAnchor_)); + }); + }); + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_manager_types.html b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_manager_types.html new file mode 100644 index 00000000000..271b2666939 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_manager_types.html @@ -0,0 +1 @@ +<script src="certificate_manager_types.js"></script> diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_manager_types.js b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_manager_types.js new file mode 100644 index 00000000000..76d448770cf --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_manager_types.js @@ -0,0 +1,44 @@ +// Copyright 2016 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. + +/** + * @fileoverview Closure compiler typedefs. + */ + +/** + * The payload of the 'certificate-action' event. + * @typedef {{ + * action: !CertificateAction, + * subnode: (null|CertificateSubnode|NewCertificateSubNode), + * certificateType: !CertificateType, + * anchor: !HTMLElement + * }} + */ +var CertificateActionEventDetail; + +/** + * The payload of the 'certificates-error' event. + * @typedef {{ + * error: (null|CertificatesError|CertificatesImportError), + * anchor: ?HTMLElement + * }} + */ +var CertificatesErrorEventDetail; + +/** + * Enumeration of actions that require a popup menu to be shown to the user. + * @enum {number} + */ +var CertificateAction = { + DELETE: 0, + EDIT: 1, + EXPORT_PERSONAL: 2, + IMPORT: 3, +}; + +/** + * The name of the event fired when a certificate action is selected from the + * dropdown menu. CertificateActionEventDetail is passed as the event detail. + */ +var CertificateActionEvent = 'certificate-action'; diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_password_decryption_dialog.html b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_password_decryption_dialog.html new file mode 100644 index 00000000000..850da130e7f --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_password_decryption_dialog.html @@ -0,0 +1,34 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input.html"> +<link rel="import" href="certificate_shared_css.html"> +<link rel="import" href="certificates_browser_proxy.html"> + +<dom-module id="certificate-password-decryption-dialog"> + <template> + <style include="certificate-shared"></style> + <dialog is="cr-dialog" id="dialog" close-text="[[i18n('close')]]"> + <div slot="title"> + [[i18n('certificateManagerDecryptPasswordTitle')]] + </div> + <div slot="body"> + <paper-input type="password" id="password" + label="[[i18n('certificateManagerPassword')]]" + value="{{password_}}"> + </paper-input> + </div> + <div slot="button-container"> + <paper-button class="cancel-button" on-tap="onCancelTap_"> + [[i18n('cancel')]] + </paper-button> + <paper-button id="ok" class="action-button" on-tap="onOkTap_"> + [[i18n('ok')]] + </paper-button> + </div> + </dialog> + </template> + <script src="certificate_password_decryption_dialog.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_password_decryption_dialog.js b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_password_decryption_dialog.js new file mode 100644 index 00000000000..0e502404c1b --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_password_decryption_dialog.js @@ -0,0 +1,53 @@ +// Copyright 2016 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. + +/** + * @fileoverview A dialog prompting the user for a decryption password such that + * a previously exported personal certificate can be imported. + */ +Polymer({ + is: 'certificate-password-decryption-dialog', + + behaviors: [I18nBehavior], + + properties: { + /** @private */ + password_: { + type: String, + value: '', + }, + }, + + /** @private {?certificate_manager.CertificatesBrowserProxy} */ + browserProxy_: null, + + /** @override */ + ready: function() { + this.browserProxy_ = + certificate_manager.CertificatesBrowserProxyImpl.getInstance(); + }, + + /** @override */ + attached: function() { + /** @type {!CrDialogElement} */ (this.$.dialog).showModal(); + }, + + /** @private */ + onCancelTap_: function() { + /** @type {!CrDialogElement} */ (this.$.dialog).close(); + }, + + /** @private */ + onOkTap_: function() { + this.browserProxy_.importPersonalCertificatePasswordSelected(this.password_) + .then( + () => { + /** @type {!CrDialogElement} */ (this.$.dialog).close(); + }, + error => { + /** @type {!CrDialogElement} */ (this.$.dialog).close(); + this.fire('certificates-error', {error: error, anchor: null}); + }); + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.html b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.html new file mode 100644 index 00000000000..f17b2825fef --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.html @@ -0,0 +1,44 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input.html"> +<link rel="import" href="certificate_shared_css.html"> +<link rel="import" href="certificates_browser_proxy.html"> + +<dom-module id="certificate-password-encryption-dialog"> + <template> + <style include="certificate-shared"> + .password-buttons { + margin-bottom: 20px; + } + </style> + <dialog is="cr-dialog" id="dialog" close-text="[[i18n('close')]]"> + <div slot="title"> + [[i18n('certificateManagerEncryptPasswordTitle')]] + </div> + <div slot="body"> + <div>[[i18n('certificateManagerEncryptPasswordDescription')]]</div> + <div class="password-buttons"> + <paper-input type="password" value="{{password_}}" id="password" + label="[[i18n('certificateManagerPassword')]]" + on-input="validate_"></paper-input> + <paper-input type="password" + value="{{confirmPassword_}}" id="confirmPassword" + label="[[i18n('certificateManagerConfirmPassword')]]" + on-input="validate_"></paper-input> + </div> + </div> + <div slot="button-container"> + <paper-button class="cancel-button" on-tap="onCancelTap_"> + [[i18n('cancel')]] + </paper-button> + <paper-button id="ok" class="action-button" on-tap="onOkTap_" disabled> + [[i18n('ok')]] + </paper-button> + </div> + </dialog> + </template> + <script src="certificate_password_encryption_dialog.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.js b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.js new file mode 100644 index 00000000000..b605ee7c22a --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.js @@ -0,0 +1,69 @@ +// Copyright 2016 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. + +/** + * @fileoverview A dialog prompting the user to encrypt a personal certificate + * before it is exported to disk. + */ +Polymer({ + is: 'certificate-password-encryption-dialog', + + behaviors: [I18nBehavior], + + properties: { + /** @type {!CertificateSubnode} */ + model: Object, + + /** @private */ + password_: { + type: String, + value: '', + }, + + /** @private */ + confirmPassword_: { + type: String, + value: '', + }, + }, + + /** @private {?certificate_manager.CertificatesBrowserProxy} */ + browserProxy_: null, + + /** @override */ + ready: function() { + this.browserProxy_ = + certificate_manager.CertificatesBrowserProxyImpl.getInstance(); + }, + + /** @override */ + attached: function() { + /** @type {!CrDialogElement} */ (this.$.dialog).showModal(); + }, + + /** @private */ + onCancelTap_: function() { + /** @type {!CrDialogElement} */ (this.$.dialog).close(); + }, + + /** @private */ + onOkTap_: function() { + this.browserProxy_.exportPersonalCertificatePasswordSelected(this.password_) + .then( + () => { + this.$.dialog.close(); + }, + error => { + this.$.dialog.close(); + this.fire('certificates-error', {error: error, anchor: null}); + }); + }, + + /** @private */ + validate_: function() { + var isValid = + this.password_ != '' && this.password_ == this.confirmPassword_; + this.$.ok.disabled = !isValid; + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_shared_css.html b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_shared_css.html new file mode 100644 index 00000000000..88fbdb2e211 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_shared_css.html @@ -0,0 +1,35 @@ +<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> + +<!-- Common styles for certificate elements. --> + +<dom-module id="certificate-shared"> + <template> + <style include="cr-shared-style"> + /* .list-frame and .list-item match the styling in settings_shared_css. */ + .list-frame { + -webkit-padding-end: 20px; + -webkit-padding-start: 60px; + align-items: center; + display: block; + } + + .list-item { + align-items: center; + display: flex; + min-height: 48px; + } + + .list-item.underbar { + border-bottom: var(--cr-separator-line); + } + + .list-item.selected { + font-weight: 500; + } + + .list-item > .start { + flex: 1; + } + </style> + </template> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.html b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.html new file mode 100644 index 00000000000..691625fb095 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.html @@ -0,0 +1,63 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html"> +<link rel="import" href="chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.html"> +<link rel="import" href="chrome://resources/cr_elements/icons.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html"> +<link rel="import" href="certificate_manager_types.html"> +<link rel="import" href="certificate_shared_css.html"> +<link rel="import" href="certificates_browser_proxy.html"> + +<dom-module id="certificate-subentry"> + <template> + <style include="certificate-shared cr-icons"> + .name { + flex: auto; + } + + .untrusted { + -webkit-margin-end: 16px; + color: var(--paper-red-700); + font-weight: 500; + text-transform: uppercase; + } + + :host([is-last]) .list-item { + border-bottom: none; + } + </style> + <div class="list-item underbar"> + <div class="untrusted" hidden$="[[!model.untrusted]]"> + [[i18n('certificateManagerUntrusted')]] + </div> + <div class="name">[[model.name]]</div> + <button is="paper-icon-button-light" class="icon-more-vert" id="dots" + title="[[i18n('moreActions')]]" on-tap="onDotsTap_"></button> + <template is="cr-lazy-render" id="menu"> + <dialog is="cr-action-menu"> + <button class="dropdown-item" id="view" + on-tap="onViewTap_"> + [[i18n('certificateManagerView')]] + </button> + <button class="dropdown-item" id="edit" + hidden$="[[!canEdit_(certificateType, model)]]" + on-tap="onEditTap_"> + [[i18n('edit')]] + </button> + <button class="dropdown-item" id="export" + hidden$="[[!canExport_(certificateType, model)]]" + on-tap="onExportTap_"> + [[i18n('certificateManagerExport')]] + </button> + <button class="dropdown-item" id="delete" + hidden$="[[!canDelete_(model)]]" + on-tap="onDeleteTap_"> + [[i18n('certificateManagerDelete')]] + </button> + </dialog> + </template> + <div> + </template> + <script src="certificate_subentry.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.js b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.js new file mode 100644 index 00000000000..f2bc22e51fc --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.js @@ -0,0 +1,150 @@ +// Copyright 2016 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. + +/** + * @fileoverview certificate-subentry represents an SSL certificate sub-entry. + */ + +Polymer({ + is: 'certificate-subentry', + + behaviors: [I18nBehavior], + + properties: { + /** @type {!CertificateSubnode} */ + model: Object, + + /** @type {!CertificateType} */ + certificateType: String, + }, + + /** @private {certificate_manager.CertificatesBrowserProxy} */ + browserProxy_: null, + + /** @override */ + created: function() { + this.browserProxy_ = + certificate_manager.CertificatesBrowserProxyImpl.getInstance(); + }, + + /** + * Dispatches an event indicating which certificate action was tapped. It is + * used by the parent of this element to display a modal dialog accordingly. + * @param {!CertificateAction} action + * @private + */ + dispatchCertificateActionEvent_: function(action) { + this.fire( + CertificateActionEvent, + /** @type {!CertificateActionEventDetail} */ ({ + action: action, + subnode: this.model, + certificateType: this.certificateType, + anchor: this.$.dots, + })); + }, + + /** + * Handles the case where a call to the browser resulted in a rejected + * promise. + * @param {*} error Expects {?CertificatesError}. + * @private + */ + onRejected_: function(error) { + if (error === null) { + // Nothing to do here. Null indicates that the user clicked "cancel" on + // the native file chooser dialog. + return; + } + + // Otherwise propagate the error to the parents, such that a dialog + // displaying the error will be shown. + this.fire('certificates-error', {error: error, anchor: this.$.dots}); + }, + + /** + * @param {!Event} event + * @private + */ + onViewTap_: function(event) { + this.closePopupMenu_(); + this.browserProxy_.viewCertificate(this.model.id); + }, + + /** + * @param {!Event} event + * @private + */ + onEditTap_: function(event) { + this.closePopupMenu_(); + this.dispatchCertificateActionEvent_(CertificateAction.EDIT); + }, + + /** + * @param {!Event} event + * @private + */ + onDeleteTap_: function(event) { + this.closePopupMenu_(); + this.dispatchCertificateActionEvent_(CertificateAction.DELETE); + }, + + /** + * @param {!Event} event + * @private + */ + onExportTap_: function(event) { + this.closePopupMenu_(); + if (this.certificateType == CertificateType.PERSONAL) { + this.browserProxy_.exportPersonalCertificate(this.model.id).then(() => { + this.dispatchCertificateActionEvent_(CertificateAction.EXPORT_PERSONAL); + }, this.onRejected_.bind(this)); + } else { + this.browserProxy_.exportCertificate(this.model.id); + } + }, + + /** + * @param {!CertificateType} certificateType + * @param {!CertificateSubnode} model + * @return {boolean} Whether the certificate can be edited. + * @private + */ + canEdit_: function(certificateType, model) { + return certificateType == CertificateType.CA && !model.policy; + }, + + /** + * @param {!CertificateType} certificateType + * @param {!CertificateSubnode} model + * @return {boolean} Whether the certificate can be exported. + * @private + */ + canExport_: function(certificateType, model) { + if (certificateType == CertificateType.PERSONAL) { + return model.extractable; + } + return true; + }, + + /** + * @param {!CertificateSubnode} model + * @return {boolean} Whether the certificate can be deleted. + * @private + */ + canDelete_: function(model) { + return !model.readonly && !model.policy; + }, + + /** @private */ + closePopupMenu_: function() { + this.$$('dialog[is=cr-action-menu]').close(); + }, + + /** @private */ + onDotsTap_: function() { + var actionMenu = /** @type {!CrActionMenuElement} */ (this.$.menu.get()); + actionMenu.showAt(this.$.dots); + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificates_browser_proxy.html b/chromium/ui/webui/resources/cr_components/certificate_manager/certificates_browser_proxy.html new file mode 100644 index 00000000000..0dc7c572ff1 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificates_browser_proxy.html @@ -0,0 +1 @@ +<script src="certificates_browser_proxy.js"></script> diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificates_browser_proxy.js b/chromium/ui/webui/resources/cr_components/certificate_manager/certificates_browser_proxy.js new file mode 100644 index 00000000000..bbf1d73f17a --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificates_browser_proxy.js @@ -0,0 +1,271 @@ +// Copyright 2016 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. + +/** + * @fileoverview A helper object used from the "Manage certificates" section + * to interact with the browser. + */ + +/** + * @typedef {{ + * extractable: boolean, + * id: string, + * name: string, + * policy: boolean, + * readonly: boolean, + * untrusted: boolean, + * }} + * @see chrome/browser/ui/webui/settings/certificates_handler.cc + */ +var CertificateSubnode; + +/** + * A data structure describing a certificate that is currently being imported, + * therefore it has no ID yet, but it has a name. Used within JS only. + * @typedef {{ + * name: string, + * }} + */ +var NewCertificateSubNode; + +/** + * @typedef {{ + * id: string, + * name: string, + * subnodes: !Array<!CertificateSubnode> + * }} + * @see chrome/browser/ui/webui/settings/certificates_handler.cc + */ +var Certificate; + +/** + * @typedef {{ + * ssl: boolean, + * email: boolean, + * objSign: boolean + * }} + */ +var CaTrustInfo; + +/** + * Generic error returned from C++ via a Promise reject callback. + * @typedef {{ + * title: string, + * description: string + * }} + * @see chrome/browser/ui/webui/settings/certificates_handler.cc + */ +var CertificatesError; + +/** + * Enumeration of all possible certificate types. + * @enum {string} + */ +var CertificateType = { + CA: 'ca', + OTHER: 'other', + PERSONAL: 'personal', + SERVER: 'server', +}; + + +/** + * Error returned from C++ via a Promise reject callback, when some certificates + * fail to be imported. + * @typedef {{ + * title: string, + * description: string, + * certificateErrors: !Array<{name: string, error: string}> + * }} + * @see chrome/browser/ui/webui/settings/certificates_handler.cc + */ +var CertificatesImportError; + +cr.define('certificate_manager', function() { + /** @interface */ + class CertificatesBrowserProxy { + /** + * Triggers 5 events in the following order + * 1x 'certificates-model-ready' event. + * 4x 'certificates-changed' event, one for each certificate category. + */ + refreshCertificates() {} + + /** @param {string} id */ + viewCertificate(id) {} + + /** @param {string} id */ + exportCertificate(id) {} + + /** + * @param {string} id + * @return {!Promise} A promise resolved when the certificate has been + * deleted successfully or rejected with a CertificatesError. + */ + deleteCertificate(id) {} + + /** + * @param {string} id + * @return {!Promise<!CaTrustInfo>} + */ + getCaCertificateTrust(id) {} + + /** + * @param {string} id + * @param {boolean} ssl + * @param {boolean} email + * @param {boolean} objSign + * @return {!Promise} + */ + editCaCertificateTrust(id, ssl, email, objSign) {} + + cancelImportExportCertificate() {} + + /** + * @param {string} id + * @return {!Promise} A promise firing once the user has selected + * the export location. A prompt should be shown to asking for a + * password to use for encrypting the file. The password should be + * passed back via a call to + * exportPersonalCertificatePasswordSelected(). + */ + exportPersonalCertificate(id) {} + + /** + * @param {string} password + * @return {!Promise} + */ + exportPersonalCertificatePasswordSelected(password) {} + + /** + * @param {boolean} useHardwareBacked + * @return {!Promise<boolean>} A promise firing once the user has selected + * the file to be imported. If true a password prompt should be shown to + * the user, and the password should be passed back via a call to + * importPersonalCertificatePasswordSelected(). + */ + importPersonalCertificate(useHardwareBacked) {} + + /** + * @param {string} password + * @return {!Promise} + */ + importPersonalCertificatePasswordSelected(password) {} + + /** + * @return {!Promise} A promise firing once the user has selected + * the file to be imported, or failing with CertificatesError. + * Upon success, a prompt should be shown to the user to specify the + * trust levels, and that information should be passed back via a call + * to importCaCertificateTrustSelected(). + */ + importCaCertificate() {} + + /** + * @param {boolean} ssl + * @param {boolean} email + * @param {boolean} objSign + * @return {!Promise} A promise firing once the trust level for the imported + * certificate has been successfully set. The promise is rejected if an + * error occurred with either a CertificatesError or + * CertificatesImportError. + */ + importCaCertificateTrustSelected(ssl, email, objSign) {} + + /** + * @return {!Promise} A promise firing once the certificate has been + * imported. The promise is rejected if an error occurred, with either + * a CertificatesError or CertificatesImportError. + */ + importServerCertificate() {} + } + + /** + * @implements {certificate_manager.CertificatesBrowserProxy} + */ + class CertificatesBrowserProxyImpl { + /** @override */ + refreshCertificates() { + chrome.send('refreshCertificates'); + } + + /** @override */ + viewCertificate(id) { + chrome.send('viewCertificate', [id]); + } + + /** @override */ + exportCertificate(id) { + chrome.send('exportCertificate', [id]); + } + + /** @override */ + deleteCertificate(id) { + return cr.sendWithPromise('deleteCertificate', id); + } + + /** @override */ + exportPersonalCertificate(id) { + return cr.sendWithPromise('exportPersonalCertificate', id); + } + + /** @override */ + exportPersonalCertificatePasswordSelected(password) { + return cr.sendWithPromise( + 'exportPersonalCertificatePasswordSelected', password); + } + + /** @override */ + importPersonalCertificate(useHardwareBacked) { + return cr.sendWithPromise('importPersonalCertificate', useHardwareBacked); + } + + /** @override */ + importPersonalCertificatePasswordSelected(password) { + return cr.sendWithPromise( + 'importPersonalCertificatePasswordSelected', password); + } + + /** @override */ + getCaCertificateTrust(id) { + return cr.sendWithPromise('getCaCertificateTrust', id); + } + + /** @override */ + editCaCertificateTrust(id, ssl, email, objSign) { + return cr.sendWithPromise( + 'editCaCertificateTrust', id, ssl, email, objSign); + } + + /** @override */ + importCaCertificateTrustSelected(ssl, email, objSign) { + return cr.sendWithPromise( + 'importCaCertificateTrustSelected', ssl, email, objSign); + } + + /** @override */ + cancelImportExportCertificate() { + chrome.send('cancelImportExportCertificate'); + } + + /** @override */ + importCaCertificate() { + return cr.sendWithPromise('importCaCertificate'); + } + + /** @override */ + importServerCertificate() { + return cr.sendWithPromise('importServerCertificate'); + } + } + + // The singleton instance_ is replaced with a test version of this wrapper + // during testing. + cr.addSingletonGetter(CertificatesBrowserProxyImpl); + + return { + CertificatesBrowserProxy: CertificatesBrowserProxy, + CertificatesBrowserProxyImpl: CertificatesBrowserProxyImpl, + }; +}); diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificates_error_dialog.html b/chromium/ui/webui/resources/cr_components/certificate_manager/certificates_error_dialog.html new file mode 100644 index 00000000000..2c0bb746765 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificates_error_dialog.html @@ -0,0 +1,29 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="certificate_shared_css.html"> + +<dom-module id="certificates-error-dialog"> + <template> + <style include="certificate-shared"></style> + <dialog is="cr-dialog" id="dialog" close-text="[[i18n('close')]]"> + <div slot="title">[[model.title]]</div> + <div slot="body"> + <div>[[model.description]]</div> + <template is="dom-if" if="[[model.certificateErrors]]"> + <template is="dom-repeat" items="[[model.certificateErrors]]"> + <div>[[getCertificateErrorText_(item)]]</div> + </template> + </template> + </div> + <div slot="button-container"> + <paper-button id="ok" class="action-button" on-tap="onOkTap_"> + [[i18n('ok')]] + </paper-button> + </div> + </dialog> + </template> + <script src="certificates_error_dialog.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/certificates_error_dialog.js b/chromium/ui/webui/resources/cr_components/certificate_manager/certificates_error_dialog.js new file mode 100644 index 00000000000..2b3cd779653 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/certificates_error_dialog.js @@ -0,0 +1,38 @@ +// Copyright 2016 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. + +/** + * @fileoverview A dialog for showing SSL certificate related error messages. + * The user can only close the dialog, there is no other possible interaction. + */ +Polymer({ + is: 'certificates-error-dialog', + + behaviors: [I18nBehavior], + + properties: { + /** @type {!CertificatesError|!CertificatesImportError} */ + model: Object, + }, + + /** @override */ + attached: function() { + /** @type {!CrDialogElement} */ (this.$.dialog).showModal(); + }, + + /** @private */ + onOkTap_: function() { + /** @type {!CrDialogElement} */ (this.$.dialog).close(); + }, + + /** + * @param {{name: string, error: string}} importError + * @return {string} + * @private + */ + getCertificateErrorText_: function(importError) { + return loadTimeData.getStringF( + 'certificateImportErrorFormat', importError.name, importError.error); + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/certificate_manager/compiled_resources2.gyp b/chromium/ui/webui/resources/cr_components/certificate_manager/compiled_resources2.gyp new file mode 100644 index 00000000000..fef217bee75 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/certificate_manager/compiled_resources2.gyp @@ -0,0 +1,125 @@ +# Copyright 2017 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. +{ + 'targets': [ + { + 'target_name': 'ca_trust_edit_dialog', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/cr_dialog/compiled_resources2.gyp:cr_dialog', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data', + 'certificate_manager_types', + 'certificates_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'certificate_delete_confirmation_dialog', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/cr_dialog/compiled_resources2.gyp:cr_dialog', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data', + 'certificate_manager_types', + 'certificates_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'certificate_entry', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + 'certificate_manager_types', + 'certificates_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'certificate_list', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + 'certificate_manager_types', + 'certificate_subentry', + 'certificates_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'certificate_manager', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:web_ui_listener_behavior', + '<(DEPTH)/ui/webui/resources/js/cr/ui/compiled_resources2.gyp:focus_without_ink', + 'certificate_list', + 'certificate_manager_types', + 'certificates_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'certificate_manager_types', + 'dependencies': [ + 'certificates_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'certificate_password_decryption_dialog', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/cr_dialog/compiled_resources2.gyp:cr_dialog', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + 'certificate_manager_types', + 'certificates_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'certificate_password_encryption_dialog', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/cr_dialog/compiled_resources2.gyp:cr_dialog', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + 'certificate_manager_types', + 'certificates_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'certificate_subentry', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/cr_action_menu/compiled_resources2.gyp:cr_action_menu', + '<(DEPTH)/ui/webui/resources/cr_elements/cr_lazy_render/compiled_resources2.gyp:cr_lazy_render', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + 'certificate_manager_types', + 'certificates_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'certificates_browser_proxy', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'certificates_error_dialog', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/cr_dialog/compiled_resources2.gyp:cr_dialog', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data', + 'certificate_manager_types', + 'certificates_browser_proxy', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + ], +} diff --git a/chromium/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.html b/chromium/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.html new file mode 100644 index 00000000000..a89ba4d5fde --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.html @@ -0,0 +1,137 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> +<link rel="import" href="chrome://resources/html/cr.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input.html"> + +<dom-module id="bluetooth-dialog"> + <template> + <style include="cr-hidden-style iron-flex"> + #pairing { + margin-bottom: 10px; + } + + #pairing paper-input { + text-align: center; + } + + #pinDiv { + margin-top: 10px; + } + + .dialog-message { + margin-bottom: 10px; + } + + div.contents { + height: 250px; + } + + /* .display indicates a displayed pin code or passkey. */ + span.display { + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 0 0 1px #222; + color: #222; + font-size: 123.08%; /* 16px / 13px */ + height: 38px; + line-height: 38px; + margin: 0 5px; + padding: 0 15px; + text-align: center; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + } + + span.display.next { + background: rgb(77, 144, 254); + border: 2px solid rgb(77, 144, 254); + box-shadow: none; + color: #fff; + } + + span.display.untyped { + border: 1px solid #d4d4d4; + box-shadow: 0 0 0 1px #888; + color: #666; + } + + /* .confirm indicates a confirmation passkey. */ + span.confirm { + color: #999; + font-size: 153.85%; /* 20px / 13px */ + font-weight: 600; /* semibold */ + margin: 0 20px; + } + </style> + <!-- TODO(stevenjb/dschuyler): Find a solution to support i18n{} here --> + <dialog is="cr-dialog" id="dialog" on-cancel="onDialogCanceled_" + close-text="[[i18n('close')]]" on-closed="onDialogCanceled_"> + <div slot="title">[[title]]</div> + <div slot="body"> + <div class="contents layout vertical center center-justified"> + <template is="dom-if" if="[[!errorMessage_]]"> + <div id="pairing" class="layout vertical center center-justified"> + <div class="dialog-message"> + [[getMessage_(pairingDevice, pairingEvent_)]] + </div> + <div hidden$="[[!showEnterPincode_(pairingEvent_)]]"> + <paper-input id="pincode" minlength="1" maxlength="16" + type="text" auto-validate value="{{pinOrPass_}}"> + </paper-input> + </div> + <div hidden$="[[!showEnterPasskey_(pairingEvent_)]]"> + <paper-input id="passkey" minlength="6" maxlength="6" + type="text" auto-validate value="{{pinOrPass_}}"> + </paper-input> + </div> + <div id="pinDiv" class="layout horizontal center center-justified" + hidden="[[!showDisplayPassOrPin_(pairingEvent_)]]"> + <template is="dom-repeat" items="[[digits_]]"> + <span class$="[[getPinClass_(index, pairingEvent_)]]"> + [[getPinDigit_(index, pairingEvent_)]] + </span> + </template> + <span class$="[[getPinClass_(-1, pairingEvent_)]]" + hidden="[[showAcceptReject_(pairingEvent_)]]"> + [[i18n('bluetoothEnterKey')]] + </span> + </div> + </div> + </template> + <template is="dom-if" if="[[errorMessage_]]"> + <div class="layout vertical center center-justified"> + <div class="dialog-message">[[errorMessage_]]</div> + </div> + </template> + </div> + </div> + <div slot="button-container"> + <template is="dom-if" if="[[!errorMessage_]]"> + <paper-button hidden$="[[!showAcceptReject_(pairingEvent_)]]" + on-tap="onAcceptTap_">[[i18n('bluetoothAccept')]]</paper-button> + <paper-button hidden$="[[!showAcceptReject_(pairingEvent_)]]" + on-tap="onRejectTap_">[[i18n('bluetoothReject')]]</paper-button> + <paper-button hidden$="[[!showConnect_(pairingEvent_)]]" + disabled="[[!enableConnect_(pairingEvent_, pinOrPass_)]]" + on-tap="onConnectTap_">[[i18n('bluetoothPair')]]</paper-button> + <paper-button + hidden$="[[!showDismiss_(pairingDevice, pairingEvent_)]]" + on-tap="close">[[i18n('ok')]]</paper-button> + <paper-button hidden$="[[showDismiss_(pairingDevice, pairingEvent_)]]" + on-tap="onCancelTap_"> + [[i18n('cancel')]] + </paper-button> + </template> + <template is="dom-if" if="[[errorMessage_]]"> + <paper-button on-tap="close">[[i18n('ok')]]</paper-button> + </template> + </div> + </dialog> + </template> + <script src="bluetooth_dialog.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js b/chromium/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js new file mode 100644 index 00000000000..b061349e266 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js @@ -0,0 +1,459 @@ +// Copyright 2016 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. + +/** + * @fileoverview + * Dialog used for pairing a provided |pairing-device|. Set |show-error| to + * show the error results from a pairing event instead of the pairing UI. + * NOTE: This module depends on I18nBehavior which depends on loadTimeData. + */ + +var PairingEventType = chrome.bluetoothPrivate.PairingEventType; + +Polymer({ + is: 'bluetooth-dialog', + + behaviors: [I18nBehavior], + + properties: { + /** + * Interface for bluetooth calls. Set in bluetooth-page. + * @type {Bluetooth} + * @private + */ + bluetooth: { + type: Object, + value: chrome.bluetooth, + }, + + /** + * Interface for bluetoothPrivate calls. + * @type {BluetoothPrivate} + */ + bluetoothPrivate: { + type: Object, + value: chrome.bluetoothPrivate, + }, + + /** Dialog title */ + title: String, + + /** + * Current Pairing device. + * @type {!chrome.bluetooth.Device|undefined} + */ + pairingDevice: Object, + + /** + * Current Pairing event. + * @private {?chrome.bluetoothPrivate.PairingEvent} + */ + pairingEvent_: { + type: Object, + value: null, + }, + + /** + * May be set by the host to show a pairing error result, or may be + * set by the dialog if a pairing or connect error occured. + * @private + */ + errorMessage_: String, + + /** + * Pincode or passkey value, used to trigger connect enabled changes. + * @private + */ + pinOrPass_: String, + + /** + * @const {!Array<number>} + * @private + */ + digits_: { + type: Array, + readOnly: true, + value: [0, 1, 2, 3, 4, 5], + }, + }, + + observers: [ + 'dialogUpdated_(errorMessage_, pairingEvent_)', + 'pairingChanged_(pairingDevice, pairingEvent_)', + ], + + /** + * Listener for chrome.bluetoothPrivate.onPairing events. + * @private {?function(!chrome.bluetoothPrivate.PairingEvent)} + */ + bluetoothPrivateOnPairingListener_: null, + + /** + * Listener for chrome.bluetooth.onBluetoothDeviceChanged events. + * @private {?function(!chrome.bluetooth.Device)} + */ + bluetoothDeviceChangedListener_: null, + + open: function() { + this.startPairing(); + this.pinOrPass_ = ''; + this.getDialog_().showModal(); + this.itemWasFocused_ = false; + }, + + close: function() { + this.endPairing(); + var dialog = this.getDialog_(); + if (dialog.open) + dialog.close(); + }, + + /** + * Updates the dialog after a connect attempt. + * @param {!chrome.bluetooth.Device} device The device connected to + * @param {!{message: string}} lastError chrome.runtime.lastError + * @param {chrome.bluetoothPrivate.ConnectResultType} result The connect + * result + * @return {boolean} + */ + handleError: function(device, lastError, result) { + var error; + if (lastError) { + error = lastError.message; + } else { + switch (result) { + case chrome.bluetoothPrivate.ConnectResultType.IN_PROGRESS: + case chrome.bluetoothPrivate.ConnectResultType.ALREADY_CONNECTED: + case chrome.bluetoothPrivate.ConnectResultType.AUTH_CANCELED: + case chrome.bluetoothPrivate.ConnectResultType.SUCCESS: + this.errorMessage_ = ''; + return false; + default: + error = result; + } + } + + var name = device.name || device.address; + var id = 'bluetooth_connect_' + error; + if (this.i18nExists(id)) { + this.errorMessage_ = this.i18n(id, name); + } else { + this.errorMessage_ = error; + console.error('Unexpected error connecting to: ' + name + ': ' + error); + } + return true; + }, + + /** @private */ + dialogUpdated_: function() { + if (this.showEnterPincode_()) + this.$$('#pincode').focus(); + else if (this.showEnterPasskey_()) + this.$$('#passkey').focus(); + }, + + /** + * @return {!CrDialogElement} + * @private + */ + getDialog_: function() { + return /** @type {!CrDialogElement} */ (this.$.dialog); + }, + + /** @private */ + onCancelTap_: function() { + this.getDialog_().cancel(); + }, + + /** @private */ + onDialogCanceled_: function() { + if (!this.errorMessage_) + this.sendResponse_(chrome.bluetoothPrivate.PairingResponse.CANCEL); + this.endPairing(); + }, + + /** Called when the dialog is opened. Starts listening for pairing events. */ + startPairing: function() { + if (!this.bluetoothPrivateOnPairingListener_) { + this.bluetoothPrivateOnPairingListener_ = + this.onBluetoothPrivateOnPairing_.bind(this); + this.bluetoothPrivate.onPairing.addListener( + this.bluetoothPrivateOnPairingListener_); + } + if (!this.bluetoothDeviceChangedListener_) { + this.bluetoothDeviceChangedListener_ = + this.onBluetoothDeviceChanged_.bind(this); + this.bluetooth.onDeviceChanged.addListener( + this.bluetoothDeviceChangedListener_); + } + }, + + /** Called when the dialog is closed. */ + endPairing: function() { + if (this.bluetoothPrivateOnPairingListener_) { + this.bluetoothPrivate.onPairing.removeListener( + this.bluetoothPrivateOnPairingListener_); + this.bluetoothPrivateOnPairingListener_ = null; + } + if (this.bluetoothDeviceChangedListener_) { + this.bluetooth.onDeviceChanged.removeListener( + this.bluetoothDeviceChangedListener_); + this.bluetoothDeviceChangedListener_ = null; + } + this.pairingEvent_ = null; + }, + + /** + * Process bluetoothPrivate.onPairing events. + * @param {!chrome.bluetoothPrivate.PairingEvent} event + * @private + */ + onBluetoothPrivateOnPairing_: function(event) { + if (!this.pairingDevice || + event.device.address != this.pairingDevice.address) { + return; + } + if (event.pairing == PairingEventType.KEYS_ENTERED && + event.passkey === undefined && this.pairingEvent_) { + // 'keysEntered' event might not include the updated passkey so preserve + // the current one. + event.passkey = this.pairingEvent_.passkey; + } + this.pairingEvent_ = event; + }, + + /** + * Process bluetooth.onDeviceChanged events. This ensures that the dialog + * updates when the connection state changes. + * @param {!chrome.bluetooth.Device} device + * @private + */ + onBluetoothDeviceChanged_: function(device) { + if (!this.pairingDevice || device.address != this.pairingDevice.address) + return; + this.pairingDevice = device; + }, + + /** @private */ + pairingChanged_: function() { + // Auto-close the dialog when pairing completes. + if (this.pairingDevice.paired && !this.pairingDevice.connecting && + this.pairingDevice.connected) { + this.close(); + return; + } + this.errorMessage_ = ''; + this.pinOrPass_ = ''; + }, + + /** + * @return {string} + * @private + */ + getMessage_: function() { + var message; + if (!this.pairingEvent_) + message = 'bluetoothStartConnecting'; + else + message = this.getEventDesc_(this.pairingEvent_.pairing); + return this.i18n(message, this.pairingDevice.name); + }, + + /** + * @return {boolean} + * @private + */ + showEnterPincode_: function() { + return !!this.pairingEvent_ && + this.pairingEvent_.pairing == PairingEventType.REQUEST_PINCODE; + }, + + /** + * @return {boolean} + * @private + */ + showEnterPasskey_: function() { + return !!this.pairingEvent_ && + this.pairingEvent_.pairing == PairingEventType.REQUEST_PASSKEY; + }, + + /** + * @return {boolean} + * @private + */ + showDisplayPassOrPin_: function() { + if (!this.pairingEvent_) + return false; + var pairing = this.pairingEvent_.pairing; + return ( + pairing == PairingEventType.DISPLAY_PINCODE || + pairing == PairingEventType.DISPLAY_PASSKEY || + pairing == PairingEventType.CONFIRM_PASSKEY || + pairing == PairingEventType.KEYS_ENTERED); + }, + + /** + * @return {boolean} + * @private + */ + showAcceptReject_: function() { + return !!this.pairingEvent_ && + this.pairingEvent_.pairing == PairingEventType.CONFIRM_PASSKEY; + }, + + /** + * @return {boolean} + * @private + */ + showConnect_: function() { + if (!this.pairingEvent_) + return false; + var pairing = this.pairingEvent_.pairing; + return pairing == PairingEventType.REQUEST_PINCODE || + pairing == PairingEventType.REQUEST_PASSKEY; + }, + + /** + * @return {boolean} + * @private + */ + enableConnect_: function() { + if (!this.showConnect_()) + return false; + var inputId = + (this.pairingEvent_.pairing == PairingEventType.REQUEST_PINCODE) ? + '#pincode' : + '#passkey'; + var paperInput = /** @type {!PaperInputElement} */ (this.$$(inputId)); + assert(paperInput); + /** @type {string} */ var value = paperInput.value; + return !!value && paperInput.validate(); + }, + + /** + * @return {boolean} + * @private + */ + showDismiss_: function() { + return this.pairingDevice.paired || + (!!this.pairingEvent_ && + this.pairingEvent_.pairing == PairingEventType.COMPLETE); + }, + + /** @private */ + onAcceptTap_: function() { + this.sendResponse_(chrome.bluetoothPrivate.PairingResponse.CONFIRM); + }, + + /** @private */ + onConnectTap_: function() { + this.sendResponse_(chrome.bluetoothPrivate.PairingResponse.CONFIRM); + }, + + /** @private */ + onRejectTap_: function() { + this.sendResponse_(chrome.bluetoothPrivate.PairingResponse.REJECT); + }, + + /** + * @param {!chrome.bluetoothPrivate.PairingResponse} response + * @private + */ + sendResponse_: function(response) { + if (!this.pairingDevice) + return; + var options = + /** @type {!chrome.bluetoothPrivate.SetPairingResponseOptions} */ ( + {device: this.pairingDevice, response: response}); + if (response == chrome.bluetoothPrivate.PairingResponse.CONFIRM) { + var pairing = this.pairingEvent_.pairing; + if (pairing == PairingEventType.REQUEST_PINCODE) + options.pincode = this.$$('#pincode').value; + else if (pairing == PairingEventType.REQUEST_PASSKEY) + options.passkey = parseInt(this.$$('#passkey').value, 10); + } + this.bluetoothPrivate.setPairingResponse(options, () => { + if (chrome.runtime.lastError) { + // TODO(stevenjb): Show error. + console.error( + 'Error setting pairing response: ' + options.device.name + + ': Response: ' + options.response + + ': Error: ' + chrome.runtime.lastError.message); + } + this.close(); + }); + + this.fire('response', options); + }, + + /** + * @param {!PairingEventType} eventType + * @return {string} + * @private + */ + getEventDesc_: function(eventType) { + assert(eventType); + if (eventType == PairingEventType.COMPLETE || + eventType == PairingEventType.KEYS_ENTERED || + eventType == PairingEventType.REQUEST_AUTHORIZATION) { + return 'bluetoothStartConnecting'; + } + return 'bluetooth_' + /** @type {string} */ (eventType); + }, + + /** + * @param {number} index + * @return {string} + * @private + */ + getPinDigit_: function(index) { + if (!this.pairingEvent_) + return ''; + var digit = '0'; + var pairing = this.pairingEvent_.pairing; + if (pairing == PairingEventType.DISPLAY_PINCODE && + this.pairingEvent_.pincode && + index < this.pairingEvent_.pincode.length) { + digit = this.pairingEvent_.pincode[index]; + } else if ( + this.pairingEvent_.passkey && + (pairing == PairingEventType.DISPLAY_PASSKEY || + pairing == PairingEventType.KEYS_ENTERED || + pairing == PairingEventType.CONFIRM_PASSKEY)) { + var passkeyString = String(this.pairingEvent_.passkey); + if (index < passkeyString.length) + digit = passkeyString[index]; + } + return digit; + }, + + /** + * @param {number} index + * @return {string} + * @private + */ + getPinClass_: function(index) { + if (!this.pairingEvent_) + return ''; + if (this.pairingEvent_.pairing == PairingEventType.CONFIRM_PASSKEY) + return 'confirm'; + var cssClass = 'display'; + if (this.pairingEvent_.pairing == PairingEventType.DISPLAY_PASSKEY) { + if (index == 0) + cssClass += ' next'; + else + cssClass += ' untyped'; + } else if ( + this.pairingEvent_.pairing == PairingEventType.KEYS_ENTERED && + this.pairingEvent_.enteredKey) { + var enteredKey = this.pairingEvent_.enteredKey; // 1-7 + var lastKey = this.digits_.length; // 6 + if ((index == -1 && enteredKey > lastKey) || (index + 1 == enteredKey)) + cssClass += ' next'; + else if (index > enteredKey) + cssClass += ' untyped'; + } + return cssClass; + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/compiled_resources2.gyp b/chromium/ui/webui/resources/cr_components/chromeos/compiled_resources2.gyp new file mode 100644 index 00000000000..1e6afe0491a --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/compiled_resources2.gyp @@ -0,0 +1,30 @@ +# Copyright 2017 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. +{ + 'targets': [ + { + 'target_name': 'network_resources', + 'type': 'none', + 'dependencies': [ + 'network/compiled_resources2.gyp:*', + ], + }, + { + 'target_name': 'bluetooth_dialog', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/cr_dialog/compiled_resources2.gyp:cr_dialog', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + '<(DEPTH)/third_party/polymer/v1_0/components-chromium/iron-resizable-behavior/compiled_resources2.gyp:iron-resizable-behavior-extracted', + '<(DEPTH)/third_party/polymer/v1_0/components-chromium/paper-input/compiled_resources2.gyp:paper-input-extracted', + '<(EXTERNS_GYP):bluetooth', + '<(EXTERNS_GYP):bluetooth_private', + '<(INTERFACES_GYP):bluetooth_interface', + '<(INTERFACES_GYP):bluetooth_private_interface', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + ], +} diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/compiled_resources2.gyp b/chromium/ui/webui/resources/cr_components/chromeos/network/compiled_resources2.gyp new file mode 100644 index 00000000000..a18b88baaa0 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/compiled_resources2.gyp @@ -0,0 +1,63 @@ +# Copyright 2016 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. +{ + 'targets': [ + { + 'target_name': 'network_apnlist', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/chromeos/network/compiled_resources2.gyp:cr_onc_types', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + ], + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'network_ip_config', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/chromeos/network/compiled_resources2.gyp:cr_onc_types', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + ], + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'network_nameservers', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/chromeos/network/compiled_resources2.gyp:cr_onc_types', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + ], + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'network_property_list', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/chromeos/network/compiled_resources2.gyp:cr_onc_types', + '<(DEPTH)/ui/webui/resources/cr_elements/policy/compiled_resources2.gyp:cr_policy_network_behavior', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + ], + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'network_proxy', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/chromeos/network/compiled_resources2.gyp:cr_onc_types', + '<(DEPTH)/ui/webui/resources/cr_elements/policy/compiled_resources2.gyp:cr_policy_network_behavior', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + ], + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'network_proxy_input', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/chromeos/network/compiled_resources2.gyp:cr_onc_types', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + ], + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'network_proxy_exclusions', + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + ], +} diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_apnlist.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_apnlist.html new file mode 100644 index 00000000000..1a2c716e7ee --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_apnlist.html @@ -0,0 +1,43 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/html/md_select_css.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="network_property_list.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-apnlist"> + <template> + <style include="network-shared md-select"> + paper-button { + margin: 4px 0; + } + </style> + <div class="property-box"> + <div class="start">[[i18n('networkAccessPoint')]]</div> + <div class="md-select-wrapper"> + <select id="selectApn" class="md-select" on-change="onSelectApnChange_" + value="[[selectedApn_]]" + aria-label="[[i18n('networkAccessPoint')]]"> + <template is="dom-repeat" items="[[apnSelectList_]]"> + <option value="[[item.AccessPointName]]">[[apnDesc_(item)]]</option> + </template> + </select> + <span class="md-select-underline"></span> + </div> + </div> + + <div class="property-box single-column indented" + hidden$="[[!isOtherSelected_(selectedApn_, networkProperties)]]"> + <network-property-list on-property-change="onOtherApnChange_" + fields="[[otherApnFields_]]" property-dict="[[otherApn_]]" + edit-field-types="[[otherApnEditTypes_]]" prefix="Cellular.APN."> + </network-property-list> + <paper-button class="action-button" on-tap="onSaveOtherTap_"> + [[i18n('save')]] + </paper-button> + </div> + </template> + <script src="network_apnlist.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_apnlist.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_apnlist.js new file mode 100644 index 00000000000..c4e5034e13d --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_apnlist.js @@ -0,0 +1,271 @@ +// Copyright 2015 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. + +/** + * @fileoverview Polymer element for displaying and modifying a list of cellular + * access points. + */ +Polymer({ + is: 'network-apnlist', + + behaviors: [I18nBehavior], + + properties: { + /** + * The current set of properties for the network matching |guid|. + * @type {!CrOnc.NetworkProperties|undefined} + */ + networkProperties: { + type: Object, + observer: 'networkPropertiesChanged_', + }, + + /** + * The CrOnc.APNProperties.AccessPointName value of the selected APN. + * @private + */ + selectedApn_: { + type: String, + value: '', + }, + + /** + * Selectable list of APN dictionaries for the UI. Includes an entry + * corresponding to |otherApn| (see below). + * @private {!Array<!CrOnc.APNProperties>} + */ + apnSelectList_: { + type: Array, + value: function() { + return []; + } + }, + + /** + * The user settable properties for a new ('other') APN. The values for + * AccessPointName, Username, and Password will be set to the currently + * active APN if it does not match an existing list entry. + * @private {CrOnc.APNProperties|undefined} + */ + otherApn_: { + type: Object, + }, + + /** + * Array of property names to pass to the Other APN property list. + * @private {!Array<string>} + */ + otherApnFields_: { + type: Array, + value: function() { + return ['AccessPointName', 'Username', 'Password']; + }, + readOnly: true + }, + + /** + * Array of edit types to pass to the Other APN property list. + * @private + */ + otherApnEditTypes_: { + type: Object, + value: function() { + return { + 'AccessPointName': 'String', + 'Username': 'String', + 'Password': 'Password' + }; + }, + readOnly: true + }, + }, + + /** @const */ + DefaultAccessPointName: 'none', + + /** + * Polymer networkProperties changed method. + */ + networkPropertiesChanged_: function() { + if (!this.networkProperties || !this.networkProperties.Cellular) + return; + + /** @type {!CrOnc.APNProperties|undefined} */ var activeApn; + var cellular = this.networkProperties.Cellular; + /** @type {!chrome.networkingPrivate.ManagedAPNProperties|undefined} */ var + apn = cellular.APN; + if (apn && apn.AccessPointName) { + activeApn = /** @type {!CrOnc.APNProperties|undefined} */ ( + CrOnc.getSimpleActiveProperties(apn)); + } else if (cellular.LastGoodAPN && cellular.LastGoodAPN.AccessPointName) { + activeApn = cellular.LastGoodAPN; + } + this.setApnSelectList_(activeApn); + }, + + /** + * Sets the list of selectable APNs for the UI. Appends an 'Other' entry + * (see comments for |otherApn_| above). + * @param {CrOnc.APNProperties|undefined} activeApn The currently active APN + * properties. + * @private + */ + setApnSelectList_: function(activeApn) { + // Copy the list of APNs from this.networkProperties. + var result = this.getApnList_().slice(); + + // Test whether |activeApn| is in the current APN list in networkProperties. + var activeApnInList = activeApn && result.some(function(a) { + return a.AccessPointName == activeApn.AccessPointName; + }); + + // If |activeApn| is specified and not in the list, use the active + // properties for 'other'. Otherwise use any existing 'other' properties. + var otherApnProperties = + (activeApn && !activeApnInList) ? activeApn : this.otherApn_; + var otherApn = this.createApnObject_(otherApnProperties); + + // Always use 'Other' for the name of custom APN entries (the name does + // not get saved). + otherApn.Name = 'Other'; + + // If no 'active' or 'other' AccessPointName was provided, use the default. + otherApn.AccessPointName = + otherApn.AccessPointName || this.DefaultAccessPointName; + + // Save the 'other' properties. + this.otherApn_ = otherApn; + + // Append 'other' to the end of the list of APNs. + result.push(otherApn); + + this.apnSelectList_ = result; + // Set selectedApn_ after dom-repeat has been stamped. + this.async(() => { + this.selectedApn_ = + (activeApn && activeApn.AccessPointName) || otherApn.AccessPointName; + }); + }, + + /** + * @param {!CrOnc.APNProperties|undefined=} apnProperties + * @return {!CrOnc.APNProperties} A new APN object with properties from + * |apnProperties| if provided. + * @private + */ + createApnObject_: function(apnProperties) { + var newApn = {AccessPointName: ''}; + if (apnProperties) + Object.assign(newApn, apnProperties); + return newApn; + }, + + /** + * @return {!Array<!CrOnc.APNProperties>} The list of APN properties in + * |networkProperties| or an empty list if the property is not set. + * @private + */ + getApnList_: function() { + if (!this.networkProperties || !this.networkProperties.Cellular) + return []; + /** @type {!chrome.networkingPrivate.ManagedAPNList|undefined} */ var + apnlist = this.networkProperties.Cellular.APNList; + if (!apnlist) + return []; + return /** @type {!Array<!CrOnc.APNProperties>} */ ( + CrOnc.getActiveValue(apnlist)); + }, + + /** + * Event triggered when the selectApn selection changes. + * @param {!Event} event + * @private + */ + onSelectApnChange_: function(event) { + var target = /** @type {!HTMLSelectElement} */ (event.target); + var accessPointName = target.value; + // When selecting 'Other', don't set a change event unless a valid + // non-default value has been set for Other. + if (this.isOtherSelected_(accessPointName) && + (!this.otherApn_ || !this.otherApn_.AccessPointName || + this.otherApn_.AccessPointName == this.DefaultAccessPointName)) { + this.selectedApn_ = accessPointName; + return; + } + this.sendApnChange_(accessPointName); + }, + + /** + * Event triggered when any 'Other' APN network property changes. + * @param {!{detail: {field: string, value: string}}} event + * @private + */ + onOtherApnChange_: function(event) { + this.set('otherApn_.' + event.detail.field, event.detail.value); + // Don't send a change event for 'Other' until the 'Save' button is tapped. + }, + + /** + * Event triggered when the Other APN 'Save' button is tapped. + * @param {!Event} event + * @private + */ + onSaveOtherTap_: function(event) { + this.sendApnChange_(this.selectedApn_); + }, + + /** + * Send the apn-change event. + * @param {string} accessPointName + * @private + */ + sendApnChange_: function(accessPointName) { + var apnList = this.getApnList_(); + var apn = this.findApnInList(apnList, accessPointName); + if (apn == undefined) { + apn = this.createApnObject_(); + if (this.otherApn_) { + apn.AccessPointName = this.otherApn_.AccessPointName; + apn.Username = this.otherApn_.Username; + apn.Password = this.otherApn_.Password; + } + } + this.fire('apn-change', {field: 'APN', value: apn}); + }, + + /** + * @param {string} accessPointName + * @return {boolean} True if the 'other' APN is currently selected. + * @private + */ + isOtherSelected_: function(accessPointName) { + if (!this.networkProperties || !this.networkProperties.Cellular) + return false; + var apnList = this.getApnList_(); + var apn = this.findApnInList(apnList, accessPointName); + return apn == undefined; + }, + + /** + * @param {!CrOnc.APNProperties} apn + * @return {string} The most descriptive name for the access point. + * @private + */ + apnDesc_: function(apn) { + return apn.LocalizedName || apn.Name || apn.AccessPointName; + }, + + /** + * @param {!Array<!CrOnc.APNProperties>} apnList + * @param {string} accessPointName + * @return {CrOnc.APNProperties|undefined} The entry in |apnList| matching + * |accessPointName| if it exists, or undefined. + * @private + */ + findApnInList: function(apnList, accessPointName) { + return apnList.find(function(a) { + return a.AccessPointName == accessPointName; + }); + } +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_ip_config.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_ip_config.html new file mode 100644 index 00000000000..8999e86a290 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_ip_config.html @@ -0,0 +1,30 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-toggle-button/paper-toggle-button.html"> +<link rel="import" href="network_property_list.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-ip-config"> + <template> + <style include="network-shared iron-flex"></style> + <div class="property-box"> + <div id="autoIPConfigLabel" class="start"> + [[i18n('networkIPConfigAuto')]] + </div> + <paper-toggle-button checked="{{automatic_}}" disabled="[[!editable]]" + aria-labelledby="autoIPConfigLabel"> + </paper-toggle-button> + </div> + <div class="property-box single-column indented stretch" + hidden$="[[!ipConfig_]]"> + <network-property-list + fields="[[ipConfigFields_]]" property-dict="[[ipConfig_]]" + edit-field-types="[[getIPEditFields_(editable, automatic_)]]" + on-property-change="onIPChange_"> + </network-property-list> + </div> + </template> + <script src="network_ip_config.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js new file mode 100644 index 00000000000..519367b120a --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js @@ -0,0 +1,214 @@ +// Copyright 2015 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. + +/** + * @fileoverview Polymer element for displaying the IP Config properties for + * a network state. TODO(stevenjb): Allow editing of static IP configurations + * when 'editable' is true. + */ +Polymer({ + is: 'network-ip-config', + + behaviors: [I18nBehavior], + + properties: { + /** + * The network properties dictionary containing the IP Config properties to + * display and modify. + * @type {!CrOnc.NetworkProperties|undefined} + */ + networkProperties: { + type: Object, + observer: 'networkPropertiesChanged_', + }, + + /** + * Whether or not the IP Address can be edited. + */ + editable: { + type: Boolean, + value: false, + }, + + /** + * State of 'Configure IP Addresses Automatically'. + * @private + */ + automatic_: { + type: Boolean, + value: true, + observer: 'automaticChanged_', + }, + + /** + * The currently visible IP Config property dictionary. The 'RoutingPrefix' + * property is a human-readable mask instead of a prefix length. + * @private {?{ + * ipv4: !CrOnc.IPConfigUIProperties, + * ipv6: (!CrOnc.IPConfigUIProperties|undefined) + * }} + */ + ipConfig_: { + type: Object, + value: null, + }, + + /** + * Array of properties to pass to the property list. + * @private {!Array<string>} + */ + ipConfigFields_: { + type: Array, + value: function() { + return [ + 'ipv4.IPAddress', + 'ipv4.RoutingPrefix', + 'ipv4.Gateway', + 'ipv6.IPAddress', + ]; + }, + readOnly: true + }, + }, + + /** + * Saved static IP configuration properties when switching to 'automatic'. + * @private {!CrOnc.IPConfigUIProperties|undefined} + */ + savedStaticIp_: undefined, + + /** + * Polymer networkProperties changed method. + */ + networkPropertiesChanged_: function(newValue, oldValue) { + if (!this.networkProperties) + return; + + var properties = this.networkProperties; + if (newValue.GUID != (oldValue && oldValue.GUID)) + this.savedStaticIp_ = undefined; + + // Update the 'automatic' property. + if (properties.IPAddressConfigType) { + var ipConfigType = CrOnc.getActiveValue(properties.IPAddressConfigType); + this.automatic_ = (ipConfigType != CrOnc.IPConfigType.STATIC); + } + + if (properties.IPConfigs || properties.StaticIPConfig) { + // Update the 'ipConfig' property. + var ipv4 = CrOnc.getIPConfigForType(properties, CrOnc.IPType.IPV4); + var ipv6 = CrOnc.getIPConfigForType(properties, CrOnc.IPType.IPV6); + this.ipConfig_ = { + ipv4: this.getIPConfigUIProperties_(ipv4), + ipv6: this.getIPConfigUIProperties_(ipv6) + }; + } else { + this.ipConfig_ = null; + } + }, + + /** @private */ + automaticChanged_: function() { + if (!this.automatic_) { + // Ensure that there is a valid IPConfig object. + this.ipConfig_ = this.ipConfig_ || { + ipv4: { + Gateway: '192.168.1.1', + IPAddress: '192.168.1.1', + RoutingPrefix: '255.255.255.0', + Type: CrOnc.IPType.IPV4, + }, + }; + this.sendStaticIpConfig_(); + return; + } + + // Save the static IP configuration when switching to automatic. + if (this.ipConfig_) + this.savedStaticIp_ = this.ipConfig_.ipv4; + // Send the change. + this.fire('ip-change', { + field: 'IPAddressConfigType', + value: CrOnc.IPConfigType.DHCP, + }); + }, + + /** + * @param {!CrOnc.IPConfigProperties|undefined} ipconfig + * @return {!CrOnc.IPConfigUIProperties} A new IPConfigUIProperties object + * with RoutingPrefix expressed as a string mask instead of a prefix + * length. Returns an empty object if |ipconfig| is undefined. + * @private + */ + getIPConfigUIProperties_: function(ipconfig) { + var result = {}; + if (!ipconfig) + return result; + for (var key in ipconfig) { + var value = ipconfig[key]; + if (key == 'RoutingPrefix') + result.RoutingPrefix = CrOnc.getRoutingPrefixAsNetmask(value); + else + result[key] = value; + } + return result; + }, + + /** + * @param {!CrOnc.IPConfigUIProperties} ipconfig The IP Config UI properties. + * @return {!CrOnc.IPConfigProperties} A new IPConfigProperties object with + * RoutingPrefix expressed as a a prefix length. + * @private + */ + getIPConfigProperties_: function(ipconfig) { + var result = {}; + for (var key in ipconfig) { + var value = ipconfig[key]; + if (key == 'RoutingPrefix') + result.RoutingPrefix = CrOnc.getRoutingPrefixAsLength(value); + else + result[key] = value; + } + return result; + }, + + /** + * @return {Object} An object with the edit type for each editable field. + * @private + */ + getIPEditFields_: function() { + if (!this.editable || this.automatic_) + return {}; + return { + 'ipv4.IPAddress': 'String', + 'ipv4.RoutingPrefix': 'String', + 'ipv4.Gateway': 'String' + }; + }, + + /** + * Event triggered when the network property list changes. + * @param {!{detail: {field: string, value: string}}} event The + * network-property-list change event. + * @private + */ + onIPChange_: function(event) { + if (!this.ipConfig_) + return; + var field = event.detail.field; + var value = event.detail.value; + // Note: |field| includes the 'ipv4.' prefix. + this.set('ipConfig_.' + field, value); + this.sendStaticIpConfig_(); + }, + + /** @private */ + sendStaticIpConfig_: function() { + // This will also set IPAddressConfigType to STATIC. + this.fire('ip-change', { + field: 'StaticIPConfig', + value: this.getIPConfigProperties_(this.ipConfig_.ipv4) + }); + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_nameservers.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_nameservers.html new file mode 100644 index 00000000000..a64f6ca853d --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_nameservers.html @@ -0,0 +1,43 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/html/md_select_css.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input-container.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-nameservers"> + <template> + <style include=" network-shared md-select"> + paper-input-container { + -webkit-padding-start: 4px; + } + </style> + + <div class="property-box"> + <div class="start">[[i18n('networkNameservers')]]</div> + <div class="md-select-wrapper"> + <select id="nameserverType" class="md-select" on-change="onTypeChange_" + value="[[nameserversType_]]" + aria-label="[[i18n('networkNameservers')]]"> + <template is="dom-repeat" items="[[nameserverTypeNames_]]"> + <option value="[[item]]">[[nameserverTypeDesc_(item)]]</option> + </template> + </select> + <span class="md-select-underline"></span> + </div> + </div> + + <div class="property-box single-column indented" + hidden$="[[!nameservers_.length]]"> + <template is="dom-repeat" items="[[nameservers_]]"> + <paper-input-container no-label-float> + <input id="nameserver[[index]]" is="iron-input" value="[[item]]" + disabled="[[!canEdit_(editable, nameserversType_)]]" + on-change="onValueChange_"> + </paper-input-container> + </template> + </div> + </template> + <script src="network_nameservers.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js new file mode 100644 index 00000000000..237614403be --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js @@ -0,0 +1,214 @@ +// Copyright 2015 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. + +/** + * @fileoverview Polymer element for displaying network nameserver options. + */ +Polymer({ + is: 'network-nameservers', + + behaviors: [I18nBehavior], + + properties: { + /** + * The network properties dictionary containing the nameserver properties to + * display and modify. + * @type {!CrOnc.NetworkProperties|undefined} + */ + networkProperties: { + type: Object, + observer: 'networkPropertiesChanged_', + }, + + /** Whether or not the nameservers can be edited. */ + editable: { + type: Boolean, + value: false, + }, + + /** + * Array of nameserver addresses stored as strings. + * @private {!Array<string>} + */ + nameservers_: { + type: Array, + value: function() { + return []; + }, + }, + + /** + * The selected nameserver type. + * @private + */ + nameserversType_: { + type: String, + value: 'automatic', + }, + + /** + * Array of nameserver types. + * @private + */ + nameserverTypeNames_: { + type: Array, + value: ['automatic', 'google', 'custom'], + readOnly: true, + }, + }, + + /** @const */ + GOOGLE_NAMESERVERS: [ + '8.8.4.4', + '8.8.8.8', + ], + + /** @const */ + MAX_NAMESERVERS: 4, + + /** + * Saved nameservers when switching to 'automatic'. + * @private {!Array<string>} + */ + savedNameservers_: [], + + /** @private */ + networkPropertiesChanged_: function(newValue, oldValue) { + if (!this.networkProperties) + return; + + if (!oldValue || newValue.GUID != oldValue.GUID) + this.savedNameservers_ = []; + + // Update the 'nameservers' property. + var nameservers = []; + var ipv4 = + CrOnc.getIPConfigForType(this.networkProperties, CrOnc.IPType.IPV4); + if (ipv4 && ipv4.NameServers) + nameservers = ipv4.NameServers; + + // Update the 'nameserversType' property. + var configType = + CrOnc.getActiveValue(this.networkProperties.NameServersConfigType); + var type; + if (configType == CrOnc.IPConfigType.STATIC) { + if (nameservers.join(',') == this.GOOGLE_NAMESERVERS.join(',')) { + type = 'google'; + } else { + type = 'custom'; + } + } else { + type = 'automatic'; + } + this.setNameservers_(type, nameservers); + }, + + /** + * @param {string} nameserversType + * @param {!Array<string>} nameservers + * @private + */ + setNameservers_: function(nameserversType, nameservers) { + if (nameserversType == 'custom') { + // Add empty entries for unset custom nameservers. + for (var i = nameservers.length; i < this.MAX_NAMESERVERS; ++i) + nameservers[i] = ''; + } + this.nameservers_ = nameservers; + // Set nameserversType_ after dom-repeat has been stamped. + this.async(() => { + this.nameserversType_ = nameserversType; + }); + }, + + /** + * @param {string} type The nameservers type. + * @return {string} The description for |type|. + * @private + */ + nameserverTypeDesc_: function(type) { + // TODO(stevenjb): Translate. + if (type == 'custom') + return 'Custom name servers'; + if (type == 'google') + return 'Google name servers'; + return 'Automatic name servers'; + }, + + /** + * @param {boolean} editable + * @param {string} nameserversType + * @return {boolean} True if the nameservers are editable. + * @private + */ + canEdit_: function(editable, nameserversType) { + return editable && nameserversType == 'custom'; + }, + + /** + * Event triggered when the selected type changes. Updates nameservers and + * sends the change value if necessary. + * @param {!Event} event + * @private + */ + onTypeChange_: function(event) { + if (this.nameserversType_ == 'custom') + this.savedNameservers_ = this.nameservers_; + var target = /** @type {!HTMLSelectElement} */ (event.target); + var type = target.value; + this.nameserversType_ = type; + if (type == 'custom') { + // Restore the saved nameservers. + this.setNameservers_(type, this.savedNameservers_); + // Only send custom nameservers if they are not empty. + if (this.savedNameservers_.length == 0) + return; + } + this.sendNameServers_(); + }, + + /** + * Event triggered when a nameserver value changes. + * @private + */ + onValueChange_: function() { + if (this.nameserversType_ != 'custom') { + // If a user inputs Google nameservers in the custom nameservers fields, + // |nameserversType| will change to 'google' so don't send the values. + return; + } + this.sendNameServers_(); + }, + + /** + * Sends the current nameservers type (for automatic) or value. + * @private + */ + sendNameServers_: function() { + var type = this.nameserversType_; + + if (type == 'custom') { + var nameservers = new Array(this.MAX_NAMESERVERS); + for (var i = 0; i < this.MAX_NAMESERVERS; ++i) { + var nameserverInput = this.$$('#nameserver' + i); + nameservers[i] = nameserverInput ? nameserverInput.value : ''; + } + this.fire('nameservers-change', { + field: 'NameServers', + value: nameservers, + }); + } else if (type == 'google') { + this.fire('nameservers-change', { + field: 'NameServers', + value: this.GOOGLE_NAMESERVERS, + }); + } else { + // automatic + this.fire('nameservers-change', { + field: 'NameServersConfigType', + value: CrOnc.IPConfigType.DHCP, + }); + } + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_property_list.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_property_list.html new file mode 100644 index 00000000000..0d73594c0af --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_property_list.html @@ -0,0 +1,71 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html"> +<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_behavior.html"> +<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_indicator.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input-container.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-property-list"> + <template> + <style include="network-shared iron-flex"> + paper-input-container { + margin-bottom: -12px; + margin-top: -8px; + } + + /* Property lists are embedded; remove the padding. */ + .property-box { + padding: 0; + } + + .secondary { + color: var(--paper-grey-600); + font-weight: 400; + } + + cr-policy-network-indicator { + -webkit-margin-start: var(--settings-controlled-by-spacing); + } + </style> + <template is="dom-repeat" items="[[fields]]" + filter="[[computeFilter_(prefix, propertyDict, editFieldTypes)]]"> + <div class="property-box single-column stretch"> + <!-- Property label --> + <div>[[getPropertyLabel_(item, prefix)]]</div> + <!-- Uneditable property value --> + <div class="layout horizontal" + hidden="[[isEditable_(item, '', propertyDict, editFieldTypes)]]"> + <div class="secondary"> + [[getPropertyValue_(item, prefix, propertyDict)]] + </div> + <cr-policy-network-indicator + property="[[getProperty_(item, propertyDict)]]"> + </cr-policy-network-indicator> + </div> + <!-- Editable String property value --> + <template is="dom-if" if="[[isEditable_( + item, 'String', propertyDict, editFieldTypes)]]"> + <paper-input-container no-label-float> + <input id="[[item]]" is="iron-input" + value="[[getPropertyValue_(item, prefix, propertyDict)]]" + on-change="onValueChange_"> + </paper-input-container> + </template> + <!-- Editable Password property value --> + <template is="dom-if" if="[[isEditable_( + item, 'Password', propertyDict, editFieldTypes)]]"> + <paper-input-container no-label-float> + <input id="[[item]]" is="iron-input" type="password" + value="[[getPropertyValue_(item, prefix, propertyDict)]]" + on-change="onValueChange_"> + </paper-input-container> + </template> + <!-- TODO(stevenjb): Support other types. --> + </div> + </template> + </template> + <script src="network_property_list.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_property_list.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_property_list.js new file mode 100644 index 00000000000..f333386cb78 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_property_list.js @@ -0,0 +1,214 @@ +// Copyright 2015 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. + +/** + * @fileoverview Polymer element for displaying a list of network properties + * in a list. This also supports editing fields inline for fields listed in + * editFieldTypes. + */ +Polymer({ + is: 'network-property-list', + + behaviors: [I18nBehavior, CrPolicyNetworkBehavior], + + properties: { + /** + * The dictionary containing the properties to display. + * @type {!Object|undefined} + */ + propertyDict: {type: Object}, + + /** + * Fields to display. + * @type {!Array<string>} + */ + fields: { + type: Array, + value: function() { + return []; + }, + }, + + /** + * Edit type of editable fields. May contain a property for any field in + * |fields|. Other properties will be ignored. Property values can be: + * 'String' - A text input will be displayed. + * 'Password' - A string with input type = password. + * TODO(stevenjb): Support types with custom validation, e.g. IPAddress. + * TODO(stevenjb): Support 'Number'. + * When a field changes, the 'property-change' event will be fired with + * the field name and the new value provided in the event detail. + */ + editFieldTypes: { + type: Object, + value: function() { + return {}; + }, + }, + + /** Prefix used to look up property key translations. */ + prefix: { + type: String, + value: '', + }, + }, + + /** + * Event triggered when an input field changes. Fires a 'property-change' + * event with the field (property) name set to the target id, and the value + * set to the target input value. + * @param {!Event} event The input change event. + * @private + */ + onValueChange_: function(event) { + if (!this.propertyDict) + return; + var field = event.target.id; + var curValue = this.get(field, this.propertyDict); + if (typeof curValue == 'object') { + // Extract the property from an ONC managed dictionary. + curValue = CrOnc.getActiveValue( + /** @type {!CrOnc.ManagedProperty} */ (curValue)); + } + var newValue = event.target.value; + if (newValue == curValue) + return; + this.fire('property-change', {field: field, value: newValue}); + }, + + /** + * @param {string} key The property key. + * @param {string} prefix + * @return {string} The text to display for the property label. + * @private + */ + getPropertyLabel_: function(key, prefix) { + var oncKey = 'Onc' + prefix + key; + oncKey = oncKey.replace(/\./g, '-'); + if (this.i18nExists(oncKey)) + return this.i18n(oncKey); + // We do not provide translations for every possible network property key. + // For keys specific to a type, strip the type prefix. + var result = prefix + key; + for (var entry in chrome.networkingPrivate.NetworkType) { + var type = chrome.networkingPrivate.NetworkType[entry]; + if (result.startsWith(type + '.')) { + result = result.substr(type.length + 1); + break; + } + } + return result; + }, + + /** + * Generates a filter function dependent on propertyDict and editFieldTypes. + * @param {string} prefix + * @param {!Object} propertyDict + * @param {!Object} editFieldTypes + * @private + */ + computeFilter_: function(prefix, propertyDict, editFieldTypes) { + return key => { + if (editFieldTypes.hasOwnProperty(key)) + return true; + var value = this.getPropertyValue_(key, prefix, propertyDict); + return value !== undefined && value !== ''; + }; + }, + + /** + * @param {string} key The property key. + * @param {string} type The field type. + * @param {!Object} propertyDict + * @param {!Object} editFieldTypes + * @return {boolean} + * @private + */ + isEditable_: function(key, type, propertyDict, editFieldTypes) { + var property = /** @type {!CrOnc.ManagedProperty|undefined} */ ( + this.get(key, propertyDict)); + if (this.isNetworkPolicyEnforced(property)) + return false; + var editType = editFieldTypes[key]; + return editType !== undefined && (type == '' || editType == type); + }, + + /** + * @param {string} key The property key. + * @param {!Object} propertyDict + * @return {*} The managed property dictionary associated with |key|. + * @private + */ + getProperty_: function(key, propertyDict) { + return this.get(key, propertyDict); + }, + + /** + * @param {string} key The property key. + * @param {string} prefix + * @param {!Object} propertyDict + * @return {string} The text to display for the property value. + * @private + */ + getPropertyValue_: function(key, prefix, propertyDict) { + var value = this.get(key, propertyDict); + if (value === undefined) + return ''; + if (typeof value == 'object') { + // Extract the property from an ONC managed dictionary + value = + CrOnc.getActiveValue(/** @type {!CrOnc.ManagedProperty} */ (value)); + } + var customValue = this.getCustomPropertyValue_(key, value); + if (customValue) + return customValue; + if (typeof value == 'number' || typeof value == 'boolean') + return value.toString(); + assert(typeof value == 'string'); + var valueStr = /** @type {string} */ (value); + var oncKey = 'Onc' + prefix + key; + oncKey = oncKey.replace(/\./g, '-'); + oncKey += '_' + valueStr; + if (this.i18nExists(oncKey)) + return this.i18n(oncKey); + return valueStr; + }, + + /** + * @param {string} key The property key. + * @param {*} value The property value. + * @return {string} The text to display for the property value. If the key + * does not correspond to a custom property, an empty string is returned. + */ + getCustomPropertyValue_: function(key, value) { + if (key == 'Tether.BatteryPercentage') { + assert(typeof value == 'number'); + return this.i18n('OncTether-BatteryPercentage_Value', value.toString()); + } + + if (key == 'Tether.SignalStrength') { + assert(typeof value == 'number'); + // Possible |signalStrength| values should be 0, 25, 50, 75, and 100. Add + // <= checks for robustness. + if (value <= 24) + return this.i18n('OncTether-SignalStrength_Weak'); + if (value <= 49) + return this.i18n('OncTether-SignalStrength_Okay'); + if (value <= 74) + return this.i18n('OncTether-SignalStrength_Good'); + if (value <= 99) + return this.i18n('OncTether-SignalStrength_Strong'); + return this.i18n('OncTether-SignalStrength_VeryStrong'); + } + + if (key == 'Tether.Carrier') { + assert(typeof value == 'string'); + return (!value || value == 'unknown-carrier') ? + this.i18n('tetherUnknownCarrier') : + value; + } + + return ''; + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.html new file mode 100644 index 00000000000..4b328f9145b --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.html @@ -0,0 +1,154 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html"> +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> +<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_behavior.html"> +<link rel="import" href="chrome://resources/html/assert.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/html/md_select_css.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys/iron-a11y-keys.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input-container.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-toggle-button/paper-toggle-button.html"> +<link rel="import" href="network_proxy_exclusions.html"> +<link rel="import" href="network_proxy_input.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-proxy"> + <template> + <style include="network-shared md-select cr-hidden-style iron-flex iron-flex-alignment"> + network-proxy-input { + margin-bottom: 10px; + } + + network-proxy-exclusions { + margin: 10px 0; + } + + #manualProxy { + -webkit-padding-start: var(--cr-section-padding); + } + </style> + + <!-- Proxy type dropdown --> + <div class="property-box"> + <div class="start">[[i18n('networkProxyConnectionType')]]</div> + <div class="md-select-wrapper"> + <select id="proxyType" class="md-select" on-change="onTypeChange_" + value="[[proxy_.Type]]" + disabled="[[!isEditable_('Type', networkProperties, editable, + useSharedProxies)]]" + aria-label="[[i18n('networkProxyConnectionType')]]"> + <template is="dom-repeat" items="[[proxyTypes_]]"> + <option value="[[item]]">[[getProxyTypeDesc_(item)]]</option> + </template> + </select> + <span class="md-select-underline"></span> + </div> + </div> + + <!-- Autoconfiguration (PAC) --> + <div class="property-box indented" + hidden$="[[!matches_(proxy_.Type, ProxySettingsType_.PAC)]]"> + <div>[[i18n('networkProxyAutoConfig')]]</div> + <paper-input no-label-float class="middle" value="{{proxy_.PAC}}" + disabled="[[!isEditable_('PAC', networkProperties, editable, + useSharedProxies)]]" + on-change="onPACChange_"> + </paper-input> + </div> + + <!-- Web Proxy Auto Discovery (WPAD) --> + <div class="property-box indented" + hidden$="[[!matches_(proxy_.Type, ProxySettingsType_.WPAD)]]"> + <div>[[i18n('networkProxyWpad')]]</div> + <div class="middle">[[WPAD_]]</div> + </div> + + <!-- Manual --> + <div class="property-box indented" + hidden$="[[!matches_(proxy_.Type, ProxySettingsType_.MANUAL)]]"> + <div id="networkProxyToggleLabel" class="flex"> + [[i18n('networkProxyUseSame')]] + </div> + <paper-toggle-button checked="{{useSameProxy_}}" + disabled="[[!isEditable_('Type', networkProperties, editable, + useSharedProxies)]]" + aria-labelledby="networkProxyToggleLabel"> + </paper-toggle-button> + </div> + + <div id="manualProxy" class="layout vertical start" + hidden$="[[!matches_(proxy_.Type, ProxySettingsType_.MANUAL)]]"> + <div hidden$="[[!useSameProxy_]]" class="layout vertical"> + <network-proxy-input + on-proxy-change="onProxyInputChange_" + editable="[[isEditable_('Manual.HTTPProxy.Host', networkProperties, + editable, useSharedProxies)]]" + value="{{proxy_.Manual.HTTPProxy}}" + label="[[i18n('networkProxy')]]"> + </network-proxy-input> + </div> + <div hidden$="[[useSameProxy_]]" class="layout vertical"> + <network-proxy-input + on-proxy-change="onProxyInputChange_" + editable="[[isEditable_('Manual.HTTPProxy.Host', networkProperties, + editable, useSharedProxies)]]" + value="{{proxy_.Manual.HTTPProxy}}" + label="[[i18n('networkProxyHttp')]]"> + </network-proxy-input> + <network-proxy-input + on-proxy-change="onProxyInputChange_" + editable="[[isEditable_('Manual.SecureHTTPProxy.Host', + networkProperties, editable, useSharedProxies)]]" + value="{{proxy_.Manual.SecureHTTPProxy}}" + label="[[i18n('networkProxyShttp')]]"> + </network-proxy-input> + <network-proxy-input + on-proxy-change="onProxyInputChange_" + editable="[[isEditable_('Manual.FTPProxy.Host', networkProperties, + editable, useSharedProxies)]]" + value="{{proxy_.Manual.FTPProxy}}" + label="[[i18n('networkProxyFtp')]]"> + </network-proxy-input> + <network-proxy-input + on-proxy-change="onProxyInputChange_" + editable="[[isEditable_('Manual.SOCKS.Host', networkProperties, + editable, useSharedProxies)]]" + value="{{proxy_.Manual.SOCKS}}" + label="[[i18n('networkProxySocks')]]"> + </network-proxy-input> + </div> + + <div hidden="[[!isEditable_('Type', networkProperties, editable, + useSharedProxies)]]"> + <div>[[i18n('networkProxyExceptionList')]]</div> + <network-proxy-exclusions on-proxy-change="onProxyExclusionsChange_" + exclusions="{{proxy_.ExcludeDomains}}"> + </network-proxy-exclusions> + <div class="layout horizontal"> + <paper-input-container no-label-float class="flex"> + <input id="proxyExclusion" is="iron-input"> + <iron-a11y-keys keys="enter" + on-keys-pressed="onAddProxyExclusionTap_"> + </iron-a11y-keys> + </paper-input-container> + <paper-button on-tap="onAddProxyExclusionTap_"> + [[i18n('networkProxyAddException')]] + </paper-button> + </div> + </div> + + <paper-button id="saveManualProxy" + on-tap="onSaveProxyTap_" class="action-button" + disabled="[[!isSaveManualProxyEnabled_(networkProperties, + proxyModified_, proxy_.*)]]"> + [[i18n('save')]] + </paper-button> + </div> + + </template> + <script src="network_proxy.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.js new file mode 100644 index 00000000000..a1f2056eba8 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.js @@ -0,0 +1,428 @@ +// Copyright 2017 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. + +/** + * @fileoverview Polymer element for displaying and editing network proxy + * values. + */ +Polymer({ + is: 'network-proxy', + + behaviors: [ + CrPolicyNetworkBehavior, + I18nBehavior, + ], + + properties: { + /** + * The network properties dictionary containing the proxy properties to + * display and modify. + * @type {!CrOnc.NetworkProperties|undefined} + */ + networkProperties: { + type: Object, + observer: 'networkPropertiesChanged_', + }, + + /** Whether or not the proxy values can be edited. */ + editable: { + type: Boolean, + value: false, + }, + + /** Whether shared proxies are allowed. */ + useSharedProxies: { + type: Boolean, + value: false, + observer: 'updateProxy_', + }, + + /** + * UI visible / edited proxy configuration. + * @private {!CrOnc.ProxySettings} + */ + proxy_: { + type: Object, + value: function() { + return this.createDefaultProxySettings_(); + }, + }, + + /** + * The Web Proxy Auto Discovery URL extracted from networkProperties. + * @private + */ + WPAD_: { + type: String, + value: '', + }, + + /** + * Whether or not to use the same manual proxy for all protocols. + * @private + */ + useSameProxy_: { + type: Boolean, + value: false, + observer: 'useSameProxyChanged_', + }, + + /** + * Array of proxy configuration types. + * @private {!Array<string>} + * @const + */ + proxyTypes_: { + type: Array, + value: [ + CrOnc.ProxySettingsType.DIRECT, + CrOnc.ProxySettingsType.PAC, + CrOnc.ProxySettingsType.WPAD, + CrOnc.ProxySettingsType.MANUAL, + ], + readOnly: true + }, + + /** + * Object providing proxy type values for data binding. + * @private {!Object} + * @const + */ + ProxySettingsType_: { + type: Object, + value: { + DIRECT: CrOnc.ProxySettingsType.DIRECT, + PAC: CrOnc.ProxySettingsType.PAC, + MANUAL: CrOnc.ProxySettingsType.MANUAL, + WPAD: CrOnc.ProxySettingsType.WPAD, + }, + readOnly: true, + }, + }, + + /** + * Saved Manual properties so that switching to another type does not loose + * any set properties while the UI is open. + * @private {!CrOnc.ManualProxySettings|undefined} + */ + savedManual_: undefined, + + /** + * Saved ExcludeDomains properties so that switching to a non-Manual type does + * not loose any set exclusions while the UI is open. + * @private {!Array<string>|undefined} + */ + savedExcludeDomains_: undefined, + + /** + * Set to true while modifying proxy values so that an update does not + * override the edited values. + * @private {boolean} + */ + proxyModified_: false, + + /** @override */ + attached: function() { + this.reset(); + }, + + /** + * Called any time the page is refreshed or navigated to so that the proxy + * is updated correctly. + */ + reset: function() { + this.proxyModified_ = false; + this.proxy_ = this.createDefaultProxySettings_(); + this.updateProxy_(); + }, + + /** @private */ + networkPropertiesChanged_: function() { + if (this.proxyModified_) + return; // Ignore update. + this.updateProxy_(); + }, + + /** @private */ + updateProxy_: function() { + if (!this.networkProperties) + return; + + /** @type {!CrOnc.ProxySettings} */ + var proxy = this.createDefaultProxySettings_(); + + // For shared networks with unmanaged proxy settings, ignore any saved + // proxy settings (use the default values). + if (this.isShared_()) { + var property = this.getProxySettingsTypeProperty_(); + if (!this.isControlled(property) && !this.useSharedProxies) { + this.setProxyAsync_(proxy); + return; // Proxy settings will be ignored. + } + } + + /** @type {!chrome.networkingPrivate.ManagedProxySettings|undefined} */ + var proxySettings = this.networkProperties.ProxySettings; + if (proxySettings) { + proxy.Type = /** @type {!CrOnc.ProxySettingsType} */ ( + CrOnc.getActiveValue(proxySettings.Type)); + if (proxySettings.Manual) { + proxy.Manual.HTTPProxy = /** @type {!CrOnc.ProxyLocation|undefined} */ ( + CrOnc.getSimpleActiveProperties( + proxySettings.Manual.HTTPProxy)) || + {Host: '', Port: 80}; + proxy.Manual.SecureHTTPProxy = + /** @type {!CrOnc.ProxyLocation|undefined} */ ( + CrOnc.getSimpleActiveProperties( + proxySettings.Manual.SecureHTTPProxy)) || + {Host: '', Port: 80}; + proxy.Manual.FTPProxy = + /** @type {!CrOnc.ProxyLocation|undefined} */ ( + CrOnc.getSimpleActiveProperties( + proxySettings.Manual.FTPProxy)) || + {Host: '', Port: 80}; + proxy.Manual.SOCKS = + /** @type {!CrOnc.ProxyLocation|undefined} */ ( + CrOnc.getSimpleActiveProperties(proxySettings.Manual.SOCKS)) || + {Host: '', Port: 80}; + var jsonHttp = proxy.Manual.HTTPProxy; + this.useSameProxy_ = + (CrOnc.proxyMatches(jsonHttp, proxy.Manual.SecureHTTPProxy) && + CrOnc.proxyMatches(jsonHttp, proxy.Manual.FTPProxy) && + CrOnc.proxyMatches(jsonHttp, proxy.Manual.SOCKS)) || + (!proxy.Manual.SecureHTTPProxy.Host && + !proxy.Manual.FTPProxy.Host && !proxy.Manual.SOCKS.Host); + } + if (proxySettings.ExcludeDomains) { + proxy.ExcludeDomains = /** @type {!Array<string>|undefined} */ ( + CrOnc.getActiveValue(proxySettings.ExcludeDomains)); + } + proxy.PAC = /** @type {string|undefined} */ ( + CrOnc.getActiveValue(proxySettings.PAC)); + } + // Use saved ExcludeDomains and Manual if not defined. + proxy.ExcludeDomains = proxy.ExcludeDomains || this.savedExcludeDomains_; + proxy.Manual = proxy.Manual || this.savedManual_; + + // Set the Web Proxy Auto Discovery URL. + var ipv4 = + CrOnc.getIPConfigForType(this.networkProperties, CrOnc.IPType.IPV4); + this.WPAD_ = (ipv4 && ipv4.WebProxyAutoDiscoveryUrl) || ''; + + this.setProxyAsync_(proxy); + }, + + /** + * @param {!CrOnc.ProxySettings} proxy + * @private + */ + setProxyAsync_: function(proxy) { + // Set this.proxy_ after dom-repeat has been stamped. + this.async(() => { + this.proxy_ = proxy; + this.proxyModified_ = false; + }); + }, + + /** @private */ + useSameProxyChanged_: function() { + this.proxyModified_ = true; + }, + + /** + * @return {CrOnc.ProxySettings} An empty/default proxy settings object. + * @private + */ + createDefaultProxySettings_: function() { + return { + Type: CrOnc.ProxySettingsType.DIRECT, + ExcludeDomains: [], + Manual: { + HTTPProxy: {Host: '', Port: 80}, + SecureHTTPProxy: {Host: '', Port: 80}, + FTPProxy: {Host: '', Port: 80}, + SOCKS: {Host: '', Port: 1080} + }, + PAC: '' + }; + }, + + /** + * Called when the proxy changes in the UI. + * @private + */ + sendProxyChange_: function() { + var proxy = + /** @type {!CrOnc.ProxySettings} */ (Object.assign({}, this.proxy_)); + if (proxy.Type == CrOnc.ProxySettingsType.MANUAL) { + var manual = proxy.Manual; + var defaultProxy = manual.HTTPProxy || {Host: '', Port: 80}; + if (this.useSameProxy_) { + proxy.Manual.SecureHTTPProxy = /** @type {!CrOnc.ProxyLocation} */ ( + Object.assign({}, defaultProxy)); + proxy.Manual.FTPProxy = /** @type {!CrOnc.ProxyLocation} */ ( + Object.assign({}, defaultProxy)); + proxy.Manual.SOCKS = /** @type {!CrOnc.ProxyLocation} */ ( + Object.assign({}, defaultProxy)); + } else { + // Remove properties with empty hosts to unset them. + if (manual.HTTPProxy && !manual.HTTPProxy.Host) + delete manual.HTTPProxy; + if (manual.SecureHTTPProxy && !manual.SecureHTTPProxy.Host) + delete manual.SecureHTTPProxy; + if (manual.FTPProxy && !manual.FTPProxy.Host) + delete manual.FTPProxy; + if (manual.SOCKS && !manual.SOCKS.Host) + delete manual.SOCKS; + } + this.savedManual_ = Object.assign({}, manual); + this.savedExcludeDomains_ = proxy.ExcludeDomains; + } else if (proxy.Type == CrOnc.ProxySettingsType.PAC) { + if (!proxy.PAC) + return; + } + this.fire('proxy-change', {field: 'ProxySettings', value: proxy}); + this.proxyModified_ = false; + }, + + /** + * Event triggered when the selected proxy type changes. + * @param {!Event} event + * @private + */ + onTypeChange_: function(event) { + var target = /** @type {!HTMLSelectElement} */ (event.target); + var type = /** @type {chrome.networkingPrivate.ProxySettingsType} */ ( + target.value); + this.set('proxy_.Type', type); + if (type == CrOnc.ProxySettingsType.MANUAL) + this.proxyModified_ = true; + else + this.sendProxyChange_(); + }, + + /** @private */ + onPACChange_: function() { + this.sendProxyChange_(); + }, + + /** @private */ + onProxyInputChange_: function() { + this.proxyModified_ = true; + }, + + /** + * Event triggered when a proxy exclusion is added. + * @param {!Event} event The add proxy exclusion event. + * @private + */ + onAddProxyExclusionTap_: function(event) { + var value = this.$.proxyExclusion.value; + if (!value) + return; + this.push('proxy_.ExcludeDomains', value); + // Clear input. + this.$.proxyExclusion.value = ''; + this.proxyModified_ = true; + }, + + /** + * Event triggered when the proxy exclusion list changes. + * @param {!Event} event The remove proxy exclusions change event. + * @private + */ + onProxyExclusionsChange_: function(event) { + this.proxyModified_ = true; + }, + + /** @private */ + onSaveProxyTap_: function() { + this.sendProxyChange_(); + }, + + /** + * @param {string} proxyType The proxy type. + * @return {string} The description for |proxyType|. + * @private + */ + getProxyTypeDesc_: function(proxyType) { + if (proxyType == CrOnc.ProxySettingsType.MANUAL) + return this.i18n('networkProxyTypeManual'); + if (proxyType == CrOnc.ProxySettingsType.PAC) + return this.i18n('networkProxyTypePac'); + if (proxyType == CrOnc.ProxySettingsType.WPAD) + return this.i18n('networkProxyTypeWpad'); + return this.i18n('networkProxyTypeDirect'); + }, + + /** + * @return {!CrOnc.ManagedProperty|undefined} + * @private + */ + getProxySettingsTypeProperty_: function() { + return /** @type {!CrOnc.ManagedProperty|undefined} */ ( + this.get('ProxySettings.Type', this.networkProperties)); + }, + + /** + * @param {string} propertyName + * @return {boolean} Whether the named property setting is editable. + * @private + */ + isEditable_: function(propertyName) { + if (!this.editable || (this.isShared_() && !this.useSharedProxies)) + return false; + if (!this.networkProperties.hasOwnProperty('ProxySettings')) + return true; // No proxy settings defined, so not enforced. + var property = /** @type {!CrOnc.ManagedProperty|undefined} */ ( + this.get('ProxySettings.' + propertyName, this.networkProperties)); + if (!property) + return true; + return this.isPropertyEditable_(property); + }, + + /** + * @param {!CrOnc.ManagedProperty|undefined} property + * @return {boolean} Whether |property| is editable. + * @private + */ + isPropertyEditable_: function(property) { + return !this.isNetworkPolicyEnforced(property) && + !this.isExtensionControlled(property); + }, + + /** + * @return {boolean} + * @private + */ + isShared_: function() { + return this.networkProperties.Source == 'Device' || + this.networkProperties.Source == 'DevicePolicy'; + }, + + /** + * @return {boolean} + * @private + */ + isSaveManualProxyEnabled_: function() { + if (!this.proxyModified_) + return false; + var manual = this.proxy_.Manual; + var httpHost = this.get('HTTPProxy.Host', manual); + if (this.useSameProxy_) + return !!httpHost; + return !!httpHost || !!this.get('SecureHTTPProxy.Host', manual) || + !!this.get('FTPProxy.Host', manual) || !!this.get('SOCKS.Host', manual); + }, + + /** + * @param {string} property The property to test + * @param {string} value The value to test against + * @return {boolean} True if property == value + * @private + */ + matches_: function(property, value) { + return property == value; + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_exclusions.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_exclusions.html new file mode 100644 index 00000000000..f574252c285 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_exclusions.html @@ -0,0 +1,37 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> +<link rel="import" href="chrome://resources/cr_elements/icons.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-proxy-exclusions"> + <template> + <style include="network-shared cr-hidden-style iron-flex"> + iron-icon { + @apply(--cr-actionable); + margin: 5px; + } + + #container { + align-self: stretch; + border: 1px solid lightgrey; + height: 100px; + margin-top: 10px; + overflow-y: auto; + padding: 5px; + } + </style> + <div id="container"> + <template is="dom-repeat" items="[[exclusions]]"> + <div class="layout horizontal center" tabindex="0" > + <div class="flex">[[item]]</div> + <iron-icon class="favicon-image" icon="cr:clear" + on-tap="onRemoveTap_" tabindex="0" hidden="[[!editable]]"> + </iron-icon> + </div> + </template> + </div> + </template> + <script src="network_proxy_exclusions.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_exclusions.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_exclusions.js new file mode 100644 index 00000000000..347b22980d3 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_exclusions.js @@ -0,0 +1,46 @@ +// Copyright 2015 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. + +/** + * @fileoverview Polymer element for displaying a list of proxy exclusions. + * Includes UI for adding, changing, and removing entries. + */ + +(function() { + +Polymer({ + is: 'network-proxy-exclusions', + + properties: { + /** Whether or not the proxy values can be edited. */ + editable: { + type: Boolean, + value: false, + }, + + /** + * The list of exclusions. + * @type {!Array<string>} + */ + exclusions: { + type: Array, + value: function() { + return []; + }, + notify: true + } + }, + + /** + * Event triggered when an item is removed. + * @param {!{model: !{index: number}}} event + * @private + */ + onRemoveTap_: function(event) { + var index = event.model.index; + this.splice('exclusions', index, 1); + this.fire('proxy-change'); + } +}); +})(); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_input.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_input.html new file mode 100644 index 00000000000..a1104bde59f --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_input.html @@ -0,0 +1,50 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-input/iron-input.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input-container.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-proxy-input"> + <template> + <style include="network-shared"> + paper-input-container { + -webkit-margin-start: 10px; + margin-bottom: -12px; + margin-top: -8px; + } + + #container { + align-items: center; + display: flex; + flex: 0 1 auto; + flex-direction: row; + } + + #label { + flex: 1; + } + + #host { + width: 200px; + } + + #port { + width: 50px; + } + </style> + <div id="container"> + <div id="label">[[label]]</div> + <paper-input-container id="host" no-label-float> + <input is="iron-input" bind-value="{{value.Host}}" + disabled="[[!editable]]" on-change="onValueChange_"> + </paper-input-container> + <div>[[i18n('networkProxyPort')]]</div> + <paper-input-container id="port" no-label-float> + <input is="iron-input" bind-value="{{value.Port}}" + disabled="[[!editable]]" on-change="onValueChange_"> + </paper-input-container> + </div> + </template> + <script src="network_proxy_input.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_input.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_input.js new file mode 100644 index 00000000000..63609b074ce --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_input.js @@ -0,0 +1,57 @@ +// Copyright 2015 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. + +/** + * @fileoverview Polymer element for displaying and editing a single + * network proxy value. When the URL or port changes, a 'proxy-change' event is + * fired with the combined url and port values passed as a single string, + * url:port. + */ +Polymer({ + is: 'network-proxy-input', + + behaviors: [I18nBehavior], + + properties: { + /** + * Whether or not the proxy value can be edited. + */ + editable: { + type: Boolean, + value: false, + }, + + /** + * A label for the proxy value. + */ + label: { + type: String, + value: 'Proxy', + }, + + /** + * The proxy object. + * @type {!CrOnc.ProxyLocation} + */ + value: { + type: Object, + value: function() { + return {Host: '', Port: 80}; + }, + notify: true, + }, + }, + + /** + * Event triggered when an input value changes. + * @private + */ + onValueChange_: function() { + var port = parseInt(this.value.Port, 10); + if (isNaN(port)) + port = 80; + this.value.Port = port; + this.fire('proxy-change', {value: this.value}); + } +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_shared_css.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_shared_css.html new file mode 100644 index 00000000000..c235c50288d --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_shared_css.html @@ -0,0 +1,51 @@ +<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> + + <!-- Common styles for network elements. --> + +<dom-module id="network-shared"> + <template> + <style include="cr-shared-style"> + .property-box { + @apply(--cr-section); + border-top: none; + min-height: var(--cr-section-two-line-min-height); + padding: 0; + } + + .property-box.indented { + -webkit-margin-start: var(--cr-section-padding); + } + + .property-box.single-column { + align-items: flex-start; + flex-direction: column; + justify-content: center; + } + + .property-box.stretch { + align-items: stretch; + } + + .property-box > .start { + align-items: center; + flex: auto; + } + + .property-box > .middle { + -webkit-padding-start: 16px; + align-items: center; + flex: auto; + } + + paper-input-container { + --paper-input-container-input: { + color: var(--paper-grey-600); + font-size: inherit; + font-weight: 400; + }; + margin-bottom: 0; + margin-top: -9px; + } + </style> + </template> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/compiled_resources2.gyp b/chromium/ui/webui/resources/cr_components/compiled_resources2.gyp new file mode 100644 index 00000000000..eb688de9083 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/compiled_resources2.gyp @@ -0,0 +1,15 @@ +# Copyright 2017 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. +{ + 'targets': [ + { + 'target_name': 'cr_components_resources', + 'type': 'none', + 'dependencies': [ + 'chromeos/compiled_resources2.gyp:*', + 'certificate_manager/compiled_resources2.gyp:*', + ], + }, + ] +} diff --git a/chromium/ui/webui/resources/cr_components_resources.grdp b/chromium/ui/webui/resources/cr_components_resources.grdp new file mode 100644 index 00000000000..164e20e627f --- /dev/null +++ b/chromium/ui/webui/resources/cr_components_resources.grdp @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="utf-8"?> +<grit-part> + <if expr="use_nss_certs"> + <structure name="IDR_WEBUI_CA_TRUST_EDIT_DIALOG_JS" + file="cr_components/certificate_manager/ca_trust_edit_dialog.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CA_TRUST_EDIT_DIALOG_HTML" + file="cr_components/certificate_manager/ca_trust_edit_dialog.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_DELETE_CONFIRMATION_DIALOG_JS" + file="cr_components/certificate_manager/certificate_delete_confirmation_dialog.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_DELETE_CONFIRMATION_DIALOG_HTML" + file="cr_components/certificate_manager/certificate_delete_confirmation_dialog.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_MANAGER_HTML" + file="cr_components/certificate_manager/certificate_manager.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_MANAGER_JS" + file="cr_components/certificate_manager/certificate_manager.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_PASSWORD_ENCRYPTION_DIALOG_JS" + file="cr_components/certificate_manager/certificate_password_encryption_dialog.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_PASSWORD_ENCRYPTION_DIALOG_HTML" + file="cr_components/certificate_manager/certificate_password_encryption_dialog.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_PASSWORD_DECRYPTION_DIALOG_JS" + file="cr_components/certificate_manager/certificate_password_decryption_dialog.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_PASSWORD_DECRYPTION_DIALOG_HTML" + file="cr_components/certificate_manager/certificate_password_decryption_dialog.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATES_ERROR_DIALOG_JS" + file="cr_components/certificate_manager/certificates_error_dialog.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATES_ERROR_DIALOG_HTML" + file="cr_components/certificate_manager/certificates_error_dialog.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_ENTRY_HTML" + file="cr_components/certificate_manager/certificate_entry.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_ENTRY_JS" + file="cr_components/certificate_manager/certificate_entry.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_LIST_HTML" + file="cr_components/certificate_manager/certificate_list.html" + preprocess="true" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_LIST_JS" + file="cr_components/certificate_manager/certificate_list.js" + preprocess="true" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_MANAGER_TYPES_HTML" + file="cr_components/certificate_manager/certificate_manager_types.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_MANAGER_TYPES_JS" + file="cr_components/certificate_manager/certificate_manager_types.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_SHARED_CSS_HTML" + file="cr_components/certificate_manager/certificate_shared_css.html" + type="chrome_html" + preprocess="true" /> + <structure name="IDR_WEBUI_CERTIFICATE_SUBENTRY_HTML" + file="cr_components/certificate_manager/certificate_subentry.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATE_SUBENTRY_JS" + file="cr_components/certificate_manager/certificate_subentry.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATES_BROWSER_PROXY_HTML" + file="cr_components/certificate_manager/certificates_browser_proxy.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CERTIFICATES_BROWSER_PROXY_JS" + file="cr_components/certificate_manager/certificates_browser_proxy.js" + type="chrome_html" /> + </if> + <if expr="chromeos"> + <!-- Chrome OS Custom Elements --> + <structure name="IDR_WEBUI_CHROMEOS_BLUETOOTH_DIALOG_HTML" + file="cr_components/chromeos/bluetooth_dialog.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_BLUETOOTH_DIALOG_JS" + file="cr_components/chromeos/bluetooth_dialog.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_APNLIST_HTML" + file="cr_components/chromeos/network/network_apnlist.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_APNLIST_JS" + file="cr_components/chromeos/network/network_apnlist.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_IP_CONFIG_HTML" + file="cr_components/chromeos/network/network_ip_config.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_IP_CONFIG_JS" + file="cr_components/chromeos/network/network_ip_config.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_NAMESERVERS_HTML" + file="cr_components/chromeos/network/network_nameservers.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_NAMESERVERS_JS" + file="cr_components/chromeos/network/network_nameservers.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_PROPERTY_LIST_HTML" + file="cr_components/chromeos/network/network_property_list.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_PROPERTY_LIST_JS" + file="cr_components/chromeos/network/network_property_list.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_PROXY_HTML" + file="cr_components/chromeos/network/network_proxy.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_PROXY_JS" + file="cr_components/chromeos/network/network_proxy.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_PROXY_EXCLUSIONS_HTML" + file="cr_components/chromeos/network/network_proxy_exclusions.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_PROXY_EXCLUSIONS_JS" + file="cr_components/chromeos/network/network_proxy_exclusions.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_PROXY_INPUT_HTML" + file="cr_components/chromeos/network/network_proxy_input.html" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_PROXY_INPUT_JS" + file="cr_components/chromeos/network/network_proxy_input.js" + type="chrome_html" /> + <structure name="IDR_WEBUI_CHROMEOS_NETWORK_SHARED_CSS_HTML" + file="cr_components/chromeos/network/network_shared_css.html" + type="chrome_html" /> + </if> +</grit-part> diff --git a/chromium/ui/webui/resources/cr_elements/OWNERS b/chromium/ui/webui/resources/cr_elements/OWNERS index 057813143a1..5fd1662abb6 100644 --- a/chromium/ui/webui/resources/cr_elements/OWNERS +++ b/chromium/ui/webui/resources/cr_elements/OWNERS @@ -1,2 +1,3 @@ michaelpg@chromium.org +scottchen@chromium.org stevenjb@chromium.org diff --git a/chromium/ui/webui/resources/cr_elements/READE.md b/chromium/ui/webui/resources/cr_elements/READE.md new file mode 100644 index 00000000000..b8b47ef98b3 --- /dev/null +++ b/chromium/ui/webui/resources/cr_elements/READE.md @@ -0,0 +1,16 @@ +This directory contains simple Polymer web components for Web UI. These +components may be shared across any WebUI and should be compatible across +all platforms (including ios). + +These web components may not contain any i18n dependencies and may not use +I18nBehavior. Instead, any text (labels, tooltips, etc) should be passed as +properties. + +These web components should avoid the use of chrome.send and should generally +avoid dependencies on extension APIs as well. + +TODO(stevenjb/dpapad): Audit elements currently using chrome.settingsPrivate +and chrome.networkingPrivate and decide whether to move these or update the +guidelines. + +For more complex components, see cr_components. diff --git a/chromium/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.html b/chromium/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.html index d2e3639bbe8..abc2e47ac82 100644 --- a/chromium/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.html +++ b/chromium/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.html @@ -80,7 +80,7 @@ src="[[getImgSrc_(profileImageUrl_)]]" hidden="[[!profileImageUrl_]]" srcset="[[getImgSrc2x_(profileImageUrl_)]]" - title="[[profileImageLoadingLabel]]"> + title="[[profileImageLabel]]"> <!-- Shows and selects the previously selected ('old') picture. --> <img id="oldImage" role="radio" data-type$="[[selectionTypesEnum_.OLD]]" diff --git a/chromium/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.js b/chromium/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.js index c9cb62fb793..990aaf67fbb 100644 --- a/chromium/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.js +++ b/chromium/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.js @@ -28,7 +28,6 @@ Polymer({ chooseFileLabel: String, oldImageLabel: String, profileImageLabel: String, - profileImageLoadingLabel: String, takePhotoLabel: String, /** diff --git a/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_list_item.js b/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_list_item.js index 5e3992a5705..eb4d0dff4c5 100644 --- a/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_list_item.js +++ b/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_list_item.js @@ -108,6 +108,10 @@ Polymer({ if (!this.isStateTextVisible_()) return ''; var state = this.networkState.ConnectionState; + // For Cellular, an empty ConnectionState indicates that the device is + // still initializing. + if (!state && this.networkState.Type == CrOnc.Type.CELLULAR) + return CrOncStrings.networkListItemInitializing; if (state == CrOnc.ConnectionState.CONNECTED) return CrOncStrings.networkListItemConnected; if (state == CrOnc.ConnectionState.CONNECTING) diff --git a/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_select.js b/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_select.js index 0349141de90..4b98c6fff97 100644 --- a/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_select.js +++ b/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_network_select.js @@ -108,24 +108,55 @@ Polymer({ }, /** - * Request the list of visible networks. May be called externally to force a + * Requests the device and network states. May be called externally to force a * refresh and list update (e.g. when the element is shown). */ refreshNetworks: function() { + chrome.networkingPrivate.getDeviceStates( + this.getDeviceStatesCallback_.bind(this)); + }, + + /** + * @param {!Array<!CrOnc.DeviceStateProperties>} deviceStates + * @private + */ + getDeviceStatesCallback_: function(deviceStates) { + var uninitializedCellular = deviceStates.find(function(device) { + return device.Type == CrOnc.Type.CELLULAR && + device.State == CrOnc.DeviceState.UNINITIALIZED; + }); + this.getNetworkStates_(uninitializedCellular); + }, + + /** + * @param {!CrOnc.DeviceStateProperties|undefined} uninitializedCellular + * A cellular device state to pass to |getNetworksCallback_| or undefined. + */ + getNetworkStates_: function(uninitializedCellular) { var filter = { networkType: chrome.networkingPrivate.NetworkType.ALL, visible: true, configured: false }; - chrome.networkingPrivate.getNetworks( - filter, this.getNetworksCallback_.bind(this)); + chrome.networkingPrivate.getNetworks(filter, function(states) { + this.getNetworksCallback_(uninitializedCellular, states); + }.bind(this)); }, /** + * @param {!CrOnc.DeviceStateProperties|undefined} uninitializedCellular + * If defined, prepends a Cellular state with no ConnectionState to + * represent an uninitialized Cellular device. * @param {!Array<!CrOnc.NetworkStateProperties>} states * @private */ - getNetworksCallback_: function(states) { + getNetworksCallback_: function(uninitializedCellular, states) { + if (uninitializedCellular) { + states.unshift({ + GUID: '', + Type: uninitializedCellular.Type, + }); + } this.networkStateList_ = states; var defaultState = (this.networkStateList_.length > 0 && this.networkStateList_[0].ConnectionState == diff --git a/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_onc_types.js b/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_onc_types.js index 8382119b6d4..4c51796778b 100644 --- a/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_onc_types.js +++ b/chromium/ui/webui/resources/cr_elements/chromeos/network/cr_onc_types.js @@ -29,6 +29,7 @@ * networkListItemConnected: string, * networkListItemConnecting: string, * networkListItemConnectingTo: string, + * networkListItemInitializing: string, * networkListItemNotConnected: string, * vpnNameTemplate: string, * }} diff --git a/chromium/ui/webui/resources/cr_elements/compiled_resources2.gyp b/chromium/ui/webui/resources/cr_elements/compiled_resources2.gyp index cd7896afb8d..89de6886a21 100644 --- a/chromium/ui/webui/resources/cr_elements/compiled_resources2.gyp +++ b/chromium/ui/webui/resources/cr_elements/compiled_resources2.gyp @@ -13,6 +13,7 @@ 'cr_dialog/compiled_resources2.gyp:*', 'cr_drawer/compiled_resources2.gyp:*', 'cr_expand_button/compiled_resources2.gyp:*', + 'cr_link_row/compiled_resources2.gyp:*', 'cr_profile_avatar_selector/compiled_resources2.gyp:*', 'policy/compiled_resources2.gyp:*', ], diff --git a/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html b/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html index 3fb14c427f5..05ca3cc16ce 100644 --- a/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html +++ b/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html @@ -1,7 +1,8 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + <link rel="import" href="chrome://resources/html/assert.html"> <link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html"> <link rel="import" href="chrome://resources/html/util.html"> -<link rel="import" href="chrome://resources/html/polymer.html"> <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> <dom-module id="cr-action-menu"> @@ -23,6 +24,7 @@ :host ::content .dropdown-item { background: none; border: none; + border-radius: 0; box-sizing: border-box; color: var(--paper-grey-900); font: inherit; @@ -55,8 +57,8 @@ outline: none; } </style> - <div class="item-wrapper" tabindex="-1"> - <content select=".dropdown-item,hr"></content> + <div class="item-wrapper" tabindex="-1" role="menu"> + <content select=".dropdown-item,hr" id="contentNode"></content> </div> </template> <script src="cr_action_menu.js"></script> diff --git a/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js b/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js index 181936d7132..d524c5433cf 100644 --- a/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js +++ b/chromium/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js @@ -30,6 +30,9 @@ var AnchorAlignment = { AFTER_END: 2, }; +/** @const {string} */ +var DROPDOWN_ITEM_CLASS = 'dropdown-item'; + (function() { /** * Returns the point to start along the X or Y axis given a start and end @@ -118,6 +121,9 @@ Polymer({ /** @private {boolean} */ hasMousemoveListener_: false, + /** @private {?PolymerDomApi.ObserveHandle} */ + contentObserver_: null, + hostAttributes: { tabindex: 0, }, @@ -137,6 +143,10 @@ Polymer({ removeListeners_: function() { window.removeEventListener('resize', this.boundClose_); window.removeEventListener('popstate', this.boundClose_); + if (this.contentObserver_) { + Polymer.dom(this.$.contentNode).unobserveNodes(this.contentObserver_); + this.contentObserver_ = null; + } }, /** @@ -321,7 +331,7 @@ Polymer({ // Restore the scroll position. doc.scrollTop = scrollTop; doc.scrollLeft = scrollLeft; - this.addCloseListeners_(); + this.addListeners_(); }, /** @private */ @@ -369,13 +379,24 @@ Polymer({ /** * @private */ - addCloseListeners_: function() { + addListeners_: function() { this.boundClose_ = this.boundClose_ || function() { if (this.open) this.close(); }.bind(this); window.addEventListener('resize', this.boundClose_); window.addEventListener('popstate', this.boundClose_); + + this.contentObserver_ = + Polymer.dom(this.$.contentNode).observeNodes((info) => { + info.addedNodes.forEach((node) => { + if (node.classList && + node.classList.contains(DROPDOWN_ITEM_CLASS) && + !node.getAttribute('role')) { + node.setAttribute('role', 'menuitem'); + } + }); + }); }, }); })(); diff --git a/chromium/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html b/chromium/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html index bb556e458e8..2d5528b32dc 100644 --- a/chromium/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html +++ b/chromium/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html @@ -13,12 +13,13 @@ --scroll-border: 1px solid var(--paper-grey-300); border: 0; border-radius: 2px; - bottom: 0; + bottom: 50%; box-shadow: 0 0 16px rgba(0, 0, 0, 0.12), 0 16px 16px rgba(0, 0, 0, 0.24); color: inherit; + overflow-y: hidden; padding: 0; - top: 0; + top: 50%; width: 512px; } @@ -55,6 +56,7 @@ font-size: calc(15 / 13 * 100%); line-height: 1; padding: 16px 16px; + @apply(--cr-dialog-title); } :host ::slotted([slot=button-container]) { @@ -95,7 +97,7 @@ .top-container { align-items: flex-start; display: flex; - min-height: 47px; + min-height: var(--cr-dialog-top-container-min-height, 47px); } .title-container { @@ -127,6 +129,7 @@ on-keypress="onCloseKeypress_"> </button> </div> + <slot name="header"></slot> <div class="body-container"> <span id="bodyTopMarker"></span> <slot name="body"></slot> diff --git a/chromium/ui/webui/resources/cr_elements/cr_drawer/cr_drawer.html b/chromium/ui/webui/resources/cr_elements/cr_drawer/cr_drawer.html index 20d15055f7b..ad9d19fdcf8 100644 --- a/chromium/ui/webui/resources/cr_elements/cr_drawer/cr_drawer.html +++ b/chromium/ui/webui/resources/cr_elements/cr_drawer/cr_drawer.html @@ -64,14 +64,14 @@ outline: none; } - :host ::content .drawer-content { + :host ::slotted(.drawer-content) { height: calc(100% - 56px); overflow: auto; } </style> <div id="container" on-tap="onContainerTap_"> <div class="drawer-header" tabindex="-1">[[heading]]</div> - <content></content> + <slot></slot> </div> </template> </dom-module> diff --git a/chromium/ui/webui/resources/cr_elements/cr_icons_css.html b/chromium/ui/webui/resources/cr_elements/cr_icons_css.html index 6428c757957..77f613fcf69 100644 --- a/chromium/ui/webui/resources/cr_elements/cr_icons_css.html +++ b/chromium/ui/webui/resources/cr_elements/cr_icons_css.html @@ -5,7 +5,7 @@ <template> <style> :host-context([dir=rtl]) button[is='paper-icon-button-light'] { - transform: scaleX(-1); /* Flip on the X axis (aka mirror). */ + transform: scaleX(-1); /* Invert X: flip on the Y axis (aka mirror). */ } button[is='paper-icon-button-light'] { @@ -18,6 +18,15 @@ width: var(--cr-icon-ripple-size); } + button[is='paper-icon-button-light'].no-overlap { + margin-left: 0; + margin-right: 0; + } + + button[is='paper-icon-button-light'].icon-arrow-back { + background-image: url(../images/icon_arrow_back.svg); + } + button[is='paper-icon-button-light'].icon-cancel { background-image: url(../images/icon_cancel.svg); } @@ -50,10 +59,18 @@ background-image: url(../images/open_in_new.svg); } + button[is='paper-icon-button-light'].icon-menu-white { + background-image: url(../images/icon_menu_white.svg); + } + button[is='paper-icon-button-light'].icon-more-vert { background-image: url(../images/icon_more_vert.svg); } + button[is='paper-icon-button-light'].icon-refresh { + background-image: url(../images/icon_refresh.svg); + } + button[is='paper-icon-button-light'].icon-settings { background-image: url(../images/icon_settings.svg); } diff --git a/chromium/ui/webui/resources/js/chromeos/compiled_resources2.gyp b/chromium/ui/webui/resources/cr_elements/cr_link_row/compiled_resources2.gyp index a363475a954..11ed485f750 100644 --- a/chromium/ui/webui/resources/js/chromeos/compiled_resources2.gyp +++ b/chromium/ui/webui/resources/cr_elements/cr_link_row/compiled_resources2.gyp @@ -1,15 +1,13 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2016 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. { 'targets': [ { - 'target_name': 'ui_account_tweaks', + 'target_name': 'cr_link_row', 'dependencies': [ - '../compiled_resources2.gyp:cr', - '../compiled_resources2.gyp:load_time_data', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, - ] + ], } diff --git a/chromium/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.html b/chromium/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.html new file mode 100644 index 00000000000..0ea10f6da08 --- /dev/null +++ b/chromium/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.html @@ -0,0 +1,74 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html"> +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> +<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/default-theme.html"> + +<dom-module id="cr-link-row"> + <template strip-whitespace=""> + <style include="cr-hidden-style cr-icons"> + :host { + background: none; + border: none; + color: inherit; + cursor: pointer; + font-size: 100%; /* Specifically for Mac OSX, harmless elsewhere. */ + line-height: 154%; /* 20px. */ + margin: 0; + outline: none; + padding: 0; + position: relative; + width: 100%; + @apply(--cr-section); + } + + :host(.continuation), + :host(.first) { + border-top: none; + } + + :host([disabled]) { + color: var(--paper-grey-500); + cursor: auto; + pointer-events: none; + } + + #label, + #subLabel { + display: flex; + } + + #labelWrapper { + flex: 1; + flex-basis: 0.000000001px; + } + + #outer { + align-items: center; + display: flex; + min-height: var(--cr-section-two-line-min-height); + width: 100%; + } + + #outer[noSubLabel] { + min-height: var(--cr-section-min-height); + } + + #subLabel { + /* TODO(dschuyler): replace with: @apply(--cr-secondary-text); */ + color: var(--paper-grey-600); + font-weight: 400; + } + </style> + <div id="outer" noSubLabel$="[[!subLabel]]"> + <div id="labelWrapper" hidden="[[!label]]"> + <div id="label" class="label">[[label]]</div> + <div id="subLabel" class="secondary label">[[subLabel]]</div> + </div> + <button class$="[[iconClass]]" is="paper-icon-button-light"></button> + </div> + </template> + <script src="cr_link_row.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.js b/chromium/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.js new file mode 100644 index 00000000000..e700b6d9c2d --- /dev/null +++ b/chromium/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.js @@ -0,0 +1,27 @@ +// Copyright 2017 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. + +/** + * @fileoverview + * A link row is a UI element similar to a button, though usually wider than a + * button (taking up the whole 'row'). The name link comes from the intended use + * of this element to take the user to another page in the app or to an external + * page (somewhat like an HTML link). + */ +Polymer({ + is: 'cr-link-row', + extends: 'button', + + properties: { + iconClass: String, + + label: String, + + subLabel: { + type: String, + /* Value used for noSubLabel attribute. */ + value: '', + }, + }, +}); diff --git a/chromium/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.html b/chromium/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.html index 9c37afe5501..29b9923b515 100644 --- a/chromium/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.html +++ b/chromium/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.html @@ -44,7 +44,7 @@ ignore-modified-key-events="[[ignoreModifiedKeyEvents]]"> <template is="dom-repeat" items="[[avatars]]"> <paper-button class="avatar" title="[[item.label]]" - style$="background-image: [[getIconImageset_(item.url)]]" + style$="background-image: [[getIconImageSet_(item.url)]]" on-tap="onAvatarTap_"> </paper-button> </template> diff --git a/chromium/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.js b/chromium/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.js index c09ce793656..28c58de1449 100644 --- a/chromium/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.js +++ b/chromium/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.js @@ -49,10 +49,10 @@ Polymer({ /** * @param {string} iconUrl - * @return {string} A CSS imageset for multiple scale factors. + * @return {string} A CSS image-set for multiple scale factors. * @private */ - getIconImageset_: function(iconUrl) { + getIconImageSet_: function(iconUrl) { return cr.icon.getImage(iconUrl); }, diff --git a/chromium/ui/webui/resources/cr_elements/cr_scrollable_behavior.js b/chromium/ui/webui/resources/cr_elements/cr_scrollable_behavior.js index 2a497807d95..9535c1a9b40 100644 --- a/chromium/ui/webui/resources/cr_elements/cr_scrollable_behavior.js +++ b/chromium/ui/webui/resources/cr_elements/cr_scrollable_behavior.js @@ -63,7 +63,7 @@ var CrScrollableBehavior = { */ updateScrollableContents: function() { if (this.intervalId_ !== null) - return; // notifyResize is arelady in progress. + return; // notifyResize is already in progress. this.requestUpdateScroll(); @@ -93,7 +93,7 @@ var CrScrollableBehavior = { }, /** - * Setup the intial scrolling related classes for each scrollable container. + * Setup the initial scrolling related classes for each scrollable container. * Called from ready() and updateScrollableContents(). May also be called * directly when the contents change (e.g. when not using iron-list). */ @@ -119,7 +119,7 @@ var CrScrollableBehavior = { this.async(function() { var scrollTop = list.savedScrollTops.shift(); // Ignore scrollTop of 0 in case it was intermittent (we do not need to - // explicity scroll to 0). + // explicitly scroll to 0). if (scrollTop != 0) list.scroll(0, scrollTop); }); @@ -136,7 +136,8 @@ var CrScrollableBehavior = { }, /** - * This gets called once intially and any time a scrollable container scrolls. + * This gets called once initially and any time a scrollable container + * scrolls. * @param {!HTMLElement} scrollable * @private */ diff --git a/chromium/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js b/chromium/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js index 491bbb53d60..9646f99e0be 100644 --- a/chromium/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js +++ b/chromium/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js @@ -33,9 +33,8 @@ var CrSearchFieldBehavior = { }, /** - * @abstract * @return {!HTMLInputElement} The input field element the behavior should - * use. + * use. */ getSearchInput: function() {}, diff --git a/chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar.html b/chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar.html index bfbe0c97fc8..b53e5f65276 100644 --- a/chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar.html +++ b/chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar.html @@ -1,11 +1,13 @@ <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-media-query/iron-media-query.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html"> <link rel="import" href="chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-media-query/iron-media-query.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html"> <dom-module id="cr-toolbar"> <template> - <style> + <style include="cr-icons"> :host { --cr-toolbar-field-width: 580px; --cr-toolbar-height: 56px; @@ -22,9 +24,7 @@ flex: 1; font-size: 123%; font-weight: 400; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + line-height: normal; } #leftContent { @@ -130,11 +130,12 @@ <div id="leftSpacer"> <!-- Note: showing #menuPromo relies on this dom-if being [restamp]. --> <template is="dom-if" if="[[showMenu]]" restamp> - <paper-icon-button id="menuButton" icon="cr20:menu" + <button id="menuButton" is="paper-icon-button-light" + class="icon-menu-white no-overlap" on-tap="onMenuTap_" title="[[titleIfNotShowMenuPromo_(menuLabel, showMenuPromo)]]" aria-label$="[[menuLabel]]"> - </paper-icon-button> + </button> <template is="dom-if" if="[[showMenuPromo]]"> <div id="menuPromo" role="tooltip"> [[menuPromo]] diff --git a/chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html b/chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html index 5b389334c39..dd002fb9c28 100644 --- a/chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html +++ b/chromium/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.html @@ -151,7 +151,6 @@ on-blur="onInputBlur_" incremental autofocus> - </input> </div> <template is="dom-if" if="[[hasSearchText]]"> <button is="paper-icon-button-light" class="icon-cancel-toolbar" diff --git a/chromium/ui/webui/resources/cr_elements/icons.html b/chromium/ui/webui/resources/cr_elements/icons.html index 40e08ef742e..bc0eae71ea2 100644 --- a/chromium/ui/webui/resources/cr_elements/icons.html +++ b/chromium/ui/webui/resources/cr_elements/icons.html @@ -17,7 +17,6 @@ blurry at 20 px). Please use 20 px icons when available. Keep these in sorted order by id="". See also http://goo.gl/Y1OdAq --> <g id="domain"><path d="M2,3 L2,17 L11.8267655,17 L13.7904799,17 L18,17 L18,7 L12,7 L12,3 L2,3 Z M8,13 L10,13 L10,15 L8,15 L8,13 Z M4,13 L6,13 L6,15 L4,15 L4,13 Z M8,9 L10,9 L10,11 L8,11 L8,9 Z M4,9 L6,9 L6,11 L4,11 L4,9 Z M12,9 L16,9 L16,15 L12,15 L12,9 Z M12,11 L14,11 L14,13 L12,13 L12,11 Z M8,5 L10,5 L10,7 L8,7 L8,5 Z M4,5 L6,5 L6,7 L4,7 L4,5 Z"></path></g> - <g id="menu"><rect x="2" y="4" width="16" height="2"></rect><rect x="2" y="9" width="16" height="2"></rect><rect x="2" y="14" width="16" height="2"></rect></g> </svg> </iron-iconset-svg> <iron-iconset-svg name="cr" size="24"> @@ -34,6 +33,7 @@ blurry at 20 px). Please use 20 px icons when available. <g id="camera-alt"><circle cx="12" cy="12" r="3.2"></circle><path d="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"></path></g> <g id="check"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"></path></g> </if> + <g id="account-child-invert" viewBox="0 0 48 48"><path d="M24 4c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6 2.69-6 6-6z"></path><path fill="none" d="M0 0h48v48H0V0z"></path><circle fill="none" cx="24" cy="26" r="4"></circle><path d="M24 18c-6.16 0-13 3.12-13 7.23v11.54c0 2.32 2.19 4.33 5.2 5.63 2.32 1 5.12 1.59 7.8 1.59.66 0 1.33-.06 2-.14v-5.2c-.67.08-1.34.14-2 .14-2.63 0-5.39-.57-7.68-1.55.67-2.12 4.34-3.65 7.68-3.65.86 0 1.75.11 2.6.29 2.79.62 5.2 2.15 5.2 4.04v4.47c3.01-1.31 5.2-3.31 5.2-5.63V25.23C37 21.12 30.16 18 24 18zm0 12c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"></path></g> <g id="chevron-right"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path></g> <g id="clear"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></g> <g id="close"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></g> @@ -55,6 +55,7 @@ blurry at 20 px). Please use 20 px icons when available. suffix prevents the naming conflict. --> <g id="settings_icon"><path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"></path></g> <g id="star"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"></path></g> + <g id="supervisor-account" viewBox="0 0 48 48"><path d="M0 0h48v48H0z" fill="none"></path><path d="M33 24c2.76 0 4.98-2.24 4.98-5s-2.22-5-4.98-5c-2.76 0-5 2.24-5 5s2.24 5 5 5zm-15-2c3.31 0 5.98-2.69 5.98-6s-2.67-6-5.98-6c-3.31 0-6 2.69-6 6s2.69 6 6 6zm15 6c-3.67 0-11 1.84-11 5.5V38h22v-4.5c0-3.66-7.33-5.5-11-5.5zm-15-2c-4.67 0-14 2.34-14 7v5h14v-4.5c0-1.7.67-4.67 4.74-6.94C21 26.19 19.31 26 18 26z"></path></g> <g id="warning"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"></path></g> </defs> </svg> diff --git a/chromium/ui/webui/resources/cr_elements/paper_checkbox_style_css.html b/chromium/ui/webui/resources/cr_elements/paper_checkbox_style_css.html new file mode 100644 index 00000000000..6aab514b762 --- /dev/null +++ b/chromium/ui/webui/resources/cr_elements/paper_checkbox_style_css.html @@ -0,0 +1,19 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<!-- Common paper-checkbox styling for Material Design WebUI. --> +<dom-module id="paper-checkbox-style"> + <template> + <style> + paper-checkbox { + --paper-checkbox-checked-color: var(--google-blue-500); + --paper-checkbox-ink-size: 40px; + --paper-checkbox-label-color: inherit; + --paper-checkbox-label-spacing: 20px; + --paper-checkbox-size: 16px; + --paper-checkbox-unchecked-color: var(--paper-grey-600); + -webkit-margin-start: 2px; + } + </style> + </template> +</dom-module> + diff --git a/chromium/ui/webui/resources/cr_elements/paper_toggle_style_css.html b/chromium/ui/webui/resources/cr_elements/paper_toggle_style_css.html new file mode 100644 index 00000000000..95581fd1707 --- /dev/null +++ b/chromium/ui/webui/resources/cr_elements/paper_toggle_style_css.html @@ -0,0 +1,42 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> + +<!-- Common paper-button styling for Material Design WebUI. --> +<dom-module id="paper-toggle-style"> + <template> + <style> + :root { + --cr-toggle-bar-size: { + height: 12px; + left: 4px; + width: 28px; + }; + --cr-toggle-button-size: { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4); + height: 16px; + top: -2px; + width: 16px; + }; + --cr-toggle-ink-size: { + height: 40px; + top: -12px; + left: -12px; + width: 40px; + }; + + --paper-toggle-button-checked-bar: var(--cr-toggle-bar-size); + --paper-toggle-button-checked-bar-color: var(--cr-toggle-color); + --paper-toggle-button-checked-button: { + @apply(--cr-toggle-button-size); + transform: translate(18px, 0); + }; + --paper-toggle-button-checked-button-color: var(--cr-toggle-color); + --paper-toggle-button-label-spacing: 0; + --paper-toggle-button-unchecked-bar: var(--cr-toggle-bar-size); + --paper-toggle-button-unchecked-button: var(--cr-toggle-button-size); + --paper-toggle-button-unchecked-ink: var(--cr-toggle-ink-size); + }; + </style> + </template> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_elements/policy/compiled_resources2.gyp b/chromium/ui/webui/resources/cr_elements/policy/compiled_resources2.gyp index 357d75a6a0d..8b6dede4445 100644 --- a/chromium/ui/webui/resources/cr_elements/policy/compiled_resources2.gyp +++ b/chromium/ui/webui/resources/cr_elements/policy/compiled_resources2.gyp @@ -51,5 +51,10 @@ ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, + { + 'target_name': 'cr_tooltip_icon', + 'dependencies': [], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, ], } diff --git a/chromium/ui/webui/resources/cr_elements/policy/cr_policy_indicator.html b/chromium/ui/webui/resources/cr_elements/policy/cr_policy_indicator.html index 0bb096776f1..41b5170eff8 100644 --- a/chromium/ui/webui/resources/cr_elements/policy/cr_policy_indicator.html +++ b/chromium/ui/webui/resources/cr_elements/policy/cr_policy_indicator.html @@ -1,29 +1,16 @@ -<link rel="import" href="chrome://resources/cr_elements/icons.html"> -<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-tooltip/paper-tooltip.html"> + +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="cr_policy_indicator_behavior.html"> -<link rel="import" href="cr_policy_vars_css.html"> +<link rel="import" href="cr_tooltip_icon.html"> <dom-module id="cr-policy-indicator"> <template> - <style include="cr-hidden-style"> - :host { - @apply(--cr-policy-indicator); - } - - paper-tooltip { - --paper-tooltip: var(--cr-policy-tooltip); - } - </style> - <iron-icon id="indicator" tabindex="0" aria-label$="[[iconAriaLabel]]" - aria-describedby="tooltip" hidden$="[[!indicatorVisible]]" - icon="[[indicatorIcon]]"></iron-icon> - <paper-tooltip id="tooltip" for="indicator" position="top" - fit-to-visible-bounds> - [[indicatorTooltip]] - </paper-tooltip> + <style include="cr-hidden-style"></style> + <cr-tooltip-icon hidden$="[[!indicatorVisible]]" + tooltip-text="[[indicatorTooltip]]" icon-class="[[indicatorIcon]]" + icon-aria-label="[[iconAriaLabel]]"> + </cr-tooltip-icon> </template> <script src="cr_policy_indicator.js"></script> </dom-module> diff --git a/chromium/ui/webui/resources/cr_elements/policy/cr_policy_network_indicator.html b/chromium/ui/webui/resources/cr_elements/policy/cr_policy_network_indicator.html index 70082b7a034..0994787a73a 100644 --- a/chromium/ui/webui/resources/cr_elements/policy/cr_policy_network_indicator.html +++ b/chromium/ui/webui/resources/cr_elements/policy/cr_policy_network_indicator.html @@ -1,30 +1,16 @@ -<link rel="import" href="chrome://resources/cr_elements/icons.html"> -<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-tooltip/paper-tooltip.html"> + +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="cr_policy_indicator_behavior.html"> <link rel="import" href="cr_policy_network_behavior.html"> -<link rel="import" href="cr_policy_vars_css.html"> +<link rel="import" href="cr_tooltip_icon.html"> <dom-module id="cr-policy-network-indicator"> <template> - <style include="cr-hidden-style"> - :host { - @apply(--cr-policy-indicator); - } - - paper-tooltip { - --paper-tooltip: var(--cr-policy-tooltip); - } - </style> - <iron-icon id="indicator" tabindex="0" aria-describedby="tooltip" - hidden$="[[!indicatorVisible]]" icon="[[indicatorIcon]]"> - </iron-icon> - <paper-tooltip id="tooltip" for="indicator" position="top" - fit-to-visible-bounds> - [[indicatorTooltip]] - </paper-tooltip> + <style include="cr-hidden-style"></style> + <cr-tooltip-icon hidden$="[[!indicatorVisible]]" + tooltip-text="[[indicatorTooltip]]" icon-class="[[indicatorIcon]]"> + </cr-tooltip-icon> </template> <script src="cr_policy_network_indicator.js"></script> </dom-module> diff --git a/chromium/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.html b/chromium/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.html index dd33e679cfb..8758980f279 100644 --- a/chromium/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.html +++ b/chromium/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.html @@ -1,29 +1,16 @@ -<link rel="import" href="chrome://resources/cr_elements/icons.html"> -<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/paper-tooltip/paper-tooltip.html"> + +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="cr_policy_indicator_behavior.html"> -<link rel="import" href="cr_policy_vars_css.html"> +<link rel="import" href="cr_tooltip_icon.html"> <dom-module id="cr-policy-pref-indicator"> <template> - <style include="cr-hidden-style"> - :host { - @apply(--cr-policy-indicator); - } - - paper-tooltip { - --paper-tooltip: var(--cr-policy-tooltip); - } - </style> - <iron-icon id="indicator" tabindex="0" aria-label$="[[iconAriaLabel]]" - aria-describedby="tooltip" hidden$="[[!indicatorVisible]]" - icon="[[indicatorIcon]]"></iron-icon> - <paper-tooltip id="tooltip" for="indicator" position="top" - fit-to-visible-bounds> - [[indicatorTooltip]] - </paper-tooltip> + <style include="cr-hidden-style"></style> + <cr-tooltip-icon hidden$="[[!indicatorVisible]]" + tooltip-text="[[indicatorTooltip]]" icon-class="[[indicatorIcon]]" + icon-aria-label="[[iconAriaLabel]]"> + </cr-tooltip-icon> </template> <script src="cr_policy_pref_indicator.js"></script> </dom-module> diff --git a/chromium/ui/webui/resources/cr_elements/policy/cr_policy_vars_css.html b/chromium/ui/webui/resources/cr_elements/policy/cr_policy_vars_css.html deleted file mode 100644 index c16dd6503b8..00000000000 --- a/chromium/ui/webui/resources/cr_elements/policy/cr_policy_vars_css.html +++ /dev/null @@ -1,17 +0,0 @@ -<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> -<style is="custom-style"> - :root { - --cr-policy-indicator: { - @apply(--cr-icon-height-width); - display: flex; /* Position independently from the line-height. */ - }; - - --cr-policy-tooltip: { - font-size: 92.31%; /* Effectively 12px if the host default is 13px. */ - font-weight: 500; - max-width: 330px; - min-width: 200px; - padding: 10px 8px; - }; - } -</style> diff --git a/chromium/ui/webui/resources/cr_elements/policy/cr_tooltip_icon.html b/chromium/ui/webui/resources/cr_elements/policy/cr_tooltip_icon.html new file mode 100644 index 00000000000..c9a1587bf87 --- /dev/null +++ b/chromium/ui/webui/resources/cr_elements/policy/cr_tooltip_icon.html @@ -0,0 +1,34 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/icons.html"> +<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-tooltip/paper-tooltip.html"> + +<dom-module id="cr-tooltip-icon"> + <template> + <style> + :host { + @apply(--cr-icon-height-width); + display: flex; /* Position independently from the line-height. */ + } + + paper-tooltip { + --paper-tooltip: { + font-size: 92.31%; /* Effectively 12px if the host default is 13px. */ + font-weight: 500; + max-width: 330px; + min-width: 200px; + padding: 10px 8px; + }; + } + </style> + <iron-icon id="indicator" tabindex="0" aria-label$="[[iconAriaLabel]]" + aria-describedby="tooltip" icon="[[iconClass]]"></iron-icon> + <paper-tooltip id="tooltip" for="indicator" position="top" + fit-to-visible-bounds> + [[tooltipText]] + </paper-tooltip> + </template> + <script src="cr_tooltip_icon.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_elements/policy/cr_tooltip_icon.js b/chromium/ui/webui/resources/cr_elements/policy/cr_tooltip_icon.js new file mode 100644 index 00000000000..ba918fb0ca7 --- /dev/null +++ b/chromium/ui/webui/resources/cr_elements/policy/cr_tooltip_icon.js @@ -0,0 +1,12 @@ +// Copyright 2017 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. + +Polymer({ + is: 'cr-tooltip-icon', + properties: { + iconAriaLabel: String, + iconClass: String, + tooltipText: String, + }, +});
\ No newline at end of file diff --git a/chromium/ui/webui/resources/cr_elements/shared_vars_css.html b/chromium/ui/webui/resources/cr_elements/shared_vars_css.html index 224a94768a6..2352c441aed 100644 --- a/chromium/ui/webui/resources/cr_elements/shared_vars_css.html +++ b/chromium/ui/webui/resources/cr_elements/shared_vars_css.html @@ -37,12 +37,43 @@ -webkit-margin-start: var(--cr-icon-button-margin-start); } + --cr-primary-text: { + color: var(--paper-grey-900); + line-height: 154%; /* 20px. */ + } + + --cr-secondary-text: { + color: var(--paper-grey-600); + font-weight: 400; + } + + /* TODO (scottchen): re-implement with paddings instead; */ + /* These are used for row items such as radio buttons, check boxes, list + * items etc. */ + --cr-section-min-height: 48px; + --cr-section-two-line-min-height: 64px; + --cr-section-three-line-min-height: 84px; + + --cr-section-padding: 20px; + --cr-section-indent-width: 40px; + --cr-section-indent-padding: calc( + var(--cr-section-padding) + var(--cr-section-indent-width)); + + --cr-section: { + align-items: center; + border-top: var(--cr-separator-line); + display: flex; + min-height: var(--cr-section-min-height); + padding: 0 var(--cr-section-padding); + }; + + --cr-toggle-color: var(--google-blue-500); + --cr-selectable-focus: { background-color: var(--cr-focused-item-color); outline: none; } --cr-separator-height: 1px; --cr-separator-line: var(--cr-separator-height) solid rgba(0, 0, 0, 0.06); - --paper-checkbox-ink-size: 40px; } </style> diff --git a/chromium/ui/webui/resources/cr_elements_images.grdp b/chromium/ui/webui/resources/cr_elements_images.grdp index a5ec224b864..23265241ab1 100644 --- a/chromium/ui/webui/resources/cr_elements_images.grdp +++ b/chromium/ui/webui/resources/cr_elements_images.grdp @@ -9,6 +9,8 @@ file="images/arrow_right.svg" type="BINDATA" /> <include name="IDR_WEBUI_IMAGES_OPEN_IN_NEW" file="images/open_in_new.svg" type="BINDATA" /> + <include name="IDR_WEBUI_IMAGES_ICON_ARROW_BACK" + file="images/icon_arrow_back.svg" type="BINDATA" /> <include name="IDR_WEBUI_IMAGES_ICON_CANCEL" file="images/icon_cancel.svg" type="BINDATA" /> <include name="IDR_WEBUI_IMAGES_ICON_CANCEL_TOOLBAR" @@ -25,8 +27,12 @@ file="images/icon_expand_more.svg" type="BINDATA" /> <include name="IDR_WEBUI_IMAGES_ICON_EXTERNAL" file="images/open_in_new.svg" type="BINDATA" /> + <include name="IDR_WEBUI_IMAGES_ICON_MENU_WHITE" + file="images/icon_menu_white.svg" type="BINDATA" /> <include name="IDR_WEBUI_IMAGES_ICON_MORE_VERT" file="images/icon_more_vert.svg" type="BINDATA" /> + <include name="IDR_WEBUI_IMAGES_ICON_REFRESH" + file="images/icon_refresh.svg" type="BINDATA" /> <include name="IDR_WEBUI_IMAGES_ICON_SETTINGS" file="images/icon_settings.svg" type="BINDATA" /> <include name="IDR_WEBUI_IMAGES_ICON_SEARCH" diff --git a/chromium/ui/webui/resources/cr_elements_resources.grdp b/chromium/ui/webui/resources/cr_elements_resources.grdp index d9db27b42bd..d7f4a170287 100644 --- a/chromium/ui/webui/resources/cr_elements_resources.grdp +++ b/chromium/ui/webui/resources/cr_elements_resources.grdp @@ -41,6 +41,12 @@ <structure name="IDR_CR_ELEMENTS_CR_LAZY_RENDER_JS" file="../../webui/resources/cr_elements/cr_lazy_render/cr_lazy_render.js" type="chrome_html" /> + <structure name="IDR_CR_ELEMENTS_CR_LINK_ROW_HTML" + file="../../webui/resources/cr_elements/cr_link_row/cr_link_row.html" + type="chrome_html" /> + <structure name="IDR_CR_ELEMENTS_CR_LINK_ROW_JS" + file="../../webui/resources/cr_elements/cr_link_row/cr_link_row.js" + type="chrome_html" /> <if expr="chromeos"> <structure name="IDR_CR_ELEMENTS_CHROMEOS_CR_PICTURE_CR_CAMERA_HTML" file="../../webui/resources/cr_elements/chromeos/cr_picture/cr_camera.html" @@ -145,8 +151,11 @@ <structure name="IDR_CR_ELEMENTS_CR_POLICY_PREF_INDICATOR_HTML" file="../../webui/resources/cr_elements/policy/cr_policy_pref_indicator.html" type="chrome_html" /> - <structure name="IDR_CR_ELEMENTS_CR_POLICY_VARS_CSS_HTML" - file="../../webui/resources/cr_elements/policy/cr_policy_vars_css.html" + <structure name="IDR_CR_ELEMENTS_CR_TOOLTIP_ICON_HTML" + file="../../webui/resources/cr_elements/policy/cr_tooltip_icon.html" + type="chrome_html" /> + <structure name="IDR_CR_ELEMENTS_CR_TOOLTIP_ICON_JS" + file="../../webui/resources/cr_elements/policy/cr_tooltip_icon.js" type="chrome_html" /> <structure name="IDR_CR_ELEMENTS_CR_PROFILE_AVATAR_SELECTOR_HTML" file="../../webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.html" @@ -179,6 +188,12 @@ <structure name="IDR_CR_ELEMENTS_PAPER_BUTTON_STYLE_CSS_HTML" file="../../webui/resources/cr_elements/paper_button_style_css.html" type="chrome_html" /> + <structure name="IDR_CR_ELEMENTS_PAPER_CHECKBOX_STYLE_CSS_HTML" + file="../../webui/resources/cr_elements/paper_checkbox_style_css.html" + type="chrome_html" /> + <structure name="IDR_CR_ELEMENTS_PAPER_TOGGLE_STYLE_CSS_HTML" + file="../../webui/resources/cr_elements/paper_toggle_style_css.html" + type="chrome_html" /> <structure name="IDR_CR_ELEMENTS_CR_SHARED_VARS_CSS_HTML" file="../../webui/resources/cr_elements/shared_vars_css.html" type="chrome_html" /> diff --git a/chromium/ui/webui/resources/css/chrome_shared.css b/chromium/ui/webui/resources/css/chrome_shared.css index acca2c1cd22..95ef34d5564 100644 --- a/chromium/ui/webui/resources/css/chrome_shared.css +++ b/chromium/ui/webui/resources/css/chrome_shared.css @@ -8,9 +8,6 @@ @import url(chrome://resources/css/text_defaults.css); @import url(i18n_process.css); @import url(widgets.css); -<if expr="chromeos"> -@import url(chromeos/ui_account_tweaks.css); -</if> /* Prevent CSS from overriding the hidden property. */ [hidden] { diff --git a/chromium/ui/webui/resources/css/chromeos/ui_account_tweaks.css b/chromium/ui/webui/resources/css/chromeos/ui_account_tweaks.css deleted file mode 100644 index 91ae0699729..00000000000 --- a/chromium/ui/webui/resources/css/chromeos/ui_account_tweaks.css +++ /dev/null @@ -1,14 +0,0 @@ -/* 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. */ - -/* This file defines styles for internal page elements that have special - * look and feel based on account status (owner/non-owner/guest). */ - -.guest-disabled { - color: #999; -} - -a.guest-disabled { - opacity: 0.75; -} diff --git a/chromium/ui/webui/resources/css/widgets.css b/chromium/ui/webui/resources/css/widgets.css index 997bfc04794..419304710cf 100644 --- a/chromium/ui/webui/resources/css/widgets.css +++ b/chromium/ui/webui/resources/css/widgets.css @@ -249,7 +249,7 @@ input:disabled:-webkit-any([type='password'], * * <div class="checkbox"> * <label> - * <input type="checkbox"></input> + * <input type="checkbox"> * <span> * </label> * </div> diff --git a/chromium/ui/webui/resources/html/chromeos/ui_account_tweaks.html b/chromium/ui/webui/resources/html/chromeos/ui_account_tweaks.html deleted file mode 100644 index 10b6db6d7e4..00000000000 --- a/chromium/ui/webui/resources/html/chromeos/ui_account_tweaks.html +++ /dev/null @@ -1 +0,0 @@ -<script src="chrome://resources/js/chromeos/ui_account_tweaks.js"></script> diff --git a/chromium/ui/webui/resources/images/icon_arrow_back.svg b/chromium/ui/webui/resources/images/icon_arrow_back.svg new file mode 100644 index 00000000000..838eb43618d --- /dev/null +++ b/chromium/ui/webui/resources/images/icon_arrow_back.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#757575" preserveAspectRatio="xMidYMid meet"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"></path></svg>
\ No newline at end of file diff --git a/chromium/ui/webui/resources/images/icon_menu_white.svg b/chromium/ui/webui/resources/images/icon_menu_white.svg new file mode 100644 index 00000000000..aab80273728 --- /dev/null +++ b/chromium/ui/webui/resources/images/icon_menu_white.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="#fff" preserveAspectRatio="xMidYMid meet"><rect x="2" y="4" width="16" height="2"></rect><rect x="2" y="9" width="16" height="2"></rect><rect x="2" y="14" width="16" height="2"></rect></svg>
\ No newline at end of file diff --git a/chromium/ui/webui/resources/images/icon_refresh.svg b/chromium/ui/webui/resources/images/icon_refresh.svg new file mode 100644 index 00000000000..d7f66ccf991 --- /dev/null +++ b/chromium/ui/webui/resources/images/icon_refresh.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#757575" preserveAspectRatio="xMidYMid meet"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"></path></svg>
\ No newline at end of file diff --git a/chromium/ui/webui/resources/images/info.svg b/chromium/ui/webui/resources/images/info.svg new file mode 100644 index 00000000000..e76e2476640 --- /dev/null +++ b/chromium/ui/webui/resources/images/info.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 48 48" fill="#757575"> + <path d="M0 0h48v48H0z" fill="none"/> + <path d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm2 30h-4V22h4v12zm0-16h-4v-4h4v4z"/> +</svg> diff --git a/chromium/ui/webui/resources/js/chromeos/ui_account_tweaks.js b/chromium/ui/webui/resources/js/chromeos/ui_account_tweaks.js deleted file mode 100644 index 1aa912312cb..00000000000 --- a/chromium/ui/webui/resources/js/chromeos/ui_account_tweaks.js +++ /dev/null @@ -1,178 +0,0 @@ -// 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. - -/** - * @fileoverview This file contains methods that allow to tweak - * internal page UI based on the status of current user (owner/user/guest). - * It is assumed that required data is passed via i18n strings - * (using loadTimeData dictionary) that are filled with call to - * AddAccountUITweaksLocalizedValues in ui_account_tweaks.cc. - * It is also assumed that tweaked page has chrome://resources/css/widgets.css - * included. - */ - -cr.define('uiAccountTweaks', function() { - - ///////////////////////////////////////////////////////////////////////////// - // UIAccountTweaks class: - - // String specificators for different types of sessions. - /** @const */ var SESSION_TYPE_GUEST = 'guest'; - /** @const */ var SESSION_TYPE_PUBLIC = 'public-account'; - - /** - * Encapsulated handling of ChromeOS accounts options page. - * @constructor - */ - function UIAccountTweaks() {} - - /** - * @return {boolean} Whether the current user is owner or not. - */ - UIAccountTweaks.currentUserIsOwner = function() { - return loadTimeData.getBoolean('currentUserIsOwner'); - }; - - /** - * @return {boolean} Whether we're currently in guest session. - */ - UIAccountTweaks.loggedInAsGuest = function() { - return loadTimeData.getBoolean('loggedInAsGuest'); - }; - - /** - * @return {boolean} Whether we're currently in public session. - */ - UIAccountTweaks.loggedInAsPublicAccount = function() { - return loadTimeData.getBoolean('loggedInAsPublicAccount'); - }; - - /** - * @return {boolean} Whether we're currently in supervised user mode. - */ - UIAccountTweaks.loggedInAsSupervisedUser = function() { - return loadTimeData.getBoolean('loggedInAsSupervisedUser'); - }; - - /** - * Enables an element unless it should be disabled for the session type. - * - * @param {!Element} element Element that should be enabled. - */ - UIAccountTweaks.enableElementIfPossible = function(element) { - var sessionType; - if (UIAccountTweaks.loggedInAsGuest()) - sessionType = SESSION_TYPE_GUEST; - else if (UIAccountTweaks.loggedInAsPublicAccount()) - sessionType = SESSION_TYPE_PUBLIC; - - if (sessionType && - element.getAttribute(sessionType + '-visibility') == 'disabled') { - return; - } - - element.disabled = false; - }; - - /** - * Disables or hides some elements in specified type of session in ChromeOS. - * All elements within given document with *sessionType*-visibility - * attribute are either hidden (for *sessionType*-visibility="hidden") - * or disabled (for *sessionType*-visibility="disabled"). - * - * @param {Document} document Document that should processed. - * @param {string} sessionType name of the session type processed. - * @private - */ - UIAccountTweaks.applySessionTypeVisibility_ = function( - document, sessionType) { - var elements = - document.querySelectorAll('[' + sessionType + '-visibility]'); - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; - var visibility = element.getAttribute(sessionType + '-visibility'); - if (visibility == 'hidden') - element.hidden = true; - else if (visibility == 'disabled') - UIAccountTweaks.disableElementsForSessionType(element, sessionType); - } - }; - - /** - * Updates specific visibility of elements for Guest session in ChromeOS. - * Calls applySessionTypeVisibility_ method. - * - * @param {Document} document Document that should processed. - */ - UIAccountTweaks.applyGuestSessionVisibility = function(document) { - if (!UIAccountTweaks.loggedInAsGuest()) - return; - UIAccountTweaks.applySessionTypeVisibility_(document, SESSION_TYPE_GUEST); - }; - - /** - * Updates specific visibility of elements for Public account session in - * ChromeOS. Calls applySessionTypeVisibility_ method. - * - * @param {Document} document Document that should processed. - */ - UIAccountTweaks.applyPublicSessionVisibility = function(document) { - if (!UIAccountTweaks.loggedInAsPublicAccount()) - return; - UIAccountTweaks.applySessionTypeVisibility_(document, SESSION_TYPE_PUBLIC); - }; - - /** - * Disables and marks page elements for specified session type. - * Adds #-disabled css class to all elements within given subtree, - * disables interactive elements (input/select/button), and removes href - * attribute from <a> elements. - * - * @param {!Element} element Root element of DOM subtree that should be - * disabled. - * @param {string} sessionType session type specificator. - */ - UIAccountTweaks.disableElementsForSessionType = function( - element, sessionType) { - UIAccountTweaks.disableElementForSessionType_(element, sessionType); - - // Walk the tree, searching each ELEMENT node. - var walker = document.createTreeWalker( - element, NodeFilter.SHOW_ELEMENT, null, false); - - var node = walker.nextNode(); - while (node) { - UIAccountTweaks.disableElementForSessionType_( - /** @type {!Element} */ (node), sessionType); - node = walker.nextNode(); - } - }; - - /** - * Disables single element for given session type. - * Adds *sessionType*-disabled css class, adds disabled attribute for - * appropriate elements (input/select/button), and removes href attribute from - * <a> element. - * - * @private - * @param {!Element} element Element that should be disabled. - * @param {string} sessionType account session Type specificator. - */ - UIAccountTweaks.disableElementForSessionType_ = function( - element, sessionType) { - element.classList.add(sessionType + '-disabled'); - if (element.nodeName == 'INPUT' || element.nodeName == 'SELECT' || - element.nodeName == 'BUTTON') { - element.disabled = true; - } else if (element.nodeName == 'A') { - element.onclick = function() { - return false; - }; - } - }; - - // Export - return {UIAccountTweaks: UIAccountTweaks}; - -}); diff --git a/chromium/ui/webui/resources/js/cr/ui/menu.js b/chromium/ui/webui/resources/js/cr/ui/menu.js index 487ff8553c9..ed00ed84f6a 100644 --- a/chromium/ui/webui/resources/js/cr/ui/menu.js +++ b/chromium/ui/webui/resources/js/cr/ui/menu.js @@ -78,6 +78,7 @@ cr.define('cr.ui', function() { * Clears menu. */ clear: function() { + this.selectedItem = null; this.textContent = ''; }, diff --git a/chromium/ui/webui/resources/webui_resources.grd b/chromium/ui/webui/resources/webui_resources.grd index 24f7ea9c074..e8eb478d2a4 100644 --- a/chromium/ui/webui/resources/webui_resources.grd +++ b/chromium/ui/webui/resources/webui_resources.grd @@ -496,18 +496,8 @@ without changes to the corresponding grd file. --> file="js/web_ui_listener_behavior.js" type="chrome_html" /> <structure name="IDR_WEBUI_JS_WEBUI_RESOURCE_TEST" file="js/webui_resource_test.js" type="chrome_html" /> - <if expr="chromeos"> - <structure name="IDR_WEBUI_CSS_UI_ACCOUNT_TWEAKS" - file="css/chromeos/ui_account_tweaks.css" - type="chrome_html" /> - <structure name="IDR_WEBUI_HTML_UI_ACCOUNT_TWEAKS" - file="html/chromeos/ui_account_tweaks.html" - type="chrome_html" /> - <structure name="IDR_WEBUI_JS_UI_ACCOUNT_TWEAKS" - file="js/chromeos/ui_account_tweaks.js" - type="chrome_html" /> - </if> <if expr="not is_android"> + <part file="cr_components_resources.grdp" /> <part file="cr_elements_resources.grdp" /> <part file="polymer_resources.grdp" /> </if> |