Commit f14a9324 authored by Aga Wronska's avatar Aga Wronska Committed by Commit Bot

Load Play Store terms from the disc during offline Demo Mode setup.

Offline ToS are loaded if attempt to load online content fails.
Play Store ToS are region dependent. The proper version is decided
based on language from chosen locale and device region from the VPD.

Dedicated resources are used for most of the regions targeted by v1
Demo Mode:
  * Belgium: nl-BE, fr-BE
  * Finland: fi-FI
  * Sweden: sv-SE
  * Norway: nb-NO
  * Denmark: da-DK
  * France: fr-FR
  * Ireland: en-IE
  * Netherlands: nl-NL
  * Canada: en-CA, fr-CA
  * US: en-US
  * UK - en-GB
  * Luxembourg: eu (no dedicated terms available for de-LU nor fr-LU)

The other regions use default resources:
  * EMEA: emea
  * EU: eu
  * APAC: apac
  * AMERICAS: en-US
  * Germany: de
  * South Korea: kr
  * Anything else defaults to en-US

Information about VPD regions:
https://storage.googleapis.com/chromeos-factory-docs/sdk/regions.html#available-regions

If Demo Mode expands the list of targeted regions dedicated resources
should be added for the new regions.

Bug: 857275
Test: Run wizard controller and demo setup browser tests.
      Run about_ui_unittest.
      Manually perform offline demo mode setup.

