// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Chrome window that hosts UI. Only one window is allowed.
* @type {chrome.app.window.AppWindow}
*/
let appWindow = null;
/** @type {TermsOfServicePage} */
let termsPage = null;
/**
* Used for bidirectional communication with native code.
* @type {chrome.runtime.Port}
*/
let port = null;
/**
* Stores current device id.
* @type {string}
*/
let currentDeviceId = null;
/**
* Stores last focused element before showing overlay. It is used to restore
* focus once overlay is closed.
* @type {Object}
*/
let lastFocusedElement = null;
/**
* Stores locale set for the current browser process.
* @type {string}
*/
let locale = null;
/**
* Host window outer default width.
* @const {number}
*/
const OUTER_WIDTH = 768;
/**
* Host window outer default height.
* @const {number}
*/
const OUTER_HEIGHT = 640;
/**
* Sends a native message to ArcSupportHost.
* @param {string} event The event type in message.
* @param {Object=} opt_props Extra properties for the message.
*/
function sendNativeMessage(event, opt_props) {
const message = Object.assign({'event': event}, opt_props);
port.postMessage(message);
}
/**
* Class to handle checkbox corresponding to a preference.
*/
class PreferenceCheckbox {
/**
* Creates a Checkbox which handles the corresponding preference update.
* @param {Element} container The container this checkbox corresponds to.
* The element must have <input type="checkbox" class="checkbox-option">
* for the checkbox itself, and <p class="checkbox-text"> for its label.
* @param {string} learnMoreContent I18n content which is shown when "Learn
* More" link is clicked.
* @param {string?} learnMoreLinkId The ID for the "Learn More" link element.
* TODO: Get rid of this. The element can have class so that it can be
* identified easily. Also, it'd be better to extract the link element
* (tag) from the i18n text, and let i18n focus on the content.
* @param {string?} policyText The content of the policy indicator.
*/
constructor(container, learnMoreContent, learnMoreLinkId, policyText) {
this.container_ = container;
this.learnMoreContent_ = learnMoreContent;
this.checkbox_ = container.querySelector('.checkbox-option');
this.label_ = container.querySelector('.checkbox-text');
this.isManaged_ = false;
const learnMoreLink = this.label_.querySelector(learnMoreLinkId);
if (learnMoreLink) {
learnMoreLink.addEventListener(
'click', (event) => this.onLearnMoreLinkClicked(event));
learnMoreLink.addEventListener(
'keydown', (event) => this.suppressKeyDown(event));
}
// Create controlled indicator for policy if necessary.
if (policyText) {
this.policyIndicator_ =
new appWindow.contentWindow.cr.ui.ControlledIndicator();
this.policyIndicator_.setAttribute('textpolicy', policyText);
// TODO: better to have a dedicated element for this place.
this.label_.insertBefore(this.policyIndicator_, learnMoreLink);
} else {
this.policyIndicator_ = null;
}
}
/**
* Returns if the checkbox is checked or not. Note that this *may* be
* different from the preference value, because the user's check is
* not propagated to the preference until the user clicks "AGREE" button.
*/
isChecked() {
return this.checkbox_.checked;
}
/**
* Returns if the checkbox reflects a managed setting, rather than a
* user-controlled setting.
*/
isManaged() {
return this.isManaged_;
}
/**
* Called when the preference value in native code is updated.
*/
onPreferenceChanged(isEnabled, isManaged) {
this.checkbox_.checked = isEnabled;
this.checkbox_.disabled = isManaged;
this.label_.disabled = isManaged;
this.isManaged_ = isManaged;
if (this.policyIndicator_) {
if (isManaged) {
this.policyIndicator_.setAttribute('controlled-by', 'policy');
} else {
this.policyIndicator_.removeAttribute('controlled-by');
}
}
}
/**
* Called when the "Learn More" link is clicked.
*/
onLearnMoreLinkClicked(event) {
showTextOverlay(this.learnMoreContent_);
event.stopPropagation();
}
/**
* Called when a key is pressed down on the "Learn More" or "Settings" links.
* This prevent propagation of the current event in order to prevent parent
* check box toggles its state.
*/
suppressKeyDown(event) {
event.stopPropagation();
}
}
/**
* Handles the checkbox action of metrics preference.
* This has special customization e.g. show/hide the checkbox based on
* the native preference.
*/
class MetricsPreferenceCheckbox extends PreferenceCheckbox {
constructor(
container, learnMoreContent, learnMoreLinkId, isOwner, textDisabled,
textEnabled, textManagedDisabled, textManagedEnabled) {
// Do not use policy indicator.
// Learn More link handling is done by this class.
// So pass |null| intentionally.
super(container, learnMoreContent, null, null);
this.textLabel_ = container.querySelector('.content-text');
this.learnMoreLinkId_ = learnMoreLinkId;
this.isOwner_ = isOwner;
// Two dimensional array. First dimension is whether it is managed or not,
// the second one is whether it is enabled or not.
this.texts_ = [
[textDisabled, textEnabled],
[textManagedDisabled, textManagedEnabled],
];
}
onPreferenceChanged(isEnabled, isManaged) {
isManaged = isManaged || !this.isOwner_;
super.onPreferenceChanged(isEnabled, isManaged);
// Hide the checkbox if it is not allowed to (re-)enable.
// TODO(jhorwich) Remove checkbox functionality from the metrics notice as
// we've removed the ability for a device owner to enable it during ARC
// setup.
const canEnable = false;
this.checkbox_.hidden = !canEnable;
this.textLabel_.hidden = canEnable;
const label = canEnable ? this.label_ : this.textLabel_;
// Update label text.
label.innerHTML = this.texts_[isManaged ? 1 : 0][isEnabled ? 1 : 0];
// Work around for the current translation text.
// The translation text has tags for following links, although those
// tags are not the target of the translation (but those content text is
// the translation target).
// So, meanwhile, we set the link every time we update the text.
// TODO: fix the translation text, and main html.
const learnMoreLink = label.querySelector(this.learnMoreLinkId_);
if (learnMoreLink) {
learnMoreLink.addEventListener(
'click', (event) => this.onLearnMoreLinkClicked(event));
learnMoreLink.addEventListener(
'keydown', (event) => this.suppressKeyDown(event));
}
// settings-link is used only in privacy section.
const settingsLink = label.querySelector('#settings-link');
if (settingsLink) {
settingsLink.addEventListener(
'click', (event) => this.onPrivacySettingsLinkClicked(event));
settingsLink.addEventListener(
'keydown', (event) => this.suppressKeyDown(event));
}
}
/** Called when "privacy settings" link is clicked. */
onPrivacySettingsLinkClicked(event) {
sendNativeMessage('onOpenPrivacySettingsPageClicked');
event.stopPropagation();
}
}
/**
* Represents the page loading state.
* @enum {number}
*/
const LoadState = {
UNLOADED: 0,
LOADING: 1,
ABORTED: 2,
LOADED: 3,
};
/**
* Handles events for Terms-Of-Service page. Also this implements the async
* loading of Terms-Of-Service content.
*/
class TermsOfServicePage {
/**
* @param {Element} container The container of the page.
* @param {boolean} isManaged Set true if ARC is managed.
* @param {string} countryCode The country code for the terms of service.
* @param {MetricsPreferenceCheckbox} metricsCheckbox. The checkbox for the
* metrics preference.
* @param {PreferenceCheckbox} backupRestoreCheckbox The checkbox for the
* backup-restore preference.
* @param {PreferenceCheckbox} locationServiceCheckbox The checkbox for the
* location service.
* @param {string} learnMorePaiService. Contents of learn more link of Play
* auto install service.
*/
constructor(
container, isManaged, countryCode, metricsCheckbox, backupRestoreCheckbox,
locationServiceCheckbox, learnMorePaiService) {
this.loadingContainer_ =
container.querySelector('#terms-of-service-loading');
this.contentContainer_ =
container.querySelector('#terms-of-service-content');
this.metricsCheckbox_ = metricsCheckbox;
this.backupRestoreCheckbox_ = backupRestoreCheckbox;
this.locationServiceCheckbox_ = locationServiceCheckbox;
this.isManaged_ = isManaged;
this.tosContent_ = '';
this.tosShown_ = false;
// Set event listener for webview loading.
this.termsView_ = container.querySelector('#terms-view');
this.termsView_.addEventListener(
'loadstart', () => this.onTermsViewLoadStarted_());
this.termsView_.addEventListener(
'contentload', () => this.onTermsViewLoaded_());
this.termsView_.addEventListener(
'loadabort', (event) => this.onTermsViewLoadAborted_(event.reason));
const requestFilter = {urls: ['<all_urls>'], types: ['main_frame']};
this.termsView_.request.onCompleted.addListener(
this.onTermsViewRequestCompleted_.bind(this), requestFilter);
this.countryCode = countryCode.toLowerCase();
let scriptInitTermsView =
'document.countryCode = \'' + this.countryCode + '\';';
scriptInitTermsView += 'document.language = \'' + locale + '\';';
scriptInitTermsView += 'document.viewMode = \'large-view\';';
this.termsView_.addContentScripts([
{
name: 'preProcess',
matches: ['<all_urls>'],
js: {code: scriptInitTermsView},
run_at: 'document_start',
},
{
name: 'postProcess',
matches: ['<all_urls>'],
css: {files: ['playstore.css']},
js: {files: ['playstore.js']},
run_at: 'document_end',
},
]);
// webview is not allowed to open links in the new window. Hook these
// events and open links in overlay dialog.
this.termsView_.addEventListener('newwindow', function(event) {
event.preventDefault();
showURLOverlay(event.targetUrl);
});
this.state_ = LoadState.UNLOADED;
this.serviceContainer_ = container.querySelector('#service-container');
this.locationService_ =
container.querySelector('#location-service-preference');
this.paiService_ = container.querySelector('#pai-service-description');
this.googleServiceConfirmation_ =
container.querySelector('#google-service-confirmation');
this.agreeButton_ = container.querySelector('#button-agree');
this.nextButton_ = container.querySelector('#button-next');
// On managed case, do not show TermsOfService section. Note that the
// checkbox for the preferences are still visible.
const visibility = isManaged ? 'hidden' : 'visible';
container.querySelector('#terms-container').style.visibility = visibility;
// PAI service.
const paiLabel = this.paiService_.querySelector('.content-text');
const paiLearnMoreLink = paiLabel.querySelector('#learn-more-link-pai');
if (paiLearnMoreLink) {
paiLearnMoreLink.onclick = function(event) {
event.stopPropagation();
showTextOverlay(learnMorePaiService);
};
}
// Set event handler for buttons.
this.agreeButton_.addEventListener('click', () => this.onAgree());
this.nextButton_.addEventListener('click', () => this.onNext_());
container.querySelector('#button-cancel')
.addEventListener('click', () => this.onCancel_());
}
/** Called when the TermsOfService page is shown. */
onShow() {
if (this.isManaged_ || this.state_ === LoadState.LOADED) {
// Note: in managed case, because it does not show the contents of terms
// of service, it is ok to show the content container immediately.
this.showContent_();
} else {
this.startTermsViewLoading_();
}
}
/** Shows the loaded terms-of-service content. */
showContent_() {
this.loadingContainer_.hidden = true;
this.contentContainer_.hidden = false;
this.locationService_.hidden = true;
this.paiService_.hidden = true;
this.googleServiceConfirmation_.hidden = true;
this.serviceContainer_.style.overflow = 'hidden';
this.agreeButton_.hidden = true;
this.nextButton_.hidden = false;
this.updateTermsHeight_();
this.nextButton_.focus();
if (!this.termsView_.src.startsWith('https://play.google/play-terms')) {
// This is reload due to language selection. Set focus on dropdown to pass
// GAR criteria(b/308537845)
const getDropDown = {code: 'getLangZoneSelect();'};
termsPage.termsView_.executeScript(
getDropDown, this.focusOnLangZoneSelect_.bind(this));
}
}
/** Callback for getDropDown in showContext_. */
focusOnLangZoneSelect_(results) {
if (results.length !== 1) {
console.error('unexpected return value of the script');
return;
}
if (results[0]) {
this.termsView_.focus();
const details = {code: 'getLangZoneSelect().focus();'};
termsPage.termsView_.executeScript(details, function(results) {});
}
}
onNext_() {
this.locationService_.hidden = false;
this.paiService_.hidden = false;
this.googleServiceConfirmation_.hidden = false;
this.serviceContainer_.style.overflowY = 'auto';
this.serviceContainer_.scrollTop = this.serviceContainer_.scrollHeight;
this.agreeButton_.hidden = false;
this.nextButton_.hidden = true;
this.agreeButton_.focus();
}
/**
* Updates terms view height manually because webview is not automatically
* resized in case parent div element gets resized.
*/
updateTermsHeight_() {
// Update the height in next cycle to prevent webview animation and
// wrong layout caused by whole-page layout change.
setTimeout(function() {
const doc = appWindow.contentWindow.document;
// Reset terms-view height in order to stabilize style computation. For
// some reason, child webview affects final result.
this.termsView_.style.height = '0px';
const termsContainer =
this.contentContainer_.querySelector('#terms-container');
const style = window.getComputedStyle(termsContainer, null);
this.termsView_.style.height = style.getPropertyValue('height');
}.bind(this), 0);
}
/** Starts to load the terms of service webview content. */
startTermsViewLoading_() {
if (this.state_ === LoadState.LOADING) {
// If there already is inflight loading task, do nothing.
return;
}
const defaultLocation = 'https://play.google/play-terms/';
if (this.termsView_.src) {
// This is reloading the page, typically clicked RETRY on error page.
this.fastLocation_ = undefined;
if (this.termsView_.src === defaultLocation) {
this.termsView_.reload();
} else {
this.termsView_.src = defaultLocation;
}
} else {
// startTermsViewLoading used to have load time optimization logic
// (b/62540008), but this logic was removed because ToS webpage
// load time had improved.
this.termsView_.src = defaultLocation;
}
}
/** Returns user choices and page configuration for processing. */
getPageResults_() {
return {
tosContent: this.tosContent_,
tosShown: this.tosShown_,
isMetricsEnabled: this.metricsCheckbox_.isChecked(),
isBackupRestoreEnabled: this.backupRestoreCheckbox_.isChecked(),
isBackupRestoreManaged: this.backupRestoreCheckbox_.isManaged(),
isLocationServiceEnabled: this.locationServiceCheckbox_.isChecked(),
isLocationServiceManaged: this.locationServiceCheckbox_.isManaged(),
};
}
/** Called when the terms-view starts to be loaded. */
onTermsViewLoadStarted_() {
// Note: Reloading can be triggered by user action. E.g., user may select
// their language by selection at the bottom of the Terms Of Service
// content.
this.state_ = LoadState.LOADING;
this.tosContent_ = '';
// Show loading page.
this.loadingContainer_.hidden = false;
this.contentContainer_.hidden = true;
}
/** Called when the terms-view is loaded. */
onTermsViewLoaded_() {
// This is called also when the loading is failed.
// In such a case, onTermsViewLoadAborted_() is called in advance, and
// state_ is set to ABORTED. Here, switch the view only for the
// successful loading case.
if (this.state_ === LoadState.LOADING) {
const getToSContent = {code: 'getToSContent();'};
termsPage.termsView_.executeScript(
getToSContent, this.onGetToSContent_.bind(this));
}
}
/** Callback for getToSContent. */
onGetToSContent_(results) {
if (this.state_ === LoadState.LOADING) {
if (!results || results.length !== 1 || typeof results[0] !== 'string') {
this.onTermsViewLoadAborted_('unable to get ToS content');
return;
}
onTosLoadResult(true /*success*/);
this.state_ = LoadState.LOADED;
this.tosContent_ = results[0];
this.tosShown_ = true;
this.showContent_();
if (this.fastLocation_) {
// For fast location load make sure we have right terms displayed.
this.fastLocation_ = undefined;
const checkInitialLangZoneTerms = 'processLangZoneTerms(true, \'' +
locale + '\', \'' + this.countryCode + '\');';
const details = {code: checkInitialLangZoneTerms};
termsPage.termsView_.executeScript(details, function(results) {});
}
}
}
/** Called when the terms-view loading is aborted. */
onTermsViewLoadAborted_(reason) {
console.error('TermsView loading is aborted: ' + reason);
// Mark ABORTED so that onTermsViewLoaded_() won't show the content view.
this.fastLocation_ = undefined;
this.state_ = LoadState.ABORTED;
onTosLoadResult(false /*success*/);
showErrorPage(
appWindow.contentWindow.loadTimeData.getString('serverError'),
true /*opt_shouldShowSendFeedback*/,
true /*opt_shouldShowNetworkTests*/);
}
/** Called when the terms-view's load request is completed. */
onTermsViewRequestCompleted_(details) {
if (this.state_ !== LoadState.LOADING || details.statusCode === 200) {
return;
}
// In case we failed with fast location let retry default scheme.
if (this.fastLocation_) {
this.fastLocation_ = undefined;
this.termsView_.src = 'https://play.google/play-terms/';
return;
}
this.onTermsViewLoadAborted_(
'request failed with status ' + details.statusCode);
}
/** Called when "AGREE" button is clicked. */
onAgree() {
sendNativeMessage('onAgreed', this.getPageResults_());
}
/** Called when "CANCEL" button is clicked. */
onCancel_() {
sendNativeMessage('onCanceled', this.getPageResults_());
closeWindow();
}
/** Called when metrics preference is updated. */
onMetricsPreferenceChanged(isEnabled, isManaged) {
this.metricsCheckbox_.onPreferenceChanged(isEnabled, isManaged);
// Applying metrics mode may change page layout, update terms height.
this.updateTermsHeight_();
}
/** Called when backup-restore preference is updated. */
onBackupRestorePreferenceChanged(isEnabled, isManaged) {
this.backupRestoreCheckbox_.onPreferenceChanged(isEnabled, isManaged);
}
/** Called when location service preference is updated. */
onLocationServicePreferenceChanged(isEnabled, isManaged) {
this.locationServiceCheckbox_.onPreferenceChanged(isEnabled, isManaged);
}
}
/**
* Applies localization for html content and sets terms webview.
* @param {!Object} data Localized strings and relevant information.
* @param {string} deviceId Current device id.
*/
function initialize(data, deviceId) {
currentDeviceId = deviceId;
const doc = appWindow.contentWindow.document;
const loadTimeData = appWindow.contentWindow.loadTimeData;
loadTimeData.data = data;
appWindow.contentWindow.i18nTemplate.process(doc, loadTimeData);
locale = loadTimeData.getString('locale');
// Initialize preference connected checkboxes in terms of service page.
termsPage = new TermsOfServicePage(
doc.getElementById('terms'), data.arcManaged, data.countryCode,
new MetricsPreferenceCheckbox(
doc.getElementById('metrics-preference'), data.learnMoreStatistics,
'#learn-more-link-metrics', data.isOwnerProfile,
data.textMetricsDisabled, data.textMetricsEnabled,
data.textMetricsManagedDisabled, data.textMetricsManagedEnabled),
new PreferenceCheckbox(
doc.getElementById('backup-restore-preference'),
data.learnMoreBackupAndRestore, '#learn-more-link-backup-restore',
data.controlledByPolicy),
new PreferenceCheckbox(
doc.getElementById('location-service-preference'),
data.learnMoreLocationServices, '#learn-more-link-location-service',
data.controlledByPolicy),
data.learnMorePaiService);
doc.getElementById('close-button').title =
loadTimeData.getString('overlayClose');
adjustTopMargin();
}
// With UI request to change inner window size to outer window size and reduce
// top spacing, adjust top margin to negative window top bar height.
function adjustTopMargin() {
if (!appWindow) {
return;
}
const decorationHeight =
appWindow.outerBounds.height - appWindow.innerBounds.height;
const doc = appWindow.contentWindow.document;
const headers = doc.getElementsByClassName('header');
for (let i = 0; i < headers.length; i++) {
headers[i].style.marginTop = -decorationHeight + 'px';
}
}
/**
* Handles native messages received from ArcSupportHost.
* @param {!Object} message The message received.
*/
function onNativeMessage(message) {
if (!message.action) {
return;
}
if (!appWindow) {
console.warn('Received native message when window is not available.');
return;
}
if (message.action === 'initialize') {
initialize(message.data, message.deviceId);
} else if (message.action === 'setMetricsMode') {
termsPage.onMetricsPreferenceChanged(message.enabled, message.managed);
} else if (message.action === 'setBackupAndRestoreMode') {
termsPage.onBackupRestorePreferenceChanged(
message.enabled, message.managed);
} else if (message.action === 'setLocationServiceMode') {
termsPage.onLocationServicePreferenceChanged(
message.enabled, message.managed);
} else if (message.action === 'showPage') {
showPage(message.page);
} else if (message.action === 'showErrorPage') {
showErrorPage(
message.errorMessage, message.shouldShowSendFeedback,
message.shouldShowNetworkTests);
} else if (message.action === 'closeWindow') {
closeWindow();
} else if (message.action === 'setWindowBounds') {
setWindowBounds(
message.displayWorkareaX, message.displayWorkareaY,
message.displayWorkareaWidth, message.displayWorkareaHeight);
}
}
/**
* Connects to ArcSupportHost.
*/
function connectPort() {
const hostName = 'com.google.arc_support';
port = chrome.runtime.connectNative(hostName);
port.onMessage.addListener(onNativeMessage);
}
/**
* Shows requested page and hide others. Show appWindow if it was hidden before.
* 'none' hides all views.
* @param {string} pageDivId id of divider of the page to show.
*/
function showPage(pageDivId) {
if (!appWindow) {
return;
}
hideOverlay();
appWindow.contentWindow.stopProgressAnimation();
const doc = appWindow.contentWindow.document;
const pages = doc.getElementsByClassName('section');
for (let i = 0; i < pages.length; i++) {
pages[i].hidden = pages[i].id !== pageDivId;
}
appWindow.show();
if (pageDivId === 'terms') {
termsPage.onShow();
}
// Start progress bar animation for the page that has the dynamic progress
// bar. 'error' page has the static progress bar that no need to be animated.
if (pageDivId === 'terms' || pageDivId === 'arc-loading') {
appWindow.contentWindow.startProgressAnimation(pageDivId);
}
}
/**
* Sends a message to host that TOS load has failed or succeeded.
*
* @param {boolean} success If set to true, loading has succeeded. False
* otherwise.
*/
function onTosLoadResult(success) {
sendNativeMessage('onTosLoadResult', {success: success});
}
/**
* Shows an error page, with given errorMessage.
*
* @param {string} errorMessage Localized error message text.
* @param {?boolean} opt_shouldShowSendFeedback If set to true, show "Send
* feedback" button.
* @param {?boolean} opt_shouldShowNetworkTests If set to true, show "Check
* network" button. If set to false, position the "Send feedback" button
* after the flex div that separates the left and right side of the error
* dialog.
*/
function showErrorPage(
errorMessage, opt_shouldShowSendFeedback, opt_shouldShowNetworkTests) {
if (!appWindow) {
return;
}
const doc = appWindow.contentWindow.document;
const messageElement = doc.getElementById('error-message');
messageElement.innerText = errorMessage;
const sendFeedbackElement = doc.getElementById('button-send-feedback');
sendFeedbackElement.hidden = !opt_shouldShowSendFeedback;
const networkTestsElement = doc.getElementById('button-run-network-tests');
networkTestsElement.hidden = !opt_shouldShowNetworkTests;
showPage('error');
// If the error is not network-related, position send feedback after the flex
// div.
const feedbackSeparator = doc.getElementById('div-error-separating-buttons');
feedbackSeparator.style.order = opt_shouldShowNetworkTests ? 'initial' : -1;
sendNativeMessage('onErrorPageShown', {
networkTestsShown: opt_shouldShowNetworkTests,
});
}
/**
* Shows overlay dialog and required content.
* @param {string} overlayClass Defines which content to show, 'overlay-url' for
* webview based content and 'overlay-text' for
* simple text view.
*/
function showOverlay(overlayClass) {
const doc = appWindow.contentWindow.document;
const overlayContainer = doc.getElementById('overlay-container');
overlayContainer.classList.remove('overlay-text');
overlayContainer.classList.remove('overlay-url');
overlayContainer.classList.add('overlay-loading');
overlayContainer.classList.add(overlayClass);
overlayContainer.hidden = false;
lastFocusedElement = doc.activeElement;
doc.getElementById('overlay-close').focus();
}
/**
* Opens overlay dialog and shows formatted text content there.
* @param {string} content HTML formatted text to show.
*/
function showTextOverlay(content) {
const doc = appWindow.contentWindow.document;
const textContent = doc.getElementById('overlay-text-content');
textContent.innerHTML = content;
showOverlay('overlay-text');
}
/**
* Opens overlay dialog and shows external URL there.
* @param {string} url Target URL to open in overlay dialog.
*/
function showURLOverlay(url) {
const doc = appWindow.contentWindow.document;
const overlayWebview = doc.getElementById('overlay-url');
overlayWebview.src = url;
showOverlay('overlay-url');
}
/**
* Shows Google Privacy Policy in overlay dialog. Policy link is detected from
* the content of terms view.
*/
function showPrivacyPolicyOverlay() {
const defaultLink =
'https://www.google.com/intl/' + locale + '/policies/privacy/';
if (termsPage.isManaged_) {
showURLOverlay(defaultLink);
return;
}
const details = {code: 'getPrivacyPolicyLink();'};
termsPage.termsView_.executeScript(details, function(results) {
if (results && results.length === 1 && typeof results[0] === 'string') {
showURLOverlay(results[0]);
} else {
showURLOverlay(defaultLink);
}
});
}
/**
* Hides overlay dialog.
*/
function hideOverlay() {
const doc = appWindow.contentWindow.document;
const overlayContainer = doc.getElementById('overlay-container');
overlayContainer.hidden = true;
if (lastFocusedElement) {
lastFocusedElement.focus();
lastFocusedElement = null;
}
}
function setWindowBounds(x, y, width, height) {
if (!appWindow) {
return;
}
let outerWidth = OUTER_WIDTH;
let outerHeight = OUTER_HEIGHT;
if (outerWidth > width) {
outerWidth = width;
}
if (outerHeight > height) {
outerHeight = height;
}
appWindow.outerBounds.width = outerWidth;
appWindow.outerBounds.height = outerHeight;
appWindow.outerBounds.left = Math.ceil(x + (width - outerWidth) / 2);
appWindow.outerBounds.top = Math.ceil(y + (height - outerHeight) / 2);
}
function closeWindow() {
if (appWindow) {
appWindow.close();
}
}
chrome.app.runtime.onLaunched.addListener(function() {
const onAppContentLoad = function() {
const onRetry = function() {
sendNativeMessage('onRetryClicked');
};
const onSendFeedback = function() {
sendNativeMessage('onSendFeedbackClicked');
};
const onRunNetworkTests = function() {
sendNativeMessage('onRunNetworkTestsClicked');
};
const doc = appWindow.contentWindow.document;
doc.getElementById('button-retry').addEventListener('click', onRetry);
doc.getElementById('button-send-feedback')
.addEventListener('click', onSendFeedback);
doc.getElementById('button-run-network-tests')
.addEventListener('click', onRunNetworkTests);
doc.getElementById('overlay-close').addEventListener('click', hideOverlay);
doc.getElementById('privacy-policy-link')
.addEventListener('click', showPrivacyPolicyOverlay);
const overlay = doc.getElementById('overlay-container');
appWindow.contentWindow.cr.ui.overlay.setupOverlay(overlay);
appWindow.contentWindow.cr.ui.overlay.globalInitialization();
overlay.addEventListener('cancelOverlay', hideOverlay);
const overlayWebview = doc.getElementById('overlay-url');
overlayWebview.addEventListener('contentload', function() {
overlay.classList.remove('overlay-loading');
});
overlayWebview.addContentScripts([{
name: 'postProcess',
matches: ['https://support.google.com/*'],
css: {files: ['overlay.css']},
run_at: 'document_end',
}]);
focusManager = new appWindow.contentWindow.ArcOptInFocusManager();
focusManager.initialize();
connectPort();
sendNativeMessage('requestWindowBounds');
};
const onWindowClosed = function() {
appWindow = null;
// Notify to Chrome.
sendNativeMessage('onWindowClosed');
// On window closed, then dispose the extension. So, close the port
// otherwise the background page would be kept alive so that the extension
// would not be unloaded.
port.disconnect();
port = null;
};
const onWindowCreated = function(createdWindow) {
appWindow = createdWindow;
appWindow.contentWindow.onload = onAppContentLoad;
appWindow.onClosed.addListener(onWindowClosed);
};
const options = {
'id': 'play_store_wnd',
'resizable': false,
'hidden': true,
'frame': {type: 'chrome', color: '#ffffff'},
'outerBounds': {'width': OUTER_WIDTH, 'height': OUTER_HEIGHT},
};
chrome.app.window.create('main.html', options, onWindowCreated);
});