summaryrefslogtreecommitdiffstats
path: root/toolkit/components/formautofill/shared/AddressMetaDataLoader.sys.mjs
blob: a7be227921ccadb7d52c72f4301048e3820e1c8d (plain)
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
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  AddressMetaData: "resource://gre/modules/shared/AddressMetaData.sys.mjs",
  AddressMetaDataExtension:
    "resource://gre/modules/shared/AddressMetaDataExtension.sys.mjs",
});

export class AddressMetaDataLoader {
  // Status of address data loading. We'll load all the countries with basic level 1
  // information while requesting conutry information, and set country to true.
  // Level 1 Set is for recording which country's level 1/level 2 data is loaded,
  // since we only load this when getCountryAddressData called with level 1 parameter.
  static dataLoaded = {
    country: false,
    level1: new Set(),
  };

  static addressData = {};

  static DATA_PREFIX = "data/";

  /**
   * Load address meta data and extension into one object.
   *
   * @returns {object}
   *          An object containing address data object with properties from extension.
   */
  static loadAddressMetaData() {
    const addressMetaData = lazy.AddressMetaData;

    for (const key in lazy.AddressMetaDataExtension) {
      let addressDataForKey = addressMetaData[key];
      if (!addressDataForKey) {
        addressDataForKey = addressMetaData[key] = {};
      }

      Object.assign(addressDataForKey, lazy.AddressMetaDataExtension[key]);
    }
    return addressMetaData;
  }

  /**
   * Convert certain properties' string value into array. We should make sure
   * the cached data is parsed.
   *
   * @param   {object} data Original metadata from addressReferences.
   * @returns {object} parsed metadata with property value that converts to array.
   */
  static #parse(data) {
    if (!data) {
      return null;
    }

    const properties = [
      "languages",
      "sub_keys",
      "sub_isoids",
      "sub_names",
      "sub_lnames",
    ];
    for (const key of properties) {
      if (!data[key]) {
        continue;
      }
      // No need to normalize data if the value is array already.
      if (Array.isArray(data[key])) {
        return data;
      }

      data[key] = data[key].split("~");
    }
    return data;
  }

  /**
   * We'll cache addressData in the loader once the data loaded from scripts.
   * It'll become the example below after loading addressReferences with extension:
   * addressData: {
   *               "data/US": {"lang": ["en"], ...// Data defined in libaddressinput metadata
   *                           "alternative_names": ... // Data defined in extension }
   *               "data/CA": {} // Other supported country metadata
   *               "data/TW": {} // Other supported country metadata
   *               "data/TW/台北市": {} // Other supported country level 1 metadata
   *              }
   *
   * @param   {string} country
   * @param   {string?} level1
   * @returns {object} Default locale metadata
   */
  static #loadData(country, level1 = null) {
    // Load the addressData if needed
    if (!this.dataLoaded.country) {
      this.addressData = this.loadAddressMetaData();
      this.dataLoaded.country = true;
    }
    if (!level1) {
      return this.#parse(this.addressData[`${this.DATA_PREFIX}${country}`]);
    }
    // If level1 is set, load addressReferences under country folder with specific
    // country/level 1 for level 2 information.
    if (!this.dataLoaded.level1.has(country)) {
      Object.assign(this.addressData, this.loadAddressMetaData());
      this.dataLoaded.level1.add(country);
    }
    return this.#parse(
      this.addressData[`${this.DATA_PREFIX}${country}/${level1}`]
    );
  }

  /**
   * Return the region metadata with default locale and other locales (if exists).
   *
   * @param   {string} country
   * @param   {string?} level1
   * @returns {object} Return default locale and other locales metadata.
   */
  static getData(country, level1 = null) {
    const defaultLocale = this.#loadData(country, level1);
    if (!defaultLocale) {
      return null;
    }

    const countryData = this.#parse(
      this.addressData[`${this.DATA_PREFIX}${country}`]
    );
    let locales = [];
    // TODO: Should be able to support multi-locale level 1/ level 2 metadata query
    //      in Bug 1421886
    if (countryData.languages) {
      const list = countryData.languages.filter(
        key => key !== countryData.lang
      );
      locales = list.map(key =>
        this.#parse(this.addressData[`${defaultLocale.id}--${key}`])
      );
    }
    return { defaultLocale, locales };
  }

  /**
   * Return an array containing countries alpha2 codes.
   *
   * @returns {Array} Return an array containing countries alpha2 codes.
   */
  static get #countryCodes() {
    return Object.keys(lazy.AddressMetaDataExtension).map(dataKey =>
      dataKey.replace(this.DATA_PREFIX, "")
    );
  }

  static getCountries(locales = []) {
    const displayNames = new Intl.DisplayNames(locales, {
      type: "region",
      fallback: "none",
    });
    const countriesMap = new Map();
    for (const countryCode of this.#countryCodes) {
      countriesMap.set(countryCode, displayNames.of(countryCode));
    }
    return countriesMap;
  }
}

export default AddressMetaDataLoader;