1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 cr.define('help', function() { 6 var Page = cr.ui.pageManager.Page; 7 var PageManager = cr.ui.pageManager.PageManager; 8 9 /** 10 * Encapsulated handling of the About page. Called 'help' internally to avoid 11 * confusion with generic AboutUI (about:memory, about:sandbox, etc.). 12 */ 13 function HelpPage() { 14 var id = loadTimeData.valueExists('aboutOverlayTabTitle') ? 15 'aboutOverlayTabTitle' : 'aboutTitle'; 16 Page.call(this, 'help', loadTimeData.getString(id), 'help-page'); 17 } 18 19 cr.addSingletonGetter(HelpPage); 20 21 HelpPage.prototype = { 22 __proto__: Page.prototype, 23 24 /** 25 * List of the channel names. Should be ordered in increasing level of 26 * stability. 27 * @private 28 */ 29 channelList_: ['dev-channel', 'beta-channel', 'stable-channel'], 30 31 /** 32 * Name of the channel the device is currently on. 33 * @private 34 */ 35 currentChannel_: null, 36 37 /** 38 * Name of the channel the device is supposed to be on. 39 * @private 40 */ 41 targetChannel_: null, 42 43 /** 44 * Last status received from the version updater. 45 * @private 46 */ 47 status_: null, 48 49 /** 50 * Last message received from the version updater. 51 * @private 52 */ 53 message_: null, 54 55 /** @override */ 56 initializePage: function() { 57 Page.prototype.initializePage.call(this); 58 59 $('product-license').innerHTML = loadTimeData.getString('productLicense'); 60 if (cr.isChromeOS) { 61 $('product-os-license').innerHTML = 62 loadTimeData.getString('productOsLicense'); 63 } 64 65 var productTOS = $('product-tos'); 66 if (productTOS) 67 productTOS.innerHTML = loadTimeData.getString('productTOS'); 68 69 $('get-help').onclick = function() { 70 chrome.send('openHelpPage'); 71 }; 72 <if expr="_google_chrome"> 73 $('report-issue').onclick = function() { 74 chrome.send('openFeedbackDialog'); 75 }; 76 </if> 77 78 this.maybeSetOnClick_($('more-info-expander'), 79 this.toggleMoreInfo_.bind(this)); 80 81 this.maybeSetOnClick_($('promote'), function() { 82 chrome.send('promoteUpdater'); 83 }); 84 this.maybeSetOnClick_($('relaunch'), function() { 85 chrome.send('relaunchNow'); 86 }); 87 if (cr.isChromeOS) { 88 this.maybeSetOnClick_($('relaunch-and-powerwash'), function() { 89 chrome.send('relaunchAndPowerwash'); 90 }); 91 92 this.channelTable_ = { 93 'stable-channel': { 94 'name': loadTimeData.getString('stable'), 95 'label': loadTimeData.getString('currentChannelStable'), 96 }, 97 'beta-channel': { 98 'name': loadTimeData.getString('beta'), 99 'label': loadTimeData.getString('currentChannelBeta') 100 }, 101 'dev-channel': { 102 'name': loadTimeData.getString('dev'), 103 'label': loadTimeData.getString('currentChannelDev') 104 } 105 }; 106 } 107 this.maybeSetOnClick_($('about-done'), function() { 108 // Event listener for the close button when shown as an overlay. 109 PageManager.closeOverlay(); 110 }); 111 112 var self = this; 113 var channelChanger = $('channel-changer'); 114 if (channelChanger) { 115 channelChanger.onchange = function(event) { 116 self.setChannel_(event.target.value, false); 117 }; 118 } 119 120 if (cr.isChromeOS) { 121 // Add event listener for the check for and apply updates button. 122 this.maybeSetOnClick_($('request-update'), function() { 123 self.setUpdateStatus_('checking'); 124 $('request-update').disabled = true; 125 chrome.send('requestUpdate'); 126 }); 127 128 $('change-channel').onclick = function() { 129 PageManager.showPageByName('channel-change-page', false); 130 }; 131 132 var channelChangeDisallowedError = document.createElement('div'); 133 channelChangeDisallowedError.className = 'channel-change-error-bubble'; 134 135 var channelChangeDisallowedIcon = document.createElement('div'); 136 channelChangeDisallowedIcon.classList.add('help-page-icon-large'); 137 channelChangeDisallowedIcon.classList.add('channel-change-error-icon'); 138 channelChangeDisallowedError.appendChild(channelChangeDisallowedIcon); 139 140 var channelChangeDisallowedText = document.createElement('div'); 141 channelChangeDisallowedText.className = 'channel-change-error-text'; 142 channelChangeDisallowedText.textContent = 143 loadTimeData.getString('channelChangeDisallowedMessage'); 144 channelChangeDisallowedError.appendChild(channelChangeDisallowedText); 145 146 $('channel-change-disallowed-icon').onclick = function() { 147 PageManager.showBubble(channelChangeDisallowedError, 148 $('channel-change-disallowed-icon'), 149 $('help-container'), 150 cr.ui.ArrowLocation.TOP_END); 151 }; 152 } 153 154 // Attempt to update. 155 chrome.send('onPageLoaded'); 156 }, 157 158 /** @override */ 159 didClosePage: function() { 160 this.setMoreInfoVisible_(false); 161 }, 162 163 /** 164 * Sets the visible state of the 'More Info' section. 165 * @param {boolean} visible Whether the section should be visible. 166 * @private 167 */ 168 setMoreInfoVisible_: function(visible) { 169 var moreInfo = $('more-info-container'); 170 if (visible == moreInfo.classList.contains('visible')) 171 return; 172 173 moreInfo.classList.toggle('visible', visible); 174 moreInfo.style.height = visible ? moreInfo.scrollHeight + 'px' : ''; 175 moreInfo.addEventListener('webkitTransitionEnd', function(event) { 176 $('more-info-expander').textContent = visible ? 177 loadTimeData.getString('hideMoreInfo') : 178 loadTimeData.getString('showMoreInfo'); 179 }); 180 }, 181 182 /** 183 * Toggles the visible state of the 'More Info' section. 184 * @private 185 */ 186 toggleMoreInfo_: function() { 187 var moreInfo = $('more-info-container'); 188 this.setMoreInfoVisible_(!moreInfo.classList.contains('visible')); 189 }, 190 191 /** 192 * Assigns |method| to the onclick property of |el| if |el| exists. 193 * @param {HTMLElement} el The element on which to set the click handler. 194 * @param {Function} method The click handler. 195 * @private 196 */ 197 maybeSetOnClick_: function(el, method) { 198 if (el) 199 el.onclick = method; 200 }, 201 202 /** 203 * @param {string} state The state of the update. 204 * private 205 */ 206 setUpdateImage_: function(state) { 207 $('update-status-icon').className = 'help-page-icon ' + state; 208 }, 209 210 /** 211 * @return {boolean} True, if new channel switcher UI is used, 212 * false otherwise. 213 * @private 214 */ 215 isNewChannelSwitcherUI_: function() { 216 return !loadTimeData.valueExists('disableNewChannelSwitcherUI'); 217 }, 218 219 /** 220 * @return {boolean} True if target and current channels are not null and 221 * not equal. 222 * @private 223 */ 224 channelsDiffer_: function() { 225 var current = this.currentChannel_; 226 var target = this.targetChannel_; 227 return (current != null && target != null && current != target); 228 }, 229 230 /** 231 * @return {boolean} True if target channel is more stable than the current 232 * one, and false otherwise. 233 * @private 234 */ 235 targetChannelIsMoreStable_: function() { 236 var current = this.currentChannel_; 237 var target = this.targetChannel_; 238 if (current == null || target == null) 239 return false; 240 var currentIndex = this.channelList_.indexOf(current); 241 var targetIndex = this.channelList_.indexOf(target); 242 if (currentIndex < 0 || targetIndex < 0) 243 return false; 244 return currentIndex < targetIndex; 245 }, 246 247 /** 248 * @param {string} status The status of the update. 249 * @param {string} message Failure message to display. 250 * @private 251 */ 252 setUpdateStatus_: function(status, message) { 253 this.status_ = status; 254 this.message_ = message; 255 256 this.updateUI_(); 257 }, 258 259 /** 260 * Updates UI elements on the page according to current state. 261 * @private 262 */ 263 updateUI_: function() { 264 var status = this.status_; 265 var message = this.message_; 266 var channel = this.targetChannel_; 267 268 if (this.channelList_.indexOf(channel) >= 0) { 269 $('current-channel').textContent = loadTimeData.getStringF( 270 'currentChannel', this.channelTable_[channel].label); 271 this.updateChannelChangePageContainerVisibility_(); 272 } 273 274 if (status == null) 275 return; 276 277 if (cr.isMac && 278 $('update-status-message') && 279 $('update-status-message').hidden) { 280 // Chrome has reached the end of the line on this system. The 281 // update-obsolete-system message is displayed. No other auto-update 282 // status should be displayed. 283 return; 284 } 285 286 if (status == 'checking') { 287 this.setUpdateImage_('working'); 288 $('update-status-message').innerHTML = 289 loadTimeData.getString('updateCheckStarted'); 290 } else if (status == 'updating') { 291 this.setUpdateImage_('working'); 292 if (this.channelsDiffer_()) { 293 $('update-status-message').innerHTML = 294 loadTimeData.getStringF('updatingChannelSwitch', 295 this.channelTable_[channel].label); 296 } else { 297 $('update-status-message').innerHTML = 298 loadTimeData.getStringF('updating'); 299 } 300 } else if (status == 'nearly_updated') { 301 this.setUpdateImage_('up-to-date'); 302 if (this.channelsDiffer_()) { 303 $('update-status-message').innerHTML = 304 loadTimeData.getString('successfulChannelSwitch'); 305 } else { 306 $('update-status-message').innerHTML = 307 loadTimeData.getString('updateAlmostDone'); 308 } 309 } else if (status == 'updated') { 310 this.setUpdateImage_('up-to-date'); 311 $('update-status-message').innerHTML = 312 loadTimeData.getString('upToDate'); 313 } else if (status == 'failed') { 314 this.setUpdateImage_('failed'); 315 $('update-status-message').innerHTML = message; 316 } 317 318 // Following invariant must be established at the end of this function: 319 // { ~$('relaunch_and_powerwash').hidden -> $('relaunch').hidden } 320 var relaunchAndPowerwashHidden = true; 321 if ($('relaunch-and-powerwash')) { 322 // It's allowed to do powerwash only for customer devices, 323 // when user explicitly decides to update to a more stable 324 // channel. 325 relaunchAndPowerwashHidden = 326 !this.targetChannelIsMoreStable_() || status != 'nearly_updated'; 327 $('relaunch-and-powerwash').hidden = relaunchAndPowerwashHidden; 328 } 329 330 if (cr.isChromeOS) { 331 // Only enable the update button if it hasn't been used yet or the 332 // status isn't 'updated'. 333 if (!$('request-update').disabled || status != 'updated') { 334 // Disable the button if an update is already in progress. 335 $('request-update').disabled = 336 ['checking', 'updating', 'nearly_updated'].indexOf(status) > -1; 337 } 338 } 339 340 var container = $('update-status-container'); 341 if (container) { 342 container.hidden = status == 'disabled'; 343 $('relaunch').hidden = 344 (status != 'nearly_updated') || !relaunchAndPowerwashHidden; 345 346 if (cr.isChromeOS) { 347 // Assume the "updated" status is stale if we haven't checked yet. 348 if (status == 'updated' && !$('request-update').disabled) 349 container.hidden = true; 350 351 // Hide the request update button if auto-updating is disabled or 352 // a relaunch button is showing. 353 $('request-update').hidden = status == 'disabled' || 354 !$('relaunch').hidden || !relaunchAndPowerwashHidden; 355 } 356 357 if (!cr.isMac) 358 $('update-percentage').hidden = status != 'updating'; 359 } 360 }, 361 362 /** 363 * @param {number} progress The percent completion. 364 * @private 365 */ 366 setProgress_: function(progress) { 367 $('update-percentage').innerHTML = progress + '%'; 368 }, 369 370 /** 371 * @param {string} message The allowed connection types message. 372 * @private 373 */ 374 setAllowedConnectionTypesMsg_: function(message) { 375 $('allowed-connection-types-message').innerText = message; 376 }, 377 378 /** 379 * @param {boolean} visible Whether to show the message. 380 * @private 381 */ 382 showAllowedConnectionTypesMsg_: function(visible) { 383 $('allowed-connection-types-message').hidden = !visible; 384 }, 385 386 /** 387 * @param {string} state The promote state to set. 388 * @private 389 */ 390 setPromotionState_: function(state) { 391 if (state == 'hidden') { 392 $('promote').hidden = true; 393 } else if (state == 'enabled') { 394 $('promote').disabled = false; 395 $('promote').hidden = false; 396 } else if (state == 'disabled') { 397 $('promote').disabled = true; 398 $('promote').hidden = false; 399 } 400 }, 401 402 /** 403 * @param {boolean} obsolete Whether the system is obsolete. 404 * @private 405 */ 406 setObsoleteSystem_: function(obsolete) { 407 if (cr.isMac && $('update-obsolete-system-container')) { 408 $('update-obsolete-system-container').hidden = !obsolete; 409 } 410 }, 411 412 /** 413 * @param {boolean} endOfTheLine Whether the train has rolled into 414 * the station. 415 * @private 416 */ 417 setObsoleteSystemEndOfTheLine_: function(endOfTheLine) { 418 if (cr.isMac && 419 $('update-obsolete-system-container') && 420 !$('update-obsolete-system-container').hidden && 421 $('update-status-message')) { 422 $('update-status-message').hidden = endOfTheLine; 423 if (endOfTheLine) { 424 this.setUpdateImage_('failed'); 425 } 426 } 427 }, 428 429 /** 430 * @param {string} version Version of Chrome OS. 431 * @private 432 */ 433 setOSVersion_: function(version) { 434 if (!cr.isChromeOS) 435 console.error('OS version unsupported on non-CrOS'); 436 437 $('os-version').parentNode.hidden = (version == ''); 438 $('os-version').textContent = version; 439 }, 440 441 /** 442 * @param {string} firmware Firmware on Chrome OS. 443 * @private 444 */ 445 setOSFirmware_: function(firmware) { 446 if (!cr.isChromeOS) 447 console.error('OS firmware unsupported on non-CrOS'); 448 449 $('firmware').parentNode.hidden = (firmware == ''); 450 $('firmware').textContent = firmware; 451 }, 452 453 /** 454 * Updates page UI according to device owhership policy. 455 * @param {boolean} isEnterpriseManaged True if the device is 456 * enterprise managed. 457 * @private 458 */ 459 updateIsEnterpriseManaged_: function(isEnterpriseManaged) { 460 help.ChannelChangePage.updateIsEnterpriseManaged(isEnterpriseManaged); 461 this.updateUI_(); 462 }, 463 464 /** 465 * Updates name of the current channel, i.e. the name of the 466 * channel the device is currently on. 467 * @param {string} channel The name of the current channel. 468 * @private 469 */ 470 updateCurrentChannel_: function(channel) { 471 if (this.channelList_.indexOf(channel) < 0) 472 return; 473 this.currentChannel_ = channel; 474 help.ChannelChangePage.updateCurrentChannel(channel); 475 this.updateUI_(); 476 }, 477 478 /** 479 * Updates name of the target channel, i.e. the name of the 480 * channel the device is supposed to be. 481 * @param {string} channel The name of the target channel. 482 * @private 483 */ 484 updateTargetChannel_: function(channel) { 485 if (this.channelList_.indexOf(channel) < 0) 486 return; 487 this.targetChannel_ = channel; 488 help.ChannelChangePage.updateTargetChannel(channel); 489 this.updateUI_(); 490 }, 491 492 /** 493 * @param {boolean} enabled True if the release channel can be enabled. 494 * @private 495 */ 496 updateEnableReleaseChannel_: function(enabled) { 497 this.updateChannelChangerContainerVisibility_(enabled); 498 $('change-channel').disabled = !enabled; 499 $('channel-change-disallowed-icon').hidden = enabled; 500 }, 501 502 /** 503 * Sets the device target channel. 504 * @param {string} channel The name of the target channel. 505 * @param {boolean} isPowerwashAllowed True iff powerwash is allowed. 506 * @private 507 */ 508 setChannel_: function(channel, isPowerwashAllowed) { 509 chrome.send('setChannel', [channel, isPowerwashAllowed]); 510 $('channel-change-confirmation').hidden = false; 511 $('channel-change-confirmation').textContent = loadTimeData.getStringF( 512 'channel-changed', this.channelTable_[channel].name); 513 this.updateTargetChannel_(channel); 514 }, 515 516 /** 517 * Sets the value of the "Build Date" field of the "More Info" section. 518 * @param {string} buildDate The date of the build. 519 * @private 520 */ 521 setBuildDate_: function(buildDate) { 522 $('build-date-container').classList.remove('empty'); 523 $('build-date').textContent = buildDate; 524 }, 525 526 /** 527 * Updates channel-change-page-container visibility according to 528 * internal state. 529 * @private 530 */ 531 updateChannelChangePageContainerVisibility_: function() { 532 if (!this.isNewChannelSwitcherUI_()) { 533 $('channel-change-page-container').hidden = true; 534 return; 535 } 536 $('channel-change-page-container').hidden = 537 !help.ChannelChangePage.isPageReady(); 538 }, 539 540 /** 541 * Updates channel-changer dropdown visibility if |visible| is 542 * true and new channel switcher UI is disallowed. 543 * @param {boolean} visible True if channel-changer should be 544 * displayed, false otherwise. 545 * @private 546 */ 547 updateChannelChangerContainerVisibility_: function(visible) { 548 if (this.isNewChannelSwitcherUI_()) { 549 $('channel-changer').hidden = true; 550 return; 551 } 552 $('channel-changer').hidden = !visible; 553 }, 554 }; 555 556 HelpPage.setUpdateStatus = function(status, message) { 557 HelpPage.getInstance().setUpdateStatus_(status, message); 558 }; 559 560 HelpPage.setProgress = function(progress) { 561 HelpPage.getInstance().setProgress_(progress); 562 }; 563 564 HelpPage.setAndShowAllowedConnectionTypesMsg = function(message) { 565 HelpPage.getInstance().setAllowedConnectionTypesMsg_(message); 566 HelpPage.getInstance().showAllowedConnectionTypesMsg_(true); 567 }; 568 569 HelpPage.showAllowedConnectionTypesMsg = function(visible) { 570 HelpPage.getInstance().showAllowedConnectionTypesMsg_(visible); 571 }; 572 573 HelpPage.setPromotionState = function(state) { 574 HelpPage.getInstance().setPromotionState_(state); 575 }; 576 577 HelpPage.setObsoleteSystem = function(obsolete) { 578 HelpPage.getInstance().setObsoleteSystem_(obsolete); 579 }; 580 581 HelpPage.setObsoleteSystemEndOfTheLine = function(endOfTheLine) { 582 HelpPage.getInstance().setObsoleteSystemEndOfTheLine_(endOfTheLine); 583 }; 584 585 HelpPage.setOSVersion = function(version) { 586 HelpPage.getInstance().setOSVersion_(version); 587 }; 588 589 HelpPage.setOSFirmware = function(firmware) { 590 HelpPage.getInstance().setOSFirmware_(firmware); 591 }; 592 593 HelpPage.updateIsEnterpriseManaged = function(isEnterpriseManaged) { 594 if (!cr.isChromeOS) 595 return; 596 HelpPage.getInstance().updateIsEnterpriseManaged_(isEnterpriseManaged); 597 }; 598 599 HelpPage.updateCurrentChannel = function(channel) { 600 if (!cr.isChromeOS) 601 return; 602 HelpPage.getInstance().updateCurrentChannel_(channel); 603 }; 604 605 HelpPage.updateTargetChannel = function(channel) { 606 if (!cr.isChromeOS) 607 return; 608 HelpPage.getInstance().updateTargetChannel_(channel); 609 }; 610 611 HelpPage.updateEnableReleaseChannel = function(enabled) { 612 HelpPage.getInstance().updateEnableReleaseChannel_(enabled); 613 }; 614 615 HelpPage.setChannel = function(channel, isPowerwashAllowed) { 616 HelpPage.getInstance().setChannel_(channel, isPowerwashAllowed); 617 }; 618 619 HelpPage.setBuildDate = function(buildDate) { 620 HelpPage.getInstance().setBuildDate_(buildDate); 621 }; 622 623 // Export 624 return { 625 HelpPage: HelpPage 626 }; 627 }); 628