File: playstore.js

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (239 lines) | stat: -rw-r--r-- 7,539 bytes parent folder | download | duplicates (6)
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();