Change-Id: I556f08720f6214f1b33be09af9ac232c103b2f14
Reviewed-on: https://chromium-review.googlesource.com/1211737Reviewed-by: default avatarAlexander Alekseev <alemate@chromium.org>
Reviewed-by: default avatarMichael Giuffrida <michaelpg@chromium.org>
Commit-Queue: Aga Wronska <agawronska@chromium.org>
Cr-Commit-Position: refs/heads/master@{#592416}
parent c0045fe4
...@@ -713,6 +713,15 @@ IN_PROC_BROWSER_TEST_F(DemoSetupTest, OfflineSetupFlowSuccess) { ...@@ -713,6 +713,15 @@ IN_PROC_BROWSER_TEST_F(DemoSetupTest, OfflineSetupFlowSuccess) {
ClickScreenDialogButton(OobeScreen::SCREEN_OOBE_EULA, DemoSetupDialog::kEula, ClickScreenDialogButton(OobeScreen::SCREEN_OOBE_EULA, DemoSetupDialog::kEula,
OobeButton::kText, JSExecution::kSync); OobeButton::kText, JSExecution::kSync);
OobeScreenWaiter(OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE).Wait();
EXPECT_TRUE(IsScreenShown(OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE));
SetPlayStoreTermsForTesting();
ClickOobeButtonWithId(OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE,
"#arc-tos-next-button", JSExecution::kSync);
ClickOobeButtonWithId(OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE,
"#arc-tos-accept-button", JSExecution::kAsync);
OobeScreenWaiter(OobeScreen::SCREEN_OOBE_DEMO_SETUP).Wait(); OobeScreenWaiter(OobeScreen::SCREEN_OOBE_DEMO_SETUP).Wait();
// TODO(agawronska): Progress dialog transition is async - extra work is // TODO(agawronska): Progress dialog transition is async - extra work is
// needed to be able to check it reliably. // needed to be able to check it reliably.
...@@ -757,6 +766,15 @@ IN_PROC_BROWSER_TEST_F(DemoSetupTest, OfflineSetupFlowError) { ...@@ -757,6 +766,15 @@ IN_PROC_BROWSER_TEST_F(DemoSetupTest, OfflineSetupFlowError) {
ClickScreenDialogButton(OobeScreen::SCREEN_OOBE_EULA, DemoSetupDialog::kEula, ClickScreenDialogButton(OobeScreen::SCREEN_OOBE_EULA, DemoSetupDialog::kEula,
OobeButton::kText, JSExecution::kSync); OobeButton::kText, JSExecution::kSync);
OobeScreenWaiter(OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE).Wait();
EXPECT_TRUE(IsScreenShown(OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE));
SetPlayStoreTermsForTesting();
ClickOobeButtonWithId(OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE,
"#arc-tos-next-button", JSExecution::kSync);
ClickOobeButtonWithId(OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE,
"#arc-tos-accept-button", JSExecution::kAsync);
OobeScreenWaiter(OobeScreen::SCREEN_OOBE_DEMO_SETUP).Wait(); OobeScreenWaiter(OobeScreen::SCREEN_OOBE_DEMO_SETUP).Wait();
// TODO(agawronska): Progress dialog transition is async - extra work is // TODO(agawronska): Progress dialog transition is async - extra work is
// needed to be able to check it reliably. // needed to be able to check it reliably.
......
...@@ -922,8 +922,9 @@ void WizardController::OnOfflineDemoModeSetup() { ...@@ -922,8 +922,9 @@ void WizardController::OnOfflineDemoModeSetup() {
if (is_official_build_) { if (is_official_build_) {
if (!StartupUtils::IsEulaAccepted()) { if (!StartupUtils::IsEulaAccepted()) {
ShowEulaScreen(); ShowEulaScreen();
} else if (arc::IsArcTermsOfServiceOobeNegotiationNeeded()) {
ShowArcTermsOfServiceScreen();
} else { } else {
// TODO(crbug.com/857275): Show Play Store ToS when available offline.
ShowDemoModeSetupScreen(); ShowDemoModeSetupScreen();
} }
} else { } else {
...@@ -958,15 +959,11 @@ void WizardController::OnEulaAccepted() { ...@@ -958,15 +959,11 @@ void WizardController::OnEulaAccepted() {
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
PerformPostEulaActions(); PerformPostEulaActions();
// TODO(crbug.com/857275): Show Play Store ToS when available offline.
if (demo_setup_controller_ && demo_setup_controller_->IsOfflineEnrollment()) {
ShowDemoModeSetupScreen();
return;
}
if (arc::IsArcTermsOfServiceOobeNegotiationNeeded()) { if (arc::IsArcTermsOfServiceOobeNegotiationNeeded()) {
ShowArcTermsOfServiceScreen(); ShowArcTermsOfServiceScreen();
return; return;
} else if (demo_setup_controller_) {
ShowDemoModeSetupScreen();
} }
if (skip_update_enroll_after_eula_) { if (skip_update_enroll_after_eula_) {
...@@ -1116,7 +1113,11 @@ void WizardController::OnArcTermsOfServiceSkipped() { ...@@ -1116,7 +1113,11 @@ void WizardController::OnArcTermsOfServiceSkipped() {
void WizardController::OnArcTermsOfServiceAccepted() { void WizardController::OnArcTermsOfServiceAccepted() {
if (demo_setup_controller_) { if (demo_setup_controller_) {
InitiateOOBEUpdate(); if (demo_setup_controller_->IsOfflineEnrollment()) {
ShowDemoModeSetupScreen();
} else {
InitiateOOBEUpdate();
}
return; return;
} }
......
...@@ -2201,10 +2201,20 @@ IN_PROC_BROWSER_TEST_F(WizardControllerDemoSetupTest, ...@@ -2201,10 +2201,20 @@ IN_PROC_BROWSER_TEST_F(WizardControllerDemoSetupTest,
EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress()); EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1); EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
EXPECT_CALL(*mock_demo_setup_screen_, Show()).Times(1); EXPECT_CALL(*mock_arc_terms_of_service_screen_, Show()).Times(1);
OnExit(ScreenExitCode::EULA_ACCEPTED); OnExit(ScreenExitCode::EULA_ACCEPTED);
CheckCurrentScreen(OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE);
EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
EXPECT_CALL(*mock_arc_terms_of_service_screen_, Hide()).Times(1);
EXPECT_CALL(*mock_demo_setup_screen_, Show()).Times(1);
OnExit(ScreenExitCode::ARC_TERMS_OF_SERVICE_ACCEPTED);
base::RunLoop().RunUntilIdle();
CheckCurrentScreen(OobeScreen::SCREEN_OOBE_DEMO_SETUP); CheckCurrentScreen(OobeScreen::SCREEN_OOBE_DEMO_SETUP);
EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress()); EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
......
...@@ -9,6 +9,15 @@ a:visited { ...@@ -9,6 +9,15 @@ a:visited {
text-decoration: none; text-decoration: none;
} }
/* Disable links in offline ToS, because they cannot be loaded anyway. */
.offline-terms a,
.offline-terms a:link,
.offline-terms a:visited {
color: rgba(0, 0, 0, 0.87);
pointer-events: none;
text-decoration: none;
}
body { body {
margin: 0; margin: 0;
padding: 8px 4px 8px 8px; padding: 8px 4px 8px 8px;
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
// <include src="oobe_screen_update.js"> // <include src="oobe_screen_update.js">
// <include src="oobe_screen_welcome.js"> // <include src="oobe_screen_welcome.js">
// <include src="multi_tap_detector.js"> // <include src="multi_tap_detector.js">
// <include src="web_view_helper.js">
cr.define('cr.ui.Oobe', function() { cr.define('cr.ui.Oobe', function() {
return { return {
......
...@@ -18,53 +18,6 @@ login.createScreen('EulaScreen', 'eula', function() { ...@@ -18,53 +18,6 @@ login.createScreen('EulaScreen', 'eula', function() {
'}' '}'
}; };
/**
* Load text/html contents from the given url into the given webview. The
* contents is loaded via XHR and is sent to webview via data url so that it
* is properly sandboxed.
*
* @param {!WebView} webview Webview element to host the terms.
* @param {!string} url URL to load terms contents.
*/
function loadUrlToWebview(webview, url) {
assert(webview.tagName === 'WEBVIEW');
var onError = function() {
webview.src = 'about:blank';
};
var setContents = function(contents) {
webview.src =
'data:text/html;charset=utf-8,' + encodeURIComponent(contents);
};
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.setRequestHeader('Accept', 'text/html');
xhr.onreadystatechange = function() {
if (xhr.readyState != 4)
return;
if (xhr.status != 200) {
onError();
return;
}
var contentType = xhr.getResponseHeader('Content-Type');
if (contentType && !/text\/html/.test(contentType)) {
onError();
return;
}
setContents(xhr.response);
};
try {
xhr.send();
} catch (e) {
onError();
}
}
// A class to wrap online contents loading for a webview. A PendingLoad is // A class to wrap online contents loading for a webview. A PendingLoad is
// constructed with a target webview, an url to load, a load timeout and an // constructed with a target webview, an url to load, a load timeout and an
// error callback. It attaches to a "pendingLoad" property of |webview| after // error callback. It attaches to a "pendingLoad" property of |webview| after
...@@ -238,7 +191,7 @@ login.createScreen('EulaScreen', 'eula', function() { ...@@ -238,7 +191,7 @@ login.createScreen('EulaScreen', 'eula', function() {
var TERMS_URL = 'chrome://terms'; var TERMS_URL = 'chrome://terms';
var loadBundledEula = function() { var loadBundledEula = function() {
loadUrlToWebview(webview, TERMS_URL); WebViewHelper.loadUrlToWebview(webview, TERMS_URL);
}; };
webview.addContentScripts([{ webview.addContentScripts([{
......
...@@ -11,7 +11,7 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() { ...@@ -11,7 +11,7 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() {
EXTERNAL_API: [ EXTERNAL_API: [
'setMetricsMode', 'setBackupAndRestoreMode', 'setLocationServicesMode', 'setMetricsMode', 'setBackupAndRestoreMode', 'setLocationServicesMode',
'loadPlayStoreToS', 'setArcManaged', 'hideSkipButton', 'setupForDemoMode', 'loadPlayStoreToS', 'setArcManaged', 'hideSkipButton', 'setupForDemoMode',
'setTosForTesting' 'clearDemoMode', 'setTosForTesting'
], ],
/** @override */ /** @override */
...@@ -207,7 +207,7 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() { ...@@ -207,7 +207,7 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() {
if (this.language_ && this.language_ == language && this.countryCode_ && if (this.language_ && this.language_ == language && this.countryCode_ &&
this.countryCode_ == countryCode && this.countryCode_ == countryCode &&
!this.classList.contains('error')) { !this.classList.contains('error') && !this.usingOfflineTerms_) {
this.enableButtons_(true); this.enableButtons_(true);
return; return;
} }
...@@ -411,6 +411,7 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() { ...@@ -411,6 +411,7 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() {
*/ */
reloadPlayStoreToS: function() { reloadPlayStoreToS: function() {
this.termsError = false; this.termsError = false;
this.usingOfflineTerms_ = false;
var termsView = this.getElement_('arc-tos-view'); var termsView = this.getElement_('arc-tos-view');
termsView.src = 'https://play.google.com/about/play-terms.html'; termsView.src = 'https://play.google.com/about/play-terms.html';
this.removeClass_('arc-tos-loaded'); this.removeClass_('arc-tos-loaded');
...@@ -426,6 +427,13 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() { ...@@ -426,6 +427,13 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() {
this.addClass_('arc-tos-for-demo-mode'); this.addClass_('arc-tos-for-demo-mode');
}, },
/**
* Sets up the variant of the screen dedicated for demo mode.
*/
clearDemoMode: function() {
this.removeClass_('arc-tos-for-demo-mode');
},
/** /**
* Adds new class to the list of classes of root OOBE style. * Adds new class to the list of classes of root OOBE style.
* @param {string} className class to remove. * @param {string} className class to remove.
...@@ -468,9 +476,22 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() { ...@@ -468,9 +476,22 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() {
return; return;
} }
var getToSContent = {code: 'getToSContent();'};
var termsView = this.getElement_('arc-tos-view'); var termsView = this.getElement_('arc-tos-view');
termsView.executeScript(getToSContent, this.onGetToSContent_.bind(this)); if (this.usingOfflineTerms_) {
// Process offline ToS. Scripts added to web view by addContentScripts()
// are not executed when using data url.
this.tosContent_ = termsView.src;
var setParameters =
`document.body.classList.add('large-view', 'offline-terms');`;
termsView.executeScript({code: setParameters});
termsView.insertCSS({file: 'playstore.css'});
this.setTermsViewContentLoadedState_();
} else {
// Process online ToS.
var getToSContent = {code: 'getToSContent();'};
termsView.executeScript(
getToSContent, this.onGetToSContent_.bind(this));
}
}, },
/** /**
...@@ -483,6 +504,15 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() { ...@@ -483,6 +504,15 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() {
} }
this.tosContent_ = results[0]; this.tosContent_ = results[0];
this.setTermsViewContentLoadedState_();
},
/**
* Sets the screen in the loaded state. Should be called after arc terms
* were loaded.
* @private
*/
setTermsViewContentLoadedState_: function() {
this.removeClass_('arc-tos-loading'); this.removeClass_('arc-tos-loading');
this.removeClass_('error'); this.removeClass_('error');
this.addClass_('arc-tos-loaded'); this.addClass_('arc-tos-loaded');
...@@ -522,6 +552,14 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() { ...@@ -522,6 +552,14 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() {
* Handles event when terms view cannot be loaded. * Handles event when terms view cannot be loaded.
*/ */
onTermsViewErrorOccurred: function(details) { onTermsViewErrorOccurred: function(details) {
// If in demo mode fallback to offline Terms of Service copy.
if (this.isDemoModeSetup_()) {
this.usingOfflineTerms_ = true;
const TERMS_URL = 'chrome://terms/arc';
var webView = this.getElement_('arc-tos-view');
WebViewHelper.loadUrlToWebview(webView, TERMS_URL);
return;
}
this.showError_(); this.showError_();
}, },
...@@ -557,7 +595,7 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() { ...@@ -557,7 +595,7 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() {
$('arc-tos-root').onBeforeShow(); $('arc-tos-root').onBeforeShow();
isDemoModeSetup = this.hasClass_('arc-tos-for-demo-mode'); var isDemoModeSetup = this.isDemoModeSetup_();
if (isDemoModeSetup) { if (isDemoModeSetup) {
this.hideSkipButton(); this.hideSkipButton();
this.setMetricsMode( this.setMetricsMode(
...@@ -576,7 +614,6 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() { ...@@ -576,7 +614,6 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() {
/** @override */ /** @override */
onBeforeHide: function() { onBeforeHide: function() {
this.removeClass_('arc-tos-for-demo-mode');
this.reset_(); this.reset_();
}, },
...@@ -678,6 +715,15 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() { ...@@ -678,6 +715,15 @@ login.createScreen('ArcTermsOfServiceScreen', 'arc-tos', function() {
event.stopPropagation(); event.stopPropagation();
self.showLearnMoreOverlay(learnMorePaiServiceText); self.showLearnMoreOverlay(learnMorePaiServiceText);
}; };
},
/**
* Returns whether arc terms are shown as a part of demo mode setup.
* @return {boolean}
* @private
*/
isDemoModeSetup_: function() {
return this.hasClass_('arc-tos-for-demo-mode');
} }
}; };
}); });
// Copyright 2018 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 Web view helper.
*/
/** Web view helper shared between OOBE screens. */
class WebViewHelper {
/**
* Loads text/html contents from the given url into the given webview. The
* content is loaded via XHR and is sent to webview via data url so that it
* is properly sandboxed.
*
* @param {!WebView} webView web view element to host the text/html content.
* @param {string} url URL to load text/html from.
*/
static loadUrlToWebview(webView, url) {
assert(webView.tagName === 'WEBVIEW');
const onError = function() {
webView.src = 'about:blank';
};
const setContents = function(contents) {
webView.src =
'data:text/html;charset=utf-8,' + encodeURIComponent(contents);
};
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.setRequestHeader('Accept', 'text/html');
xhr.onreadystatechange = function() {
if (xhr.readyState != XMLHttpRequest.DONE)
return;
if (xhr.status != 200) {
onError();
return;
}
var contentType = xhr.getResponseHeader('Content-Type');
if (contentType && !contentType.includes('text/html')) {
onError();
return;
}
setContents(xhr.response);
};
try {
xhr.send();
} catch (e) {
onError();
}
}
}
\ No newline at end of file
...@@ -76,8 +76,14 @@ ...@@ -76,8 +76,14 @@
#endif #endif
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
#include <map>
#include "base/stl_util.h"
#include "base/strings/strcat.h"
#include "chrome/browser/browser_process_platform_part_chromeos.h" #include "chrome/browser/browser_process_platform_part_chromeos.h"
#include "chrome/browser/chromeos/customization/customization_document.h" #include "chrome/browser/chromeos/customization/customization_document.h"
#include "chromeos/system/statistics_provider.h"
#include "components/language/core/common/locale_util.h"
#endif #endif
using content::BrowserThread; using content::BrowserThread;
...@@ -92,16 +98,80 @@ constexpr char kStringsJsPath[] = "strings.js"; ...@@ -92,16 +98,80 @@ constexpr char kStringsJsPath[] = "strings.js";
constexpr char kKeyboardUtilsPath[] = "keyboard_utils.js"; constexpr char kKeyboardUtilsPath[] = "keyboard_utils.js";
// Loads bundled Eula contents. The online version of Eula is fetched in Eula // APAC region name.
// screen javascript. This is intentional because chrome://terms runs in a constexpr char kApac[] = "apac";
// privileged webui context and should never load from untrusted places. // EMEA region name.
constexpr char kEmea[] = "emea";
// EU region name.
constexpr char kEu[] = "eu";
// List of countries that belong to APAC.
const char* const kApacCountries[] = {"au", "bd", "cn", "hk", "id", "in", "jp",
"kh", "la", "lk", "mm", "mn", "my", "nz",
"np", "ph", "sg", "th", "tw", "vn"};
// List of countries that belong to EMEA.
const char* const kEmeaCountries[] = {"na", "za", "am", "az", "ch", "eg", "ge",
"il", "is", "ke", "kg", "li", "mk", "no",
"rs", "ru", "tr", "tz", "ua", "ug", "za"};
// List of countries that belong to EU.
const char* const kEuCountries[] = {
"at", "be", "bg", "cz", "dk", "es", "fi", "fr", "gb", "gr", "hr", "hu",
"ie", "it", "lt", "lu", "lv", "nl", "pl", "pt", "ro", "se", "si", "sk"};
// Maps country to one of 3 regions: APAC, EMEA, EU.
typedef std::map<std::string, std::string> CountryRegionMap;
// Returns country to region map with EU, EMEA and APAC countries.
CountryRegionMap CreateCountryRegionMap() {
CountryRegionMap region_map;
for (size_t i = 0; i < base::size(kApacCountries); ++i) {
region_map.emplace(kApacCountries[i], kApac);
}
for (size_t i = 0; i < base::size(kEmeaCountries); ++i) {
region_map.emplace(kEmeaCountries[i], kEmea);
}
for (size_t i = 0; i < base::size(kEuCountries); ++i) {
region_map.emplace(kEuCountries[i], kEu);
}
return region_map;
}
// Reads device region from VPD. Returns "us" in case of read or parsing errors.
std::string ReadDeviceRegionFromVpd() {
std::string region = "us";
chromeos::system::StatisticsProvider* provider =
chromeos::system::StatisticsProvider::GetInstance();
bool region_found =
provider->GetMachineStatistic(chromeos::system::kRegionKey, &region);
if (region_found) {
// We only need the first part of the complex region codes like ca.ansi.
std::vector<std::string> region_pieces = base::SplitString(
region, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (!region_pieces.empty())
region = region_pieces[0];
} else {
LOG(WARNING) << "Device region for Play Store ToS not found in VPD - "
"defaulting to US.";
}
return base::ToLowerASCII(region);
}
// Loads bundled terms of service contents (Eula, OEM Eula, Play Store Terms).
// The online version of terms is fetched in OOBE screen javascript. This is
// intentional because chrome://terms runs in a privileged webui context and
// should never load from untrusted places.
class ChromeOSTermsHandler class ChromeOSTermsHandler
: public base::RefCountedThreadSafe<ChromeOSTermsHandler> { : public base::RefCountedThreadSafe<ChromeOSTermsHandler> {
public: public:
static void Start(const std::string& path, static void Start(const std::string& path,
const content::URLDataSource::GotDataCallback& callback) { const content::URLDataSource::GotDataCallback& callback,
const base::FilePath& chromeos_assets_path) {
scoped_refptr<ChromeOSTermsHandler> handler( scoped_refptr<ChromeOSTermsHandler> handler(
new ChromeOSTermsHandler(path, callback)); new ChromeOSTermsHandler(path, callback, chromeos_assets_path));
handler->StartOnUIThread(); handler->StartOnUIThread();
} }
...@@ -109,12 +179,13 @@ class ChromeOSTermsHandler ...@@ -109,12 +179,13 @@ class ChromeOSTermsHandler
friend class base::RefCountedThreadSafe<ChromeOSTermsHandler>; friend class base::RefCountedThreadSafe<ChromeOSTermsHandler>;
ChromeOSTermsHandler(const std::string& path, ChromeOSTermsHandler(const std::string& path,
const content::URLDataSource::GotDataCallback& callback) const content::URLDataSource::GotDataCallback& callback,
: path_(path), const base::FilePath& chromeos_assets_path)
callback_(callback), : path_(path),
// Previously we were using "initial locale" http://crbug.com/145142 callback_(callback),
locale_(g_browser_process->GetApplicationLocale()) { // Previously we were using "initial locale" http://crbug.com/145142
} locale_(g_browser_process->GetApplicationLocale()),
chromeos_assets_path_(chromeos_assets_path) {}
virtual ~ChromeOSTermsHandler() {} virtual ~ChromeOSTermsHandler() {}
...@@ -126,6 +197,12 @@ class ChromeOSTermsHandler ...@@ -126,6 +197,12 @@ class ChromeOSTermsHandler
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE}, FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&ChromeOSTermsHandler::LoadOemEulaFileAsync, this), base::BindOnce(&ChromeOSTermsHandler::LoadOemEulaFileAsync, this),
base::BindOnce(&ChromeOSTermsHandler::ResponseOnUIThread, this)); base::BindOnce(&ChromeOSTermsHandler::ResponseOnUIThread, this));
} else if (path_ == chrome::kArcTermsURLPath) {
// Load ARC++ terms from the file.
base::PostTaskWithTraitsAndReply(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&ChromeOSTermsHandler::LoadArcTermsFileAsync, this),
base::BindOnce(&ChromeOSTermsHandler::ResponseOnUIThread, this));
} else { } else {
// Load local ChromeOS terms from the file. // Load local ChromeOS terms from the file.
base::PostTaskWithTraitsAndReply( base::PostTaskWithTraitsAndReply(
...@@ -170,11 +247,54 @@ class ChromeOSTermsHandler ...@@ -170,11 +247,54 @@ class ChromeOSTermsHandler
} }
} }
void LoadArcTermsFileAsync() {
base::ScopedBlockingCall scoped_blocking_call(
base::BlockingType::MAY_BLOCK);
const CountryRegionMap kCountryRegionMap = CreateCountryRegionMap();
const std::string kDeviceRegion = ReadDeviceRegionFromVpd();
// To determine version of Play Store ToS:
// * try to match language and device region combination
// * if not found check the mapping to default region (APAC, EMEA, EU)
// * if no default region mapping default to en-US
// Note: AMERICAS region defaults to en-US and to simplify it is not
// included in the country region map.
const std::string locale_region = base::StrCat(
{base::ToLowerASCII(language::ExtractBaseLanguage(locale_)), "-",
kDeviceRegion});
if (base::ReadFileToString(CreateArcTermsPath(locale_region), &contents_))
return;
const auto region = kCountryRegionMap.find(kDeviceRegion);
if (region != kCountryRegionMap.end()) {
LOG(WARNING) << "Could not find offline Play Store ToS for: "
<< locale_region << ". Trying by region: " << region->second;
if (base::ReadFileToString(CreateArcTermsPath(region->second.c_str()),
&contents_)) {
return;
}
}
LOG(WARNING) << "Could not find offline Play Store ToS by locale nor "
"region for: "
<< locale_region << ". Loading: en-US";
if (base::ReadFileToString(CreateArcTermsPath("en-us"), &contents_))
return;
LOG(ERROR) << "Failed to load offline Play Store ToS";
contents_.clear();
}
base::FilePath CreateArcTermsPath(const std::string& locale) const {
return chromeos_assets_path_.Append(
base::StringPrintf(chrome::kArcTermsPathFormat, locale.c_str()));
}
void ResponseOnUIThread() { void ResponseOnUIThread() {
DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_CURRENTLY_ON(BrowserThread::UI);
// If we fail to load Chrome OS EULA from disk, load it from resources. // If we fail to load Chrome OS EULA from disk, load it from resources.
// Do nothing if OEM EULA load failed. // Do nothing if OEM EULA or Play Store ToS load failed.
if (contents_.empty() && path_ != chrome::kOemEulaURLPath) if (contents_.empty() && path_.empty())
contents_ = l10n_util::GetStringUTF8(IDS_TERMS_HTML); contents_ = l10n_util::GetStringUTF8(IDS_TERMS_HTML);
callback_.Run(base::RefCountedString::TakeString(&contents_)); callback_.Run(base::RefCountedString::TakeString(&contents_));
} }
...@@ -188,6 +308,9 @@ class ChromeOSTermsHandler ...@@ -188,6 +308,9 @@ class ChromeOSTermsHandler
// Locale of the EULA. // Locale of the EULA.
const std::string locale_; const std::string locale_;
// Path to Chrome OS assets.
const base::FilePath chromeos_assets_path_;
// EULA contents that was loaded from file. // EULA contents that was loaded from file.
std::string contents_; std::string contents_;
...@@ -463,7 +586,10 @@ void AboutUIHTMLSource::StartDataRequest( ...@@ -463,7 +586,10 @@ void AboutUIHTMLSource::StartDataRequest(
#if !defined(OS_ANDROID) #if !defined(OS_ANDROID)
} else if (source_name_ == chrome::kChromeUITermsHost) { } else if (source_name_ == chrome::kChromeUITermsHost) {
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
ChromeOSTermsHandler::Start(path, callback); std::string assets_dir = chromeos_assets_dir_for_tests_.empty()
? chrome::kChromeOSAssetPath
: chromeos_assets_dir_for_tests_;
ChromeOSTermsHandler::Start(path, callback, base::FilePath(assets_dir));
return; return;
#else #else
response = l10n_util::GetStringUTF8(IDS_TERMS_HTML); response = l10n_util::GetStringUTF8(IDS_TERMS_HTML);
......
...@@ -40,10 +40,19 @@ class AboutUIHTMLSource : public content::URLDataSource { ...@@ -40,10 +40,19 @@ class AboutUIHTMLSource : public content::URLDataSource {
Profile* profile() { return profile_; } Profile* profile() { return profile_; }
// Overrides Chrome OS assets location for tests.
void SetChromeOSAssetsDirForTests(const std::string& path) {
chromeos_assets_dir_for_tests_ = path;
}
private: private:
std::string source_name_; std::string source_name_;
Profile* profile_; Profile* profile_;
// Directory to be used as Chrome OS assets location in tests. If set, it
// overrides chrome::kChromeOSAssetPath.
std::string chromeos_assets_dir_for_tests_;
DISALLOW_COPY_AND_ASSIGN(AboutUIHTMLSource); DISALLOW_COPY_AND_ASSIGN(AboutUIHTMLSource);
}; };
......
// Copyright 2018 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.
#include "chrome/browser/ui/webui/about_ui.h"
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/memory/ref_counted_memory.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/task/post_task.h"
#include "base/test/scoped_task_environment.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/scoped_browser_locale.h"
#include "chromeos/system/fake_statistics_provider.h"
#include "chromeos/system/statistics_provider.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class TestDataReceiver {
public:
TestDataReceiver() = default;
virtual ~TestDataReceiver() = default;
bool data_received() const { return data_received_; }
std::string data() const { return data_; }
void OnDataReceived(scoped_refptr<base::RefCountedMemory> bytes) {
data_received_ = true;
data_ = base::StringPiece(reinterpret_cast<const char*>(bytes->front()),
bytes->size())
.as_string();
}
private:
bool data_received_ = false;
std::string data_;
DISALLOW_COPY_AND_ASSIGN(TestDataReceiver);
};
} // namespace
class ChromeOSTermsTest : public testing::Test {
protected:
ChromeOSTermsTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
~ChromeOSTermsTest() override = default;
void SetUp() override {
// Create root tmp directory for fake ARC ToS data.
base::FilePath root_path;
base::CreateNewTempDirectory(FILE_PATH_LITERAL(""), &root_path);
ASSERT_TRUE(root_dir_.Set(root_path));
arc_tos_dir_ = root_dir_.GetPath().Append("arc_tos");
ASSERT_TRUE(base::CreateDirectory(arc_tos_dir_));
tested_html_source_ = std::make_unique<AboutUIHTMLSource>(
chrome::kChromeUITermsHost, nullptr);
tested_html_source_->SetChromeOSAssetsDirForTests(
root_dir_.GetPath().value());
}
// Creates directory for the given |locale| that contains terms.html. Writes
// the |locale| string to the created file.
bool CreateTermsForLocale(const std::string& locale) {
base::FilePath dir = arc_tos_dir_.Append(base::ToLowerASCII(locale));
if (!base::CreateDirectory(dir))
return false;
if (base::WriteFile(dir.AppendASCII("terms.html"), locale.c_str(),
locale.length()) != static_cast<int>(locale.length())) {
return false;
}
return true;
}
// Sets device region in VPD.
void SetRegion(const std::string& region) {
statistics_provider_.SetMachineStatistic(chromeos::system::kRegionKey,
region);
}
// Starts data request.
void StartRequest(TestDataReceiver* data_receiver) {
content::ResourceRequestInfo::WebContentsGetter wc_getter;
tested_html_source_->StartDataRequest(
chrome::kArcTermsURLPath, std::move(wc_getter),
base::BindRepeating(&TestDataReceiver::OnDataReceived,
base::Unretained(data_receiver)));
scoped_task_environment_.RunUntilIdle();
}
private:
base::ScopedTempDir root_dir_;
base::FilePath arc_tos_dir_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
content::TestBrowserThreadBundle test_browser_thread_bundle_;
chromeos::system::ScopedFakeStatisticsProvider statistics_provider_;
std::unique_ptr<AboutUIHTMLSource> tested_html_source_;
DISALLOW_COPY_AND_ASSIGN(ChromeOSTermsTest);
};
TEST_F(ChromeOSTermsTest, NoData) {
SetRegion("ca");
ScopedBrowserLocale browser_locale("en-CA");
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ("", data_receiver.data());
}
class DemoModeChromeOSTermsTest : public ChromeOSTermsTest {
protected:
DemoModeChromeOSTermsTest() = default;
~DemoModeChromeOSTermsTest() override = default;
void SetUp() override {
ChromeOSTermsTest::SetUp();
AddDemoModeLocale();
}
// Adds locales supported by demo mode.
void AddDemoModeLocale() {
ASSERT_TRUE(CreateTermsForLocale("apac"));
ASSERT_TRUE(CreateTermsForLocale("da-DK"));
ASSERT_TRUE(CreateTermsForLocale("de-de"));
ASSERT_TRUE(CreateTermsForLocale("emea"));
ASSERT_TRUE(CreateTermsForLocale("en-CA"));
ASSERT_TRUE(CreateTermsForLocale("en-GB"));
ASSERT_TRUE(CreateTermsForLocale("en-IE"));
ASSERT_TRUE(CreateTermsForLocale("en-US"));
ASSERT_TRUE(CreateTermsForLocale("eu"));
ASSERT_TRUE(CreateTermsForLocale("fi-FI"));
ASSERT_TRUE(CreateTermsForLocale("fr-BE"));
ASSERT_TRUE(CreateTermsForLocale("fr-CA"));
ASSERT_TRUE(CreateTermsForLocale("fr-FR"));
ASSERT_TRUE(CreateTermsForLocale("ko-KR"));
ASSERT_TRUE(CreateTermsForLocale("nb-NO"));
ASSERT_TRUE(CreateTermsForLocale("nl-BE"));
ASSERT_TRUE(CreateTermsForLocale("nl-NL"));
ASSERT_TRUE(CreateTermsForLocale("sv-SE"));
}
private:
DISALLOW_COPY_AND_ASSIGN(DemoModeChromeOSTermsTest);
};
TEST_F(DemoModeChromeOSTermsTest, SimpleRegion) {
SetRegion("ca");
for (const char* locale : {"en-CA", "fr-CA"}) {
ScopedBrowserLocale browser_locale(locale);
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ(locale, data_receiver.data());
}
}
TEST_F(DemoModeChromeOSTermsTest, ComplexRegion) {
const std::string kLocale = "en-CA";
ScopedBrowserLocale browser_locale(kLocale);
for (const char* region :
{"ca.hybridansi", "ca.ansi", "ca.multix", "ca.fr"}) {
SetRegion(region);
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ(kLocale, data_receiver.data());
}
}
TEST_F(DemoModeChromeOSTermsTest, NotCaseSensitive) {
SetRegion("CA");
for (const char* locale : {"EN-CA", "en-CA", "EN-ca"}) {
ScopedBrowserLocale browser_locale(locale);
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ("en-CA", data_receiver.data());
}
}
TEST_F(DemoModeChromeOSTermsTest, DefaultToEuRegion) {
const std::string kLocale = "pl-PL";
ScopedBrowserLocale browser_locale(kLocale);
SetRegion("pl");
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ("eu", data_receiver.data());
}
TEST_F(DemoModeChromeOSTermsTest, DefaultToEmeaRegion) {
const std::string kLocale = "fr-CH";
ScopedBrowserLocale browser_locale(kLocale);
SetRegion("ch");
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ("emea", data_receiver.data());
}
TEST_F(DemoModeChromeOSTermsTest, DefaultToApacRegion) {
const std::string kLocale = "en-PH";
ScopedBrowserLocale browser_locale(kLocale);
SetRegion("ph");
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ("apac", data_receiver.data());
}
TEST_F(DemoModeChromeOSTermsTest, DefaultToAmericasRegion) {
const std::string kLocale = "en-MX";
ScopedBrowserLocale browser_locale(kLocale);
SetRegion("mx");
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ("en-US", data_receiver.data());
}
TEST_F(DemoModeChromeOSTermsTest, DefaultToEnUs) {
const std::string kLocale = "en-SA";
ScopedBrowserLocale browser_locale(kLocale);
SetRegion("sa");
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ("en-US", data_receiver.data());
}
TEST_F(DemoModeChromeOSTermsTest, NoLangCountryCombination) {
SetRegion("be");
{
const std::string kLocale = "nl-BE";
ScopedBrowserLocale browser_locale(kLocale);
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ(kLocale, data_receiver.data());
}
{
const std::string kLocale = "de-BE";
ScopedBrowserLocale browser_locale(kLocale);
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
// No language - country combination - defaults to region.
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ("eu", data_receiver.data());
}
}
TEST_F(DemoModeChromeOSTermsTest, InvalidRegion) {
const std::string kLocale = "da-DK";
ScopedBrowserLocale browser_locale(kLocale);
for (const char* region : {"", " ", ".", "..", "-", "xyz"}) {
SetRegion(region);
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ("en-US", data_receiver.data());
}
}
TEST_F(DemoModeChromeOSTermsTest, InvalidLocale) {
SetRegion("se");
for (const char* locale : {"", " ", ".", "-", "-sv"}) {
ScopedBrowserLocale browser_locale(locale);
TestDataReceiver data_receiver;
StartRequest(&data_receiver);
EXPECT_TRUE(data_receiver.data_received());
EXPECT_EQ("eu", data_receiver.data());
}
}
...@@ -246,6 +246,8 @@ void ArcTermsOfServiceScreenHandler::DoShow() { ...@@ -246,6 +246,8 @@ void ArcTermsOfServiceScreenHandler::DoShow() {
Profile* profile = ProfileManager::GetActiveUserProfile(); Profile* profile = ProfileManager::GetActiveUserProfile();
CHECK(profile); CHECK(profile);
CallJS("clearDemoMode");
// Enable ARC to match ArcSessionManager logic. ArcSessionManager expects that // Enable ARC to match ArcSessionManager logic. ArcSessionManager expects that
// ARC is enabled (prefs::kArcEnabled = true) on showing Terms of Service. If // ARC is enabled (prefs::kArcEnabled = true) on showing Terms of Service. If
// user accepts ToS then prefs::kArcEnabled is left activated. If user skips // user accepts ToS then prefs::kArcEnabled is left activated. If user skips
......
...@@ -269,6 +269,8 @@ const char kEasyUnlockLearnMoreUrl[] = ...@@ -269,6 +269,8 @@ const char kEasyUnlockLearnMoreUrl[] =
const char kEULAPathFormat[] = "/usr/share/chromeos-assets/eula/%s/eula.html"; const char kEULAPathFormat[] = "/usr/share/chromeos-assets/eula/%s/eula.html";
const char kArcTermsPathFormat[] = "arc_tos/%s/terms.html";
const char kEolNotificationURL[] = "https://www.google.com/chromebook/older/"; const char kEolNotificationURL[] = "https://www.google.com/chromebook/older/";
const char kGoogleNameserversLearnMoreURL[] = const char kGoogleNameserversLearnMoreURL[] =
...@@ -300,6 +302,8 @@ const char kNaturalScrollHelpURL[] = ...@@ -300,6 +302,8 @@ const char kNaturalScrollHelpURL[] =
const char kOemEulaURLPath[] = "oem"; const char kOemEulaURLPath[] = "oem";
const char kArcTermsURLPath[] = "arc";
const char kOnlineEulaURLPath[] = const char kOnlineEulaURLPath[] =
"https://www.google.com/intl/%s/chrome/eula_text.html"; "https://www.google.com/intl/%s/chrome/eula_text.html";
......
...@@ -216,8 +216,13 @@ extern const char kCupsPrintLearnMoreURL[]; ...@@ -216,8 +216,13 @@ extern const char kCupsPrintLearnMoreURL[];
// The URL for the "Learn more" link the the Easy Unlock settings. // The URL for the "Learn more" link the the Easy Unlock settings.
extern const char kEasyUnlockLearnMoreUrl[]; extern const char kEasyUnlockLearnMoreUrl[];
// The path to the offline Chrome OS EULA.
extern const char kEULAPathFormat[]; extern const char kEULAPathFormat[];
// The path format to the localized offline ARC++ Terms of Service.
// Relative to |kChromeOSAssetPath|.
extern const char kArcTermsPathFormat[];
// The URL for EOL notification // The URL for EOL notification
extern const char kEolNotificationURL[]; extern const char kEolNotificationURL[];
...@@ -248,8 +253,12 @@ extern const char kLinuxCreditsPath[]; ...@@ -248,8 +253,12 @@ extern const char kLinuxCreditsPath[];
// The URL for the "Learn more" link for natural scrolling on ChromeOS. // The URL for the "Learn more" link for natural scrolling on ChromeOS.
extern const char kNaturalScrollHelpURL[]; extern const char kNaturalScrollHelpURL[];
// The URL path to offline OEM EULA.
extern const char kOemEulaURLPath[]; extern const char kOemEulaURLPath[];
// THE URL path to offline ARC++ Terms of Service.
extern const char kArcTermsURLPath[];
extern const char kOnlineEulaURLPath[]; extern const char kOnlineEulaURLPath[];
// The URL for the "learn more" link for TPM firmware update. // The URL for the "learn more" link for TPM firmware update.
......
...@@ -3821,6 +3821,7 @@ test("unit_tests") { ...@@ -3821,6 +3821,7 @@ test("unit_tests") {
"../browser/chromeos/login/easy_unlock/easy_unlock_screenlock_state_handler_unittest.cc", "../browser/chromeos/login/easy_unlock/easy_unlock_screenlock_state_handler_unittest.cc",
"../browser/chromeos/login/easy_unlock/easy_unlock_service_unittest_chromeos.cc", "../browser/chromeos/login/easy_unlock/easy_unlock_service_unittest_chromeos.cc",
"../browser/extensions/api/file_system/consent_provider_unittest.cc", "../browser/extensions/api/file_system/consent_provider_unittest.cc",
"../browser/ui/webui/about_ui_unittest.cc",
] ]
} else { } else {
sources += [ sources += [
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment