Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 <include src="extension_error.js"> | 5 <include src="extension_error.js"> |
| 6 | 6 |
| 7 /////////////////////////////////////////////////////////////////////////////// | 7 /////////////////////////////////////////////////////////////////////////////// |
| 8 // ExtensionFocusRow: | 8 // ExtensionFocusRow: |
| 9 | 9 |
| 10 /** | 10 /** |
| (...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 137 | 137 |
| 138 cr.define('extensions', function() { | 138 cr.define('extensions', function() { |
| 139 'use strict'; | 139 'use strict'; |
| 140 | 140 |
| 141 /** | 141 /** |
| 142 * Creates a new list of extensions. | 142 * Creates a new list of extensions. |
| 143 * @param {Object=} opt_propertyBag Optional properties. | 143 * @param {Object=} opt_propertyBag Optional properties. |
| 144 * @constructor | 144 * @constructor |
| 145 * @extends {HTMLDivElement} | 145 * @extends {HTMLDivElement} |
| 146 */ | 146 */ |
| 147 var ExtensionList = cr.ui.define('div'); | 147 var ExtensionList = cr.ui.define('div'); |
|
Dan Beam
2015/03/21 01:44:20
if cr.ui.define() is limiting, do this instead
fu
Devlin
2015/03/21 04:54:00
Done.
| |
| 148 | 148 |
| 149 /** | 149 /** |
| 150 * @type {Object<string, number>} A map from extension id to last reloaded | 150 * @type {Object<string, number>} A map from extension id to last reloaded |
| 151 * timestamp. The timestamp is recorded when the user click the 'Reload' | 151 * timestamp. The timestamp is recorded when the user click the 'Reload' |
| 152 * link. It is used to refresh the icon of an unpacked extension. | 152 * link. It is used to refresh the icon of an unpacked extension. |
| 153 * This persists between calls to decorate. | 153 * This persists between calls to decorate. |
| 154 */ | 154 */ |
| 155 var extensionReloadedTimestamp = {}; | 155 var extensionReloadedTimestamp = {}; |
| 156 | 156 |
| 157 ExtensionList.prototype = { | 157 ExtensionList.prototype = { |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 181 * @private {boolean} | 181 * @private {boolean} |
| 182 */ | 182 */ |
| 183 permissionsPromptIsShowing_: false, | 183 permissionsPromptIsShowing_: false, |
| 184 | 184 |
| 185 /** | 185 /** |
| 186 * Necessary to only show the butterbar once. | 186 * Necessary to only show the butterbar once. |
| 187 * @private {boolean} | 187 * @private {boolean} |
| 188 */ | 188 */ |
| 189 butterbarShown_: false, | 189 butterbarShown_: false, |
| 190 | 190 |
| 191 /** | |
| 192 * Whether or not incognito mode is available. | |
| 193 * @private {boolean} | |
| 194 */ | |
| 195 incognitoAvailable_: false, | |
| 196 | |
| 197 /** | |
| 198 * Whether or not the app info dialog is enabled. | |
| 199 * @private {boolean} | |
| 200 */ | |
| 201 enableAppInfoDialog_: false, | |
| 202 | |
| 203 /** Needed because we use cr.ui.define. */ | |
|
Dan Beam
2015/03/21 01:44:20
see above
Devlin
2015/03/21 04:54:00
Done.
| |
| 191 decorate: function() { | 204 decorate: function() { |
| 192 chrome.developerPrivate.getExtensionsInfo( | 205 /** @private {!Array<ExtensionInfo>} */ |
| 193 {includeDisabled: true, includeTerminated: true}, | 206 this.extensions_ = []; |
| 194 function(extensions) { | 207 }, |
| 195 // Sort in order of unpacked vs. packed, followed by name, followed by | 208 |
| 196 // id. | 209 /** |
| 197 extensions.sort(function(a, b) { | 210 * Updates the extensions on the page. |
| 198 function compare(x, y) { | 211 * @param {boolean} incognitoAvailable Whether or not incognito is allowed. |
| 199 return x < y ? -1 : (x > y ? 1 : 0); | 212 * @param {boolean} enableAppInfoDialog Whether or not the app info dialog |
| 200 } | 213 * is enabled. |
| 201 function compareLocation(x, y) { | 214 * @return {Promise} A promise that is resolved once the extensions data is |
| 202 return x.location == chrome.developerPrivate.Location.UNPACKED ? | 215 * fully updated. |
| 203 -1 : (x.location == y.location ? 0 : 1); | 216 */ |
| 204 } | 217 updateExtensionsData: function(incognitoAvailable, enableAppInfoDialog) { |
| 205 return compareLocation(a, b) || | 218 // If we start to need more information about the extension configuration, |
| 206 compare(a.name.toLowerCase(), b.name.toLowerCase()) || | 219 // consider passing in the full object from the ExtensionSettings. |
| 207 compare(a.id, b.id); | 220 this.incognitoAvailable_ = incognitoAvailable; |
| 208 }); | 221 this.enableAppInfoDialog_ = enableAppInfoDialog; |
| 209 this.extensions_ = extensions; | 222 return new Promise(function(resolve, reject) { |
| 210 this.showExtensionNodes_(); | 223 chrome.developerPrivate.getExtensionsInfo( |
| 224 {includeDisabled: true, includeTerminated: true}, | |
| 225 function(extensions) { | |
| 226 // Sort in order of unpacked vs. packed, followed by name, followed by | |
| 227 // id. | |
| 228 extensions.sort(function(a, b) { | |
| 229 function compare(x, y) { | |
| 230 return x < y ? -1 : (x > y ? 1 : 0); | |
| 231 } | |
| 232 function compareLocation(x, y) { | |
| 233 return x.location == chrome.developerPrivate.Location.UNPACKED ? | |
| 234 -1 : (x.location == y.location ? 0 : 1); | |
| 235 } | |
| 236 return compareLocation(a, b) || | |
| 237 compare(a.name.toLowerCase(), b.name.toLowerCase()) || | |
| 238 compare(a.id, b.id); | |
| 239 }); | |
| 240 this.extensions_ = extensions; | |
| 241 this.showExtensionNodes_(); | |
| 242 resolve(); | |
| 243 }.bind(this)); | |
| 211 }.bind(this)); | 244 }.bind(this)); |
| 212 }, | 245 }, |
| 213 | 246 |
| 247 /** @return {number} The number of extensions being displayed. */ | |
| 248 getNumExtensions: function() { | |
| 249 return this.extensions_.length; | |
| 250 }, | |
| 251 | |
| 214 getIdQueryParam_: function() { | 252 getIdQueryParam_: function() { |
| 215 return parseQueryParams(document.location)['id']; | 253 return parseQueryParams(document.location)['id']; |
| 216 }, | 254 }, |
| 217 | 255 |
| 218 getOptionsQueryParam_: function() { | 256 getOptionsQueryParam_: function() { |
| 219 return parseQueryParams(document.location)['options']; | 257 return parseQueryParams(document.location)['options']; |
| 220 }, | 258 }, |
| 221 | 259 |
| 222 /** | 260 /** |
| 223 * Creates or updates all extension items from scratch. | 261 * Creates or updates all extension items from scratch. |
| 224 * @private | 262 * @private |
| 225 */ | 263 */ |
| 226 showExtensionNodes_: function() { | 264 showExtensionNodes_: function() { |
| 227 // Remove the rows from |focusGrid_| without destroying them. | 265 // Remove the rows from |focusGrid_| without destroying them. |
| 228 this.focusGrid_.rows.length = 0; | 266 this.focusGrid_.rows.length = 0; |
| 229 | 267 |
| 230 // Any node that is not updated will be removed. | 268 // Any node that is not updated will be removed. |
| 231 var seenIds = []; | 269 var seenIds = []; |
| 232 | 270 |
| 233 // Iterate over the extension data and add each item to the list. | 271 // Iterate over the extension data and add each item to the list. |
| 234 this.extensions_.forEach(function(extension, i) { | 272 this.extensions_.forEach(function(extension, i) { |
| 235 var nextExt = this.extensions_[i + 1]; | 273 var nextExt = this.extensions_[i + 1]; |
| 236 var node = $(extension.id); | 274 var node = /** @type {ExtensionFocusRow} */ ($(extension.id)); |
| 237 seenIds.push(extension.id); | 275 seenIds.push(extension.id); |
| 238 | 276 |
| 239 if (node) | 277 if (node) |
| 240 this.updateNode_(extension, node); | 278 this.updateNode_(extension, node); |
| 241 else | 279 else |
| 242 this.createNode_(extension, nextExt ? $(nextExt.id) : null); | 280 this.createNode_(extension, nextExt ? $(nextExt.id) : null); |
| 243 }, this); | 281 }, this); |
| 244 | 282 |
| 245 // Remove extensions that are no longer installed. | 283 // Remove extensions that are no longer installed. |
| 246 var nodes = document.querySelectorAll('.extension-list-item-wrapper[id]'); | 284 var nodes = document.querySelectorAll('.extension-list-item-wrapper[id]'); |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 261 } | 299 } |
| 262 } | 300 } |
| 263 | 301 |
| 264 var idToHighlight = this.getIdQueryParam_(); | 302 var idToHighlight = this.getIdQueryParam_(); |
| 265 if (idToHighlight && $(idToHighlight)) | 303 if (idToHighlight && $(idToHighlight)) |
| 266 this.scrollToNode_(idToHighlight); | 304 this.scrollToNode_(idToHighlight); |
| 267 | 305 |
| 268 var idToOpenOptions = this.getOptionsQueryParam_(); | 306 var idToOpenOptions = this.getOptionsQueryParam_(); |
| 269 if (idToOpenOptions && $(idToOpenOptions)) | 307 if (idToOpenOptions && $(idToOpenOptions)) |
| 270 this.showEmbeddedExtensionOptions_(idToOpenOptions, true); | 308 this.showEmbeddedExtensionOptions_(idToOpenOptions, true); |
| 271 | |
| 272 var noExtensions = this.extensions_.length == 0; | |
| 273 this.classList.toggle('empty-extension-list', noExtensions); | |
| 274 }, | 309 }, |
| 275 | 310 |
| 276 /** Updates each row's focusable elements without rebuilding the grid. */ | 311 /** Updates each row's focusable elements without rebuilding the grid. */ |
| 277 updateFocusableElements: function() { | 312 updateFocusableElements: function() { |
| 278 var rows = document.querySelectorAll('.extension-list-item-wrapper[id]'); | 313 var rows = document.querySelectorAll('.extension-list-item-wrapper[id]'); |
| 279 for (var i = 0; i < rows.length; ++i) { | 314 for (var i = 0; i < rows.length; ++i) { |
| 280 assertInstanceof(rows[i], ExtensionFocusRow).updateFocusableElements(); | 315 assertInstanceof(rows[i], ExtensionFocusRow).updateFocusableElements(); |
| 281 } | 316 } |
| 282 }, | 317 }, |
| 283 | 318 |
| (...skipping 254 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 538 this.setText_(row, '.location-text', extension.locationText || ''); | 573 this.setText_(row, '.location-text', extension.locationText || ''); |
| 539 this.setText_(row, '.blacklist-text', extension.blacklistText || ''); | 574 this.setText_(row, '.blacklist-text', extension.blacklistText || ''); |
| 540 this.setText_(row, '.extension-description', extension.description); | 575 this.setText_(row, '.extension-description', extension.description); |
| 541 | 576 |
| 542 // The 'Show Browser Action' button. | 577 // The 'Show Browser Action' button. |
| 543 this.updateVisibility_(row, '.show-button', | 578 this.updateVisibility_(row, '.show-button', |
| 544 isActive && extension.actionButtonHidden); | 579 isActive && extension.actionButtonHidden); |
| 545 | 580 |
| 546 // The 'allow in incognito' checkbox. | 581 // The 'allow in incognito' checkbox. |
| 547 this.updateVisibility_(row, '.incognito-control', | 582 this.updateVisibility_(row, '.incognito-control', |
| 548 isActive && this.data_.incognitoAvailable, | 583 isActive && this.incognitoAvailable_, |
| 549 function(item) { | 584 function(item) { |
| 550 var incognito = item.querySelector('input'); | 585 var incognito = item.querySelector('input'); |
| 551 incognito.disabled = !extension.incognitoAccess.isEnabled; | 586 incognito.disabled = !extension.incognitoAccess.isEnabled; |
| 552 incognito.checked = extension.incognitoAccess.isActive; | 587 incognito.checked = extension.incognitoAccess.isActive; |
| 553 }); | 588 }); |
| 554 | 589 |
| 555 // Hide butterBar if incognito is not enabled for the extension. | 590 // Hide butterBar if incognito is not enabled for the extension. |
| 556 var butterBar = row.querySelector('.butter-bar'); | 591 var butterBar = row.querySelector('.butter-bar'); |
| 557 butterBar.hidden = | 592 butterBar.hidden = |
| 558 butterBar.hidden || !extension.incognitoAccess.isEnabled; | 593 butterBar.hidden || !extension.incognitoAccess.isEnabled; |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 587 | 622 |
| 588 // The 'Options' button or link, depending on its behaviour. | 623 // The 'Options' button or link, depending on its behaviour. |
| 589 var optionsEnabled = isActive && !!extension.optionsPage; | 624 var optionsEnabled = isActive && !!extension.optionsPage; |
| 590 this.updateVisibility_(row, '.options-link', optionsEnabled && | 625 this.updateVisibility_(row, '.options-link', optionsEnabled && |
| 591 extension.optionsPage.openInTab); | 626 extension.optionsPage.openInTab); |
| 592 this.updateVisibility_(row, '.options-button', optionsEnabled && | 627 this.updateVisibility_(row, '.options-button', optionsEnabled && |
| 593 !extension.optionsPage.openInTab); | 628 !extension.optionsPage.openInTab); |
| 594 | 629 |
| 595 // The 'View in Web Store/View Web Site' link. | 630 // The 'View in Web Store/View Web Site' link. |
| 596 var siteLinkEnabled = !!extension.homepageUrl && | 631 var siteLinkEnabled = !!extension.homepageUrl && |
| 597 !this.data_.enableAppInfoDialog; | 632 !this.enableAppInfoDialog_; |
| 598 this.updateVisibility_(row, '.site-link', siteLinkEnabled, | 633 this.updateVisibility_(row, '.site-link', siteLinkEnabled, |
| 599 function(item) { | 634 function(item) { |
| 600 item.href = extension.homepageUrl; | 635 item.href = extension.homepageUrl; |
| 601 item.textContent = loadTimeData.getString( | 636 item.textContent = loadTimeData.getString( |
| 602 extension.homepageProvided ? 'extensionSettingsVisitWebsite' : | 637 extension.homepageProvided ? 'extensionSettingsVisitWebsite' : |
| 603 'extensionSettingsVisitWebStore'); | 638 'extensionSettingsVisitWebStore'); |
| 604 }); | 639 }); |
| 605 | 640 |
| 606 var isUnpacked = | 641 var isUnpacked = |
| 607 extension.location == chrome.developerPrivate.Location.UNPACKED; | 642 extension.location == chrome.developerPrivate.Location.UNPACKED; |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 714 } | 749 } |
| 715 if (!dependentExtension) | 750 if (!dependentExtension) |
| 716 return; | 751 return; |
| 717 | 752 |
| 718 var depNode = dependentTemplate.cloneNode(true); | 753 var depNode = dependentTemplate.cloneNode(true); |
| 719 depNode.querySelector('.dep-extension-title').textContent = | 754 depNode.querySelector('.dep-extension-title').textContent = |
| 720 dependentExtension.name; | 755 dependentExtension.name; |
| 721 depNode.querySelector('.dep-extension-id').textContent = | 756 depNode.querySelector('.dep-extension-id').textContent = |
| 722 dependentExtension.id; | 757 dependentExtension.id; |
| 723 dependentList.appendChild(depNode); | 758 dependentList.appendChild(depNode); |
| 724 }); | 759 }, this); |
| 725 }); | 760 }); |
| 726 | 761 |
| 727 // The active views. | 762 // The active views. |
| 728 this.updateVisibility_(row, '.active-views', extension.views.length > 0, | 763 this.updateVisibility_(row, '.active-views', extension.views.length > 0, |
| 729 function(item) { | 764 function(item) { |
| 730 var link = item.querySelector('a'); | 765 var link = item.querySelector('a'); |
| 731 | 766 |
| 732 // Link needs to be an only child before the list is updated. | 767 // Link needs to be an only child before the list is updated. |
| 733 while (link.nextElementSibling) | 768 while (link.nextElementSibling) |
| 734 item.removeChild(link.nextElementSibling); | 769 item.removeChild(link.nextElementSibling); |
| (...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 940 // TODO(dbeam): why do we need to focus <extensionoptions> before and | 975 // TODO(dbeam): why do we need to focus <extensionoptions> before and |
| 941 // after its showing animation? Makes very little sense to me. | 976 // after its showing animation? Makes very little sense to me. |
| 942 overlay.setInitialFocus(); | 977 overlay.setInitialFocus(); |
| 943 }, | 978 }, |
| 944 }; | 979 }; |
| 945 | 980 |
| 946 return { | 981 return { |
| 947 ExtensionList: ExtensionList | 982 ExtensionList: ExtensionList |
| 948 }; | 983 }; |
| 949 }); | 984 }); |
| OLD | NEW |