blob: 9776a1696fb6711df0f878a4cd13ca0b8d78a15e [file] [log] [blame]
// Copyright 2013 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.
// Decodes a UTF16 string that is encoded as base64.
function decodeUTF16Base64ToString(encoded_text) {
var data = atob(encoded_text);
var result = '';
for (var i = 0; i < data.length; i += 2) {
result +=
String.fromCharCode(data.charCodeAt(i) * 256 + data.charCodeAt(i + 1));
}
return result;
}
function toggleHelpBox() {
var helpBoxOuter = document.getElementById('details');
helpBoxOuter.classList.toggle(HIDDEN_CLASS);
var detailsButton = document.getElementById('details-button');
if (helpBoxOuter.classList.contains(HIDDEN_CLASS))
detailsButton.innerText = detailsButton.detailsText;
else
detailsButton.innerText = detailsButton.hideDetailsText;
// Details appears over the main content on small screens.
if (mobileNav) {
document.getElementById('main-content').classList.toggle(HIDDEN_CLASS);
var runnerContainer = document.querySelector('.runner-container');
if (runnerContainer) {
runnerContainer.classList.toggle(HIDDEN_CLASS);
}
}
}
function diagnoseErrors() {
// <if expr="not chromeos">
if (window.errorPageController)
errorPageController.diagnoseErrorsButtonClick();
// </if>
// <if expr="chromeos">
var extensionId = 'idddmepepmjcgiedknnmlbadcokidhoa';
var diagnoseFrame = document.getElementById('diagnose-frame');
diagnoseFrame.innerHTML =
'<iframe src="chrome-extension://' + extensionId +
'/index.html"></iframe>';
// </if>
}
// Subframes use a different layout but the same html file. This is to make it
// easier to support platforms that load the error page via different
// mechanisms (Currently just iOS).
if (window.top.location != window.location)
document.documentElement.setAttribute('subframe', '');
// Re-renders the error page using |strings| as the dictionary of values.
// Used by NetErrorTabHelper to update DNS error pages with probe results.
function updateForDnsProbe(strings) {
var context = new JsEvalContext(strings);
jstProcess(context, document.getElementById('t'));
onDocumentLoadOrUpdate();
}
// Given the classList property of an element, adds an icon class to the list
// and removes the previously-
function updateIconClass(classList, newClass) {
var oldClass;
if (classList.hasOwnProperty('last_icon_class')) {
oldClass = classList['last_icon_class'];
if (oldClass == newClass)
return;
}
classList.add(newClass);
if (oldClass !== undefined)
classList.remove(oldClass);
classList['last_icon_class'] = newClass;
if (newClass == 'icon-offline') {
document.firstElementChild.classList.add('offline');
new Runner('.interstitial-wrapper');
} else {
document.body.classList.add('neterror');
}
}
// Does a search using |baseSearchUrl| and the text in the search box.
function search(baseSearchUrl) {
var searchTextNode = document.getElementById('search-box');
document.location = baseSearchUrl + searchTextNode.value;
return false;
}
// Use to track clicks on elements generated by the navigation correction
// service. If |trackingId| is negative, the element does not come from the
// correction service.
function trackClick(trackingId) {
// This can't be done with XHRs because XHRs are cancelled on navigation
// start, and because these are cross-site requests.
if (trackingId >= 0 && errorPageController)
errorPageController.trackClick(trackingId);
}
// Called when an <a> tag generated by the navigation correction service is
// clicked. Separate function from trackClick so the resources don't have to
// be updated if new data is added to jstdata.
function linkClicked(jstdata) {
trackClick(jstdata.trackingId);
}
// Implements button clicks. This function is needed during the transition
// between implementing these in trunk chromium and implementing them in
// iOS.
function reloadButtonClick(url) {
if (window.errorPageController) {
errorPageController.reloadButtonClick();
} else {
location = url;
}
}
function downloadButtonClick() {
if (window.errorPageController) {
errorPageController.downloadButtonClick();
var downloadButton = document.getElementById('download-button');
downloadButton.disabled = true;
downloadButton.textContent = downloadButton.disabledText;
document.getElementById('download-link-wrapper')
.classList.add(HIDDEN_CLASS);
document.getElementById('download-link-clicked-wrapper')
.classList.remove(HIDDEN_CLASS);
}
}
function detailsButtonClick() {
if (window.errorPageController)
errorPageController.detailsButtonClick();
}
/**
* Replace the reload button with the Google cached copy suggestion.
*/
function setUpCachedButton(buttonStrings) {
var reloadButton = document.getElementById('reload-button');
reloadButton.textContent = buttonStrings.msg;
var url = buttonStrings.cacheUrl;
var trackingId = buttonStrings.trackingId;
reloadButton.onclick = function(e) {
e.preventDefault();
trackClick(trackingId);
if (window.errorPageController) {
errorPageController.trackCachedCopyButtonClick();
}
location = url;
};
reloadButton.style.display = '';
}
var primaryControlOnLeft = true;
// <if expr="is_macosx or is_ios or is_linux or is_android">
primaryControlOnLeft = false;
// </if>
function setAutoFetchState(scheduled, can_schedule) {
document.getElementById('cancel-save-page-button')
.classList.toggle(HIDDEN_CLASS, !scheduled);
document.getElementById('save-page-for-later-button')
.classList.toggle(HIDDEN_CLASS, scheduled || !can_schedule);
}
function savePageLaterClick() {
errorPageController.savePageForLater();
// savePageForLater will eventually trigger a call to setAutoFetchState() when
// it completes.
}
function cancelSavePageClick() {
errorPageController.cancelSavePage();
// setAutoFetchState is not called in response to cancelSavePage(), so do it
// now.
setAutoFetchState(false, true);
}
function toggleErrorInformationPopup() {
document.getElementById('error-information-popup-container')
.classList.toggle(HIDDEN_CLASS);
}
function launchOfflineItem(itemID, name_space) {
errorPageController.launchOfflineItem(itemID, name_space);
}
function launchDownloadsPage() {
errorPageController.launchDownloadsPage();
}
function getIconForSuggestedItem(item) {
// Note: |item.content_type| contains the enum values from
// chrome::mojom::AvailableContentType.
switch (item.content_type) {
case 1: // kVideo
return 'image-video';
case 2: // kAudio
return 'image-music-note';
case 0: // kPrefetchedPage
case 3: // kOtherPage
return 'image-earth';
}
return 'image-file';
}
function getSuggestedContentDiv(item, index) {
// Note: See AvailableContentToValue in available_offline_content_helper.cc
// for the data contained in an |item|.
// TODO(carlosk): Present |snippet_base64| when that content becomes
// available.
var thumbnail = '';
var extraContainerClasses = [];
// html_inline.py will try to replace src attributes with data URIs using a
// simple regex. The following is obfuscated slightly to avoid that.
var src = 'src';
if (item.thumbnail_data_uri) {
extraContainerClasses.push('suggestion-with-image');
thumbnail = `<img ${src}="${item.thumbnail_data_uri}">`;
} else {
extraContainerClasses.push('suggestion-with-icon');
iconClass = getIconForSuggestedItem(item);
thumbnail = `<div><img class="${iconClass}"></div>`;
}
var favicon = '';
if (item.favicon_data_uri) {
favicon = `<img ${src}="${item.favicon_data_uri}">`;
} else {
extraContainerClasses.push('no-favicon');
}
if (!item.attribution_base64)
extraContainerClasses.push('no-attribution');
return `
<div class="offline-content-suggestion ${extraContainerClasses.join(' ')}"
onclick="launchOfflineItem('${item.ID}', '${item.name_space}')">
<div class="offline-content-suggestion-texts">
<div id="offline-content-suggestion-title-${index}"
class="offline-content-suggestion-title">
</div>
<div class="offline-content-suggestion-attribution-freshness">
<div id="offline-content-suggestion-favicon-${index}"
class="offline-content-suggestion-favicon">
${favicon}
</div>
<div id="offline-content-suggestion-attribution-${index}"
class="offline-content-suggestion-attribution">
</div>
<div class="offline-content-suggestion-freshness">
${item.date_modified}
</div>
<div class="offline-content-suggestion-pin-spacer"></div>
<div class="offline-content-suggestion-pin"></div>
</div>
</div>
<div class="offline-content-suggestion-thumbnail">
${thumbnail}
</div>
</div>`;
}
// Populates a list of suggested offline content.
// Note: For security reasons all content downloaded from the web is considered
// unsafe and must be securely handled to be presented on the dino page. Images
// have already been safely re-encoded but textual content -- like title and
// attribution -- must be properly handled here.
function offlineContentAvailable(isShown, suggestions) {
if (!suggestions || !loadTimeData.valueExists('offlineContentList'))
return;
var suggestionsHTML = [];
for (var index = 0; index < suggestions.length; index++)
suggestionsHTML.push(getSuggestedContentDiv(suggestions[index], index));
document.getElementById('offline-content-suggestions').innerHTML =
suggestionsHTML.join('\n');
// Sets textual web content using |textContent| to make sure it's handled as
// plain text.
for (var index = 0; index < suggestions.length; index++) {
document.getElementById(`offline-content-suggestion-title-${index}`)
.textContent =
decodeUTF16Base64ToString(suggestions[index].title_base64);
document.getElementById(`offline-content-suggestion-attribution-${index}`)
.textContent =
decodeUTF16Base64ToString(suggestions[index].attribution_base64);
}
var contentListElement = document.getElementById('offline-content-list');
if (document.dir == 'rtl')
contentListElement.classList.add('is-rtl');
contentListElement.hidden = false;
// The list is configured as hidden by default. Show it if needed.
if (isShown)
toggleOfflineContentListVisibility(false);
}
function toggleOfflineContentListVisibility(updatePref) {
if (!loadTimeData.valueExists('offlineContentList'))
return;
var contentListElement = document.getElementById('offline-content-list');
var isVisible = !contentListElement.classList.toggle('list-hidden');
if (updatePref && window.errorPageController) {
errorPageController.listVisibilityChanged(isVisible);
}
}
// Called on document load, and from updateForDnsProbe().
function onDocumentLoadOrUpdate() {
var downloadButtonVisible = loadTimeData.valueExists('downloadButton') &&
loadTimeData.getValue('downloadButton').msg;
var detailsButton = document.getElementById('details-button');
// <if expr="HIDE_ERROR_MESSAGE_FOR_DINO_PAGE">
if ('chrome://dino/' == document.title) {
// If the user explicitly loads the dino page, don't show offline
// information as it's not accurate.
document.getElementById('main-message').classList.add(HIDDEN_CLASS);
}
// </if>
// If offline content suggestions will be visible, the usual buttons will not
// be presented.
var offlineContentVisible =
loadTimeData.valueExists('suggestedOfflineContentPresentation');
if (offlineContentVisible) {
document.querySelector('.nav-wrapper').classList.add(HIDDEN_CLASS);
detailsButton.classList.add(HIDDEN_CLASS);
document.getElementById('download-link').hidden = !downloadButtonVisible;
document.getElementById('download-links-wrapper')
.classList.remove(HIDDEN_CLASS);
document.getElementById('error-information-popup-container')
.classList.add('use-popup-container', HIDDEN_CLASS)
document.getElementById('error-information-button')
.classList.remove(HIDDEN_CLASS);
}
var attemptAutoFetch = loadTimeData.valueExists('attemptAutoFetch') &&
loadTimeData.getValue('attemptAutoFetch');
var reloadButtonVisible = loadTimeData.valueExists('reloadButton') &&
loadTimeData.getValue('reloadButton').msg;
// Check for Google cached copy suggestion.
var cacheButtonVisible = false;
if (loadTimeData.valueExists('cacheButton')) {
setUpCachedButton(loadTimeData.getValue('cacheButton'));
cacheButtonVisible = true;
}
var reloadButton = document.getElementById('reload-button');
var downloadButton = document.getElementById('download-button');
if (reloadButton.style.display == 'none' &&
downloadButton.style.display == 'none') {
detailsButton.classList.add('singular');
}
// Show or hide control buttons.
var controlButtonDiv = document.getElementById('control-buttons');
controlButtonDiv.hidden = offlineContentVisible ||
!(reloadButtonVisible || downloadButtonVisible || cacheButtonVisible);
}
function onDocumentLoad() {
// Sets up the proper button layout for the current platform.
if (primaryControlOnLeft) {
buttons.classList.add('suggested-left');
} else {
buttons.classList.add('suggested-right');
}
onDocumentLoadOrUpdate();
}
document.addEventListener('DOMContentLoaded', onDocumentLoad);