tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AboutWelcomeChild.sys.mjs (11408B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      6 
      7 const lazy = {};
      8 
      9 ChromeUtils.defineESModuleGetters(lazy, {
     10  AboutWelcomeDefaults:
     11    "resource:///modules/aboutwelcome/AboutWelcomeDefaults.sys.mjs",
     12  EnrollmentType: "resource://nimbus/ExperimentAPI.sys.mjs",
     13  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
     14 });
     15 
     16 ChromeUtils.defineLazyGetter(lazy, "log", () => {
     17  const { Logger } = ChromeUtils.importESModule(
     18    "resource://messaging-system/lib/Logger.sys.mjs"
     19  );
     20  return new Logger("AboutWelcomeChild");
     21 });
     22 
     23 const DID_SEE_FINAL_SCREEN_PREF = "browser.aboutwelcome.didSeeFinalScreen";
     24 
     25 XPCOMUtils.defineLazyPreferenceGetter(
     26  lazy,
     27  "toolbarEntrypoint",
     28  "browser.aboutwelcome.entrypoint",
     29  ""
     30 );
     31 
     32 export class AboutWelcomeChild extends JSWindowActorChild {
     33  // Can be used to avoid accesses to the document/contentWindow after it's
     34  // destroyed, which may throw unhandled exceptions.
     35  _destroyed = false;
     36 
     37  didDestroy() {
     38    this._destroyed = true;
     39  }
     40 
     41  actorCreated() {
     42    this.exportFunctions();
     43  }
     44 
     45  /**
     46   * Send event that can be handled by the page
     47   *
     48   * @param {{type: string, data?: any}} action
     49   */
     50  sendToPage(action) {
     51    lazy.log.debug(`Sending to page: ${action.type}`);
     52    const win = this.document.defaultView;
     53    const event = new win.CustomEvent("AboutWelcomeChromeToContent", {
     54      detail: Cu.cloneInto(action, win),
     55    });
     56    win.dispatchEvent(event);
     57  }
     58 
     59  /**
     60   * Export functions that can be called by page js
     61   */
     62  exportFunctions() {
     63    let window = this.contentWindow;
     64 
     65    Cu.exportFunction(this.AWAddScreenImpression.bind(this), window, {
     66      defineAs: "AWAddScreenImpression",
     67    });
     68 
     69    Cu.exportFunction(this.AWGetFeatureConfig.bind(this), window, {
     70      defineAs: "AWGetFeatureConfig",
     71    });
     72 
     73    Cu.exportFunction(this.AWGetFxAMetricsFlowURI.bind(this), window, {
     74      defineAs: "AWGetFxAMetricsFlowURI",
     75    });
     76 
     77    Cu.exportFunction(this.AWGetSelectedTheme.bind(this), window, {
     78      defineAs: "AWGetSelectedTheme",
     79    });
     80 
     81    Cu.exportFunction(this.AWSelectTheme.bind(this), window, {
     82      defineAs: "AWSelectTheme",
     83    });
     84 
     85    Cu.exportFunction(this.AWEvaluateScreenTargeting.bind(this), window, {
     86      defineAs: "AWEvaluateScreenTargeting",
     87    });
     88 
     89    Cu.exportFunction(this.AWEvaluateAttributeTargeting.bind(this), window, {
     90      defineAs: "AWEvaluateAttributeTargeting",
     91    });
     92 
     93    Cu.exportFunction(this.AWSendEventTelemetry.bind(this), window, {
     94      defineAs: "AWSendEventTelemetry",
     95    });
     96 
     97    Cu.exportFunction(this.AWSendToParent.bind(this), window, {
     98      defineAs: "AWSendToParent",
     99    });
    100 
    101    Cu.exportFunction(this.AWWaitForMigrationClose.bind(this), window, {
    102      defineAs: "AWWaitForMigrationClose",
    103    });
    104 
    105    Cu.exportFunction(this.AWFinish.bind(this), window, {
    106      defineAs: "AWFinish",
    107    });
    108 
    109    Cu.exportFunction(this.AWGetInstalledAddons.bind(this), window, {
    110      defineAs: "AWGetInstalledAddons",
    111    });
    112 
    113    Cu.exportFunction(this.AWEnsureAddonInstalled.bind(this), window, {
    114      defineAs: "AWEnsureAddonInstalled",
    115    });
    116 
    117    Cu.exportFunction(this.AWEnsureLangPackInstalled.bind(this), window, {
    118      defineAs: "AWEnsureLangPackInstalled",
    119    });
    120 
    121    Cu.exportFunction(
    122      this.AWNegotiateLangPackForLanguageMismatch.bind(this),
    123      window,
    124      {
    125        defineAs: "AWNegotiateLangPackForLanguageMismatch",
    126      }
    127    );
    128 
    129    Cu.exportFunction(this.AWSetRequestedLocales.bind(this), window, {
    130      defineAs: "AWSetRequestedLocales",
    131    });
    132 
    133    Cu.exportFunction(this.AWSendToDeviceEmailsSupported.bind(this), window, {
    134      defineAs: "AWSendToDeviceEmailsSupported",
    135    });
    136 
    137    Cu.exportFunction(this.AWNewScreen.bind(this), window, {
    138      defineAs: "AWNewScreen",
    139    });
    140 
    141    Cu.exportFunction(this.AWGetUnhandledCampaignAction.bind(this), window, {
    142      defineAs: "AWGetUnhandledCampaignAction",
    143    });
    144 
    145    Cu.exportFunction(
    146      this.AWFindBackupsInWellKnownLocations.bind(this),
    147      window,
    148      { defineAs: "AWFindBackupsInWellKnownLocations" }
    149    );
    150 
    151    Cu.exportFunction(this.RPMGetFormatURLPref.bind(this), window, {
    152      defineAs: "RPMGetFormatURLPref",
    153    });
    154  }
    155 
    156  /**
    157   * Wrap a promise so content can use Promise methods.
    158   */
    159  wrapPromise(promise) {
    160    return new this.contentWindow.Promise((resolve, reject) =>
    161      promise.then(resolve, reject)
    162    );
    163  }
    164 
    165  /**
    166   * Clones the result of the query into the content window.
    167   */
    168  sendQueryAndCloneForContent(...sendQueryArgs) {
    169    return this.wrapPromise(
    170      (async () => {
    171        return Cu.cloneInto(
    172          await this.sendQuery(...sendQueryArgs),
    173          this.contentWindow
    174        );
    175      })()
    176    );
    177  }
    178 
    179  AWSelectTheme(data) {
    180    return this.wrapPromise(
    181      this.sendQuery("AWPage:SELECT_THEME", data.toUpperCase())
    182    );
    183  }
    184 
    185  AWEvaluateScreenTargeting(data) {
    186    return this.sendQueryAndCloneForContent(
    187      "AWPage:EVALUATE_SCREEN_TARGETING",
    188      data
    189    );
    190  }
    191 
    192  AWEvaluateAttributeTargeting(data) {
    193    return this.wrapPromise(
    194      this.sendQuery("AWPage:EVALUATE_ATTRIBUTE_TARGETING", data)
    195    );
    196  }
    197 
    198  AWAddScreenImpression(screen) {
    199    return this.wrapPromise(
    200      this.sendQuery("AWPage:ADD_SCREEN_IMPRESSION", screen)
    201    );
    202  }
    203 
    204  AWFindBackupsInWellKnownLocations(data) {
    205    return this.sendQueryAndCloneForContent(
    206      "AWPage:BACKUP_FIND_WELL_KNOWN",
    207      data
    208    );
    209  }
    210 
    211  /**
    212   * Send initial data to page including experiment information
    213   */
    214  async getAWContent() {
    215    let attributionData = await this.sendQuery("AWPage:GET_ATTRIBUTION_DATA");
    216 
    217    let experimentMetadata =
    218      lazy.NimbusFeatures.aboutwelcome.getEnrollmentMetadata(
    219        lazy.EnrollmentType.EXPERIMENT
    220      ) ?? {};
    221 
    222    lazy.log.debug(
    223      `Loading about:welcome with ${
    224        experimentMetadata?.slug ?? "no"
    225      } experiment`
    226    );
    227 
    228    let featureConfig = lazy.NimbusFeatures.aboutwelcome.getAllVariables();
    229    featureConfig.needDefault = await this.sendQuery("AWPage:NEED_DEFAULT");
    230    featureConfig.needPin = await this.sendQuery("AWPage:DOES_APP_NEED_PIN");
    231    if (featureConfig.languageMismatchEnabled) {
    232      featureConfig.appAndSystemLocaleInfo = await this.sendQuery(
    233        "AWPage:GET_APP_AND_SYSTEM_LOCALE_INFO"
    234      );
    235    }
    236 
    237    // FeatureConfig (from experiments) has higher precendence
    238    // to defaults. But the `screens` property isn't defined we shouldn't
    239    // override the default with `null`
    240    let defaults = lazy.AboutWelcomeDefaults.getDefaults();
    241 
    242    const content = await lazy.AboutWelcomeDefaults.prepareContentForReact({
    243      ...attributionData,
    244      ...experimentMetadata,
    245      ...defaults,
    246      ...featureConfig,
    247      screens: featureConfig.screens ?? defaults.screens,
    248      backdrop: featureConfig.backdrop ?? defaults.backdrop,
    249    });
    250 
    251    return Cu.cloneInto(content, this.contentWindow);
    252  }
    253 
    254  AWGetFeatureConfig() {
    255    return this.wrapPromise(this.getAWContent());
    256  }
    257 
    258  AWGetFxAMetricsFlowURI() {
    259    return this.wrapPromise(this.sendQuery("AWPage:FXA_METRICS_FLOW_URI"));
    260  }
    261 
    262  AWGetSelectedTheme() {
    263    return this.wrapPromise(this.sendQuery("AWPage:GET_SELECTED_THEME"));
    264  }
    265 
    266  /**
    267   * Send Event Telemetry
    268   *
    269   * @param {object} eventData
    270   */
    271  AWSendEventTelemetry(eventData) {
    272    if (lazy.toolbarEntrypoint) {
    273      eventData.event_context.entrypoint = lazy.toolbarEntrypoint;
    274    }
    275    this.AWSendToParent("TELEMETRY_EVENT", {
    276      ...eventData,
    277      event_context: {
    278        ...eventData.event_context,
    279      },
    280    });
    281  }
    282 
    283  /**
    284   * Send message that can be handled by AboutWelcomeParent.sys.mjs
    285   *
    286   * @param {string} type
    287   * @param {any=} data
    288   * @returns {Promise<unknown>}
    289   */
    290  AWSendToParent(type, data) {
    291    return this.sendQueryAndCloneForContent(`AWPage:${type}`, data);
    292  }
    293 
    294  AWWaitForMigrationClose() {
    295    return this.wrapPromise(this.sendQuery("AWPage:WAIT_FOR_MIGRATION_CLOSE"));
    296  }
    297 
    298  setDidSeeFinalScreen() {
    299    this.AWSendToParent("SPECIAL_ACTION", {
    300      type: "SET_PREF",
    301      data: {
    302        pref: {
    303          name: DID_SEE_FINAL_SCREEN_PREF,
    304          value: true,
    305        },
    306      },
    307    });
    308  }
    309 
    310  focusUrlBar() {
    311    this.AWSendToParent("SPECIAL_ACTION", {
    312      type: "FOCUS_URLBAR",
    313    });
    314  }
    315 
    316  AWFinish() {
    317    this.setDidSeeFinalScreen();
    318 
    319    this.contentWindow.location.href = "about:home";
    320    this.focusUrlBar();
    321  }
    322 
    323  AWEnsureAddonInstalled(addonId) {
    324    return this.wrapPromise(
    325      this.sendQuery("AWPage:ENSURE_ADDON_INSTALLED", addonId)
    326    );
    327  }
    328 
    329  AWGetInstalledAddons() {
    330    return this.wrapPromise(
    331      this.sendQueryAndCloneForContent("AWPage:GET_INSTALLED_ADDONS")
    332    );
    333  }
    334 
    335  AWEnsureLangPackInstalled(negotiated, screenContent) {
    336    const content = Cu.cloneInto(screenContent, {});
    337    return this.wrapPromise(
    338      this.sendQuery(
    339        "AWPage:ENSURE_LANG_PACK_INSTALLED",
    340        negotiated.langPack
    341      ).then(() => {
    342        const formatting = [];
    343        const l10n = new Localization(
    344          ["branding/brand.ftl", "browser/newtab/onboarding.ftl"],
    345          false,
    346          undefined,
    347          // Use the system-ish then app then default locale.
    348          [...negotiated.requestSystemLocales, "en-US"]
    349        );
    350 
    351        // Add the negotiated language name as args.
    352        function addMessageArgsAndUseLangPack(obj) {
    353          for (const value of Object.values(obj)) {
    354            if (value?.string_id) {
    355              value.args = {
    356                ...value.args,
    357                negotiatedLanguage: negotiated.langPackDisplayName,
    358              };
    359 
    360              // Expose fluent strings wanting lang pack as raw.
    361              if (value.useLangPack) {
    362                formatting.push(
    363                  l10n.formatValue(value.string_id, value.args).then(raw => {
    364                    delete value.string_id;
    365                    value.raw = raw;
    366                  })
    367                );
    368              }
    369            }
    370          }
    371        }
    372        addMessageArgsAndUseLangPack(content.languageSwitcher);
    373        addMessageArgsAndUseLangPack(content);
    374        return Promise.all(formatting).then(() =>
    375          Cu.cloneInto(content, this.contentWindow)
    376        );
    377      })
    378    );
    379  }
    380 
    381  AWSetRequestedLocales(requestSystemLocales) {
    382    return this.sendQueryAndCloneForContent(
    383      "AWPage:SET_REQUESTED_LOCALES",
    384      requestSystemLocales
    385    );
    386  }
    387 
    388  AWNegotiateLangPackForLanguageMismatch(appAndSystemLocaleInfo) {
    389    return this.sendQueryAndCloneForContent(
    390      "AWPage:NEGOTIATE_LANGPACK",
    391      appAndSystemLocaleInfo
    392    );
    393  }
    394 
    395  AWSendToDeviceEmailsSupported() {
    396    return this.wrapPromise(
    397      this.sendQuery("AWPage:SEND_TO_DEVICE_EMAILS_SUPPORTED")
    398    );
    399  }
    400 
    401  AWNewScreen(screenId) {
    402    return this.wrapPromise(this.sendQuery("AWPage:NEW_SCREEN", screenId));
    403  }
    404 
    405  AWGetUnhandledCampaignAction() {
    406    return this.sendQueryAndCloneForContent(
    407      "AWPage:GET_UNHANDLED_CAMPAIGN_ACTION"
    408    );
    409  }
    410 
    411  RPMGetFormatURLPref(formatURL) {
    412    return Services.urlFormatter.formatURLPref(formatURL);
    413  }
    414 
    415  /**
    416   * @param {{type: string, detail?: any}} event
    417   * @override
    418   */
    419  handleEvent(event) {
    420    lazy.log.debug(`Received page event ${event.type}`);
    421  }
    422 }