1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
|
// 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.
/**
* Returns the Play Store footer element that can be detected by id or class
* name.
*/
function getPlayFooterElement() {
const elements = document.getElementsByClassName('glue-footer');
if (!elements || elements.length === 0) {
console.error('Failed to find play-footer element in ToS.');
return null;
}
if (elements.length !== 1) {
console.error('Found more than one play-footer element in ToS.');
}
return elements[0];
}
/**
* Returns the select element that controls zone/language selection.
*/
function getLangZoneSelect() {
const footer = getPlayFooterElement();
if (!footer) {
return null;
}
const elements = footer.getElementsByTagName('select');
if (!elements || elements.length === 0) {
console.error('Cannot find zone/language select select element');
return null;
}
if (elements.length !== 1) {
console.error('Found more than one zone/language select element in ToS.');
}
return elements[0];
}
/**
* Analyzes current document and tries to find the link to the Play Store ToS
* that matches requested |language| and |countryCode|. Once found, navigate
* to this link and returns True. If no match was found then returns False.
*/
function navigateToLanguageAndCountryCode(language, countryCode) {
const selectLangZoneTerms = getLangZoneSelect();
if (!selectLangZoneTerms) {
// Layout is not recognized, cannot check document structure.
return false;
}
const applyTermsForLangAndZone = function(termsLang) {
// Check special case for en_us which may be mapped to en.
const matchDefaultUs = null;
if (window.location.href.startsWith(
'https://play.google/intl/en_us/play-terms') &&
termsLang === 'en' && countryCode === 'us' &&
selectLangZoneTerms.value.startsWith('/intl/en/play-terms')) {
return true;
}
const matchByLangZone =
'/intl/' + termsLang + '_' + countryCode + '/play-terms';
if (selectLangZoneTerms.value.startsWith(matchByLangZone)) {
// Already selected what is needed.
return true;
}
for (let i = selectLangZoneTerms.options.length - 1; i >= 0; --i) {
const option = selectLangZoneTerms.options[i];
if (option.value.startsWith(matchByLangZone)) {
window.location.href = option.value;
return true;
}
}
return false;
};
// Try two versions of the language, full and short (if it exists, for
// example en-GB -> en). Note, terms may contain entries for both types, for
// example: en_ie, es-419_ar, es_as, pt-PT_pt.
if (applyTermsForLangAndZone(language)) {
return true;
}
const langSegments = language.split('-');
if (langSegments.length === 2 && applyTermsForLangAndZone(langSegments[0])) {
return true;
}
return false;
}
/**
* Processes select tag that contains list of available terms for different
* languages and zones. In case of initial load, tries to find terms that match
* exactly current language and country code and automatically redirects the
* view in case such terms are found. Leaves terms in select tag that only match
* current language or country code or default English variant or currently
* selected.
*
* @return {boolean} True.
*/
function processLangZoneTerms(initialLoad, language, countryCode) {
const langSegments = language.split('-');
if (initialLoad && navigateToLanguageAndCountryCode(language, countryCode)) {
document.body.hidden = false;
return true;
}
const footer = getPlayFooterElement();
if (!footer) {
// Layout is not recognized, show content and stop processing.
document.body.hidden = false;
return true;
}
const matchByLang = '/intl/' + language + '_';
let matchByLangShort = null;
if (langSegments.length === 2) {
matchByLangShort = '/intl/' + langSegments[0] + '_';
}
const matchByZone = '_' + countryCode + '/play-terms';
const matchByDefault = '/intl/en/play-terms';
// We are allowed to display terms by default only in language that matches
// current UI language. In other cases we have to switch to default version.
let langMatch = false;
let defaultExist = false;
const selectLangZoneTerms = getLangZoneSelect();
if (!selectLangZoneTerms) {
document.body.hidden = false;
return;
}
for (let i = selectLangZoneTerms.options.length - 1; i >= 0; --i) {
const option = selectLangZoneTerms.options[i];
if (selectLangZoneTerms.selectedIndex === i) {
langMatch = option.value.startsWith(matchByLang) ||
(matchByLangShort && option.value.startsWith(matchByLangShort));
continue;
}
if (option.value.startsWith(matchByDefault)) {
defaultExist = true;
continue;
}
option.hidden = !option.value.startsWith(matchByLang) &&
!option.value.includes(matchByZone) &&
!(matchByLangShort && option.value.startsWith(matchByLangShort)) &&
option.text !== 'English';
}
if (initialLoad && !langMatch && defaultExist) {
window.location.href = matchByDefault;
} else {
// Show content once we reached target url.
document.body.hidden = false;
}
return true;
}
/**
* Returns the raw body HTML of the ToS contents.
* @return {string} HTML of document body.
*/
function getToSContent() {
return document.body.innerHTML;
}
/**
* Formats current document in order to display it correctly.
*/
function formatDocument() {
if (document.viewMode) {
document.body.classList.add(document.viewMode);
}
// playstore.css is injected into the document and it is applied first.
// Need to remove existing links that contain references to external
// stylesheets which override playstore.css.
const links = document.head.getElementsByTagName('link');
for (let i = links.length - 1; i >= 0; --i) {
document.head.removeChild(links[i]);
}
// Create base element that forces internal links to be opened in new window.
const base = document.createElement('base');
base.target = '_blank';
document.head.appendChild(base);
// Hide content at this point. We might want to redirect our view to terms
// that exactly match current language and country code.
document.body.hidden = true;
}
/**
* Searches in footer for a privacy policy link.
* @return {string} Link to Google Privacy Policy detected from the current
* document or link to the default policy if it is not found.
*/
function getPrivacyPolicyLink() {
const footer = getPlayFooterElement();
if (footer) {
const links = footer.getElementsByTagName('a');
for (let i = 0; i < links.length; ++i) {
const targetURL = links[i].href;
if (targetURL.endsWith('/policies/privacy/')) {
return targetURL;
}
}
}
return 'https://www.google.com/policies/privacy/';
}
/**
* Processes the current document by applying required formatting and selected
* right PlayStore ToS.
* Note that document.countryCode must be set before calling this function.
*/
function processDocument() {
if (document.wasProcessed) {
return;
}
formatDocument();
const initialLoad =
window.location.href.startsWith('https://play.google/play-terms');
let language = document.language;
if (!language) {
language = navigator.language;
}
processLangZoneTerms(initialLoad, language, document.countryCode);
document.wasProcessed = true;
}
processDocument();
|