tor-browser

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

storage.sys.mjs (28146B)


      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 { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";
      6 
      7 const lazy = {};
      8 
      9 ChromeUtils.defineESModuleGetters(lazy, {
     10  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
     11  BytesValueType:
     12    "chrome://remote/content/webdriver-bidi/modules/root/network.sys.mjs",
     13  deserializeBytesValue:
     14    "chrome://remote/content/webdriver-bidi/modules/root/network.sys.mjs",
     15  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
     16  pprint: "chrome://remote/content/shared/Format.sys.mjs",
     17  UserContextManager:
     18    "chrome://remote/content/shared/UserContextManager.sys.mjs",
     19 });
     20 
     21 const PREF_COOKIE_CHIPS_ENABLED = "network.cookie.CHIPS.enabled";
     22 const PREF_COOKIE_BEHAVIOR = "network.cookie.cookieBehavior";
     23 
     24 // This is a static preference, so it cannot be modified during runtime and we can cache its value.
     25 ChromeUtils.defineLazyGetter(lazy, "cookieCHIPSEnabled", () =>
     26  Services.prefs.getBoolPref(PREF_COOKIE_CHIPS_ENABLED)
     27 );
     28 
     29 const CookieFieldsMapping = {
     30  domain: "host",
     31  expiry: "expiry",
     32  httpOnly: "isHttpOnly",
     33  name: "name",
     34  path: "path",
     35  sameSite: "sameSite",
     36  secure: "isSecure",
     37  size: "size",
     38  value: "value",
     39 };
     40 
     41 const MAX_COOKIE_EXPIRY = Number.MAX_SAFE_INTEGER;
     42 
     43 /**
     44 * Enum of possible partition types supported by the
     45 * storage.getCookies command.
     46 *
     47 * @readonly
     48 * @enum {PartitionType}
     49 */
     50 const PartitionType = {
     51  Context: "context",
     52  StorageKey: "storageKey",
     53 };
     54 
     55 const PartitionKeyAttributes = ["sourceOrigin", "userContext"];
     56 
     57 /**
     58 * Enum of possible SameSite types supported by the
     59 * storage.getCookies command.
     60 *
     61 * @readonly
     62 * @enum {SameSiteType}
     63 */
     64 const SameSiteType = {
     65  [Ci.nsICookie.SAMESITE_NONE]: "none",
     66  [Ci.nsICookie.SAMESITE_LAX]: "lax",
     67  [Ci.nsICookie.SAMESITE_STRICT]: "strict",
     68  [Ci.nsICookie.SAMESITE_UNSET]: "default",
     69 };
     70 
     71 class StorageModule extends RootBiDiModule {
     72  destroy() {}
     73 
     74  /**
     75   * Used as an argument for storage.getCookies command
     76   * to represent fields which should be used to filter the output
     77   * of the command.
     78   *
     79   * @typedef CookieFilter
     80   *
     81   * @property {string=} domain
     82   * @property {number=} expiry
     83   * @property {boolean=} httpOnly
     84   * @property {string=} name
     85   * @property {string=} path
     86   * @property {SameSiteType=} sameSite
     87   * @property {boolean=} secure
     88   * @property {number=} size
     89   * @property {Network.BytesValueType=} value
     90   */
     91 
     92  /**
     93   * Used as an argument for storage.getCookies command as one of the available variants
     94   * {BrowsingContextPartitionDescriptor} or {StorageKeyPartitionDescriptor}, to represent
     95   * fields should be used to build a partition key.
     96   *
     97   * @typedef PartitionDescriptor
     98   */
     99 
    100  /**
    101   * @typedef BrowsingContextPartitionDescriptor
    102   *
    103   * @property {PartitionType} [type=PartitionType.context]
    104   * @property {string} context
    105   */
    106 
    107  /**
    108   * @typedef StorageKeyPartitionDescriptor
    109   *
    110   * @property {PartitionType} [type=PartitionType.storageKey]
    111   * @property {string=} sourceOrigin
    112   * @property {string=} userContext
    113   */
    114 
    115  /**
    116   * @typedef PartitionKey
    117   *
    118   * @property {string=} sourceOrigin
    119   * @property {string=} userContext
    120   */
    121 
    122  /**
    123   * An object that holds the result of storage.getCookies command.
    124   *
    125   * @typedef GetCookiesResult
    126   *
    127   * @property {Array<Cookie>} cookies
    128   *    List of cookies.
    129   * @property {PartitionKey} partitionKey
    130   *    An object which represent the partition key which was used
    131   *    to retrieve the cookies.
    132   */
    133 
    134  /**
    135   * Remove zero or more cookies which match a set of provided parameters.
    136   *
    137   * @param {object=} options
    138   * @param {CookieFilter=} options.filter
    139   *     An object which holds field names and values, which
    140   *     should be used to filter the output of the command.
    141   * @param {PartitionDescriptor=} options.partition
    142   *     An object which holds the information which
    143   *     should be used to build a partition key.
    144   *
    145   * @returns {PartitionKey}
    146   *     An object with the partition key which was used to
    147   *     retrieve cookies which had to be removed.
    148   * @throws {InvalidArgumentError}
    149   *     If the provided arguments are not valid.
    150   * @throws {NoSuchFrameError}
    151   *     If the provided browsing context cannot be found.
    152   */
    153  async deleteCookies(options = {}) {
    154    let { filter = {} } = options;
    155    const { partition: partitionSpec = null } = options;
    156 
    157    this.#assertPartition(partitionSpec);
    158    filter = this.#assertCookieFilter(filter);
    159 
    160    const partitionKey = this.#expandStoragePartitionSpec(partitionSpec);
    161    const store = this.#getTheCookieStore(partitionKey);
    162    const cookies = this.#getMatchingCookies(store, filter);
    163 
    164    for (const cookie of cookies) {
    165      Services.cookies.remove(
    166        cookie.host,
    167        cookie.name,
    168        cookie.path,
    169        cookie.originAttributes
    170      );
    171    }
    172 
    173    return { partitionKey: this.#formatPartitionKey(partitionKey) };
    174  }
    175 
    176  /**
    177   * Retrieve zero or more cookies which match a set of provided parameters.
    178   *
    179   * @param {object=} options
    180   * @param {CookieFilter=} options.filter
    181   *     An object which holds field names and values, which
    182   *     should be used to filter the output of the command.
    183   * @param {PartitionDescriptor=} options.partition
    184   *     An object which holds the information which
    185   *     should be used to build a partition key.
    186   *
    187   * @returns {GetCookiesResult}
    188   *     An object which holds a list of retrieved cookies and
    189   *     the partition key which was used.
    190   * @throws {InvalidArgumentError}
    191   *     If the provided arguments are not valid.
    192   * @throws {NoSuchFrameError}
    193   *     If the provided browsing context cannot be found.
    194   */
    195  async getCookies(options = {}) {
    196    let { filter = {} } = options;
    197    const { partition: partitionSpec = null } = options;
    198 
    199    this.#assertPartition(partitionSpec);
    200    filter = this.#assertCookieFilter(filter);
    201 
    202    const partitionKey = this.#expandStoragePartitionSpec(partitionSpec);
    203    const store = this.#getTheCookieStore(partitionKey);
    204    const cookies = this.#getMatchingCookies(store, filter);
    205    const serializedCookies = [];
    206 
    207    for (const cookie of cookies) {
    208      serializedCookies.push(this.#serializeCookie(cookie));
    209    }
    210 
    211    return {
    212      cookies: serializedCookies,
    213      partitionKey: this.#formatPartitionKey(partitionKey),
    214    };
    215  }
    216 
    217  /**
    218   * An object representation of the cookie which should be set.
    219   *
    220   * @typedef PartialCookie
    221   *
    222   * @property {string} domain
    223   * @property {number=} expiry
    224   * @property {boolean=} httpOnly
    225   * @property {string} name
    226   * @property {string=} path
    227   * @property {SameSiteType=} sameSite
    228   * @property {boolean=} secure
    229   * @property {number=} size
    230   * @property {Network.BytesValueType} value
    231   */
    232 
    233  /**
    234   * Create a new cookie in a cookie store.
    235   *
    236   * @param {object=} options
    237   * @param {PartialCookie} options.cookie
    238   *     An object representation of the cookie which
    239   *     should be set.
    240   * @param {PartitionDescriptor=} options.partition
    241   *     An object which holds the information which
    242   *     should be used to build a partition key.
    243   *
    244   * @returns {PartitionKey}
    245   *     An object with the partition key which was used to
    246   *     add the cookie.
    247   * @throws {InvalidArgumentError}
    248   *     If the provided arguments are not valid.
    249   * @throws {NoSuchFrameError}
    250   *     If the provided browsing context cannot be found.
    251   * @throws {UnableToSetCookieError}
    252   *     If the cookie was not added.
    253   */
    254  async setCookie(options = {}) {
    255    const { cookie: cookieSpec, partition: partitionSpec = null } = options;
    256    lazy.assert.object(
    257      cookieSpec,
    258      lazy.pprint`Expected "cookie" to be an object, got ${cookieSpec}`
    259    );
    260 
    261    const {
    262      domain,
    263      expiry = null,
    264      httpOnly = null,
    265      name,
    266      path = null,
    267      sameSite = null,
    268      secure = null,
    269      value,
    270    } = cookieSpec;
    271    this.#assertCookie({
    272      domain,
    273      expiry,
    274      httpOnly,
    275      name,
    276      path,
    277      sameSite,
    278      secure,
    279      value,
    280    });
    281    this.#assertPartition(partitionSpec);
    282 
    283    const partitionKey = this.#expandStoragePartitionSpec(partitionSpec);
    284 
    285    // The cookie store is defined by originAttributes.
    286    const originAttributes = this.#getOriginAttributes(partitionKey, domain);
    287 
    288    // The cookie value is a network.BytesValue.
    289    const deserializedValue = lazy.deserializeBytesValue(value);
    290 
    291    // The XPCOM interface requires to be specified if a cookie is session.
    292    const isSession = expiry === null;
    293 
    294    let schemeType;
    295    if (secure) {
    296      schemeType = Ci.nsICookie.SCHEME_HTTPS;
    297    } else {
    298      schemeType = Ci.nsICookie.SCHEME_HTTP;
    299    }
    300 
    301    const isPartitioned = originAttributes.partitionKey?.length > 0;
    302 
    303    let cv;
    304    try {
    305      cv = Services.cookies.add(
    306        domain,
    307        path === null ? "/" : path,
    308        name,
    309        deserializedValue,
    310        secure === null ? false : secure,
    311        httpOnly === null ? false : httpOnly,
    312        isSession,
    313        // The XPCOM interface requires the expiry field even for session cookies.
    314        // The expiry value must be passed in milliseconds and is capped at 400
    315        // days.
    316        expiry === null
    317          ? MAX_COOKIE_EXPIRY
    318          : Services.cookies.maybeCapExpiry(expiry * 1000),
    319        originAttributes,
    320        this.#getSameSitePlatformProperty(sameSite),
    321        schemeType,
    322        isPartitioned
    323      );
    324    } catch (e) {
    325      throw new lazy.error.UnableToSetCookieError(e);
    326    }
    327 
    328    if (cv.result !== Ci.nsICookieValidation.eOK) {
    329      throw new lazy.error.UnableToSetCookieError(
    330        `Invalid cookie: ${cv.errorString}`
    331      );
    332    }
    333 
    334    return {
    335      partitionKey: this.#formatPartitionKey(partitionKey, originAttributes),
    336    };
    337  }
    338 
    339  #assertCookie(cookie) {
    340    lazy.assert.object(
    341      cookie,
    342      lazy.pprint`Expected "cookie" to be an object, got ${cookie}`
    343    );
    344 
    345    const { domain, expiry, httpOnly, name, path, sameSite, secure, value } =
    346      cookie;
    347 
    348    lazy.assert.string(
    349      domain,
    350      lazy.pprint`Expected cookie "domain" to be a string, got ${domain}`
    351    );
    352 
    353    lazy.assert.string(
    354      name,
    355      lazy.pprint`Expected cookie "name" to be a string, got ${name}`
    356    );
    357 
    358    this.#assertValue(value);
    359 
    360    if (expiry !== null) {
    361      lazy.assert.positiveInteger(
    362        expiry,
    363        lazy.pprint`Expected cookie "expiry" to be a positive integer, got ${expiry}`
    364      );
    365    }
    366 
    367    if (httpOnly !== null) {
    368      lazy.assert.boolean(
    369        httpOnly,
    370        lazy.pprint`Expected cookie "httpOnly" to be a boolean, got ${httpOnly}`
    371      );
    372    }
    373 
    374    if (path !== null) {
    375      lazy.assert.string(
    376        path,
    377        lazy.pprint`Expected cookie "path" to be a string, got ${path}`
    378      );
    379    }
    380 
    381    this.#assertSameSite(sameSite);
    382 
    383    if (secure !== null) {
    384      lazy.assert.boolean(
    385        secure,
    386        lazy.pprint`Expected cookie "secure" to be a boolean, got ${secure}`
    387      );
    388    }
    389  }
    390 
    391  #assertCookieFilter(filter) {
    392    lazy.assert.object(
    393      filter,
    394      lazy.pprint`Expected "filter" to be an object, got ${filter}`
    395    );
    396 
    397    const {
    398      domain = null,
    399      expiry = null,
    400      httpOnly = null,
    401      name = null,
    402      path = null,
    403      sameSite = null,
    404      secure = null,
    405      size = null,
    406      value = null,
    407    } = filter;
    408 
    409    if (domain !== null) {
    410      lazy.assert.string(
    411        domain,
    412        lazy.pprint`Expected filter "domain" to be a string, got ${domain}`
    413      );
    414    }
    415 
    416    if (expiry !== null) {
    417      lazy.assert.positiveInteger(
    418        expiry,
    419        lazy.pprint`Expected filter "expiry" to be a positive integer, got ${expiry}`
    420      );
    421    }
    422 
    423    if (httpOnly !== null) {
    424      lazy.assert.boolean(
    425        httpOnly,
    426        lazy.pprint`Expected filter "httpOnly" to be a boolean, got ${httpOnly}`
    427      );
    428    }
    429 
    430    if (name !== null) {
    431      lazy.assert.string(
    432        name,
    433        lazy.pprint`Expected filter "name" to be a string, got ${name}`
    434      );
    435    }
    436 
    437    if (path !== null) {
    438      lazy.assert.string(
    439        path,
    440        lazy.pprint`Expected filter "path" to be a string, got ${path}`
    441      );
    442    }
    443 
    444    this.#assertSameSite(sameSite, "filter.sameSite");
    445 
    446    if (secure !== null) {
    447      lazy.assert.boolean(
    448        secure,
    449        lazy.pprint`Expected filter "secure" to be a boolean, got ${secure}`
    450      );
    451    }
    452 
    453    if (size !== null) {
    454      lazy.assert.positiveInteger(
    455        size,
    456        lazy.pprint`Expected filter "size" to be a positive integer, got ${size}`
    457      );
    458    }
    459 
    460    if (value !== null) {
    461      this.#assertValue(value, "filter.value");
    462    }
    463 
    464    return {
    465      domain,
    466      expiry,
    467      httpOnly,
    468      name,
    469      path,
    470      sameSite,
    471      secure,
    472      size,
    473      value,
    474    };
    475  }
    476 
    477  #assertPartition(partitionSpec) {
    478    if (partitionSpec === null) {
    479      return;
    480    }
    481    lazy.assert.object(
    482      partitionSpec,
    483      lazy.pprint`Expected "partition" to be an object, got ${partitionSpec}`
    484    );
    485 
    486    const { type } = partitionSpec;
    487    lazy.assert.string(
    488      type,
    489      lazy.pprint`Expected partition "type" to be a string, got ${type}`
    490    );
    491 
    492    switch (type) {
    493      case PartitionType.Context: {
    494        const { context } = partitionSpec;
    495        lazy.assert.string(
    496          context,
    497          lazy.pprint`Expected partition "context" to be a string, got ${context}`
    498        );
    499 
    500        break;
    501      }
    502 
    503      case PartitionType.StorageKey: {
    504        const { sourceOrigin = null, userContext = null } = partitionSpec;
    505        if (sourceOrigin !== null) {
    506          lazy.assert.string(
    507            sourceOrigin,
    508            lazy.pprint`Expected partition "sourceOrigin" to be a string, got ${sourceOrigin}`
    509          );
    510          lazy.assert.that(
    511            sourceOrigin => URL.canParse(sourceOrigin),
    512            lazy.pprint`Expected partition "sourceOrigin" to be a valid URL, got ${sourceOrigin}`
    513          )(sourceOrigin);
    514 
    515          const url = new URL(sourceOrigin);
    516          lazy.assert.that(
    517            url => url.pathname === "/" && url.hash === "" && url.search === "",
    518            lazy.pprint`Expected partition "sourceOrigin" to contain only origin, got ${sourceOrigin}`
    519          )(url);
    520        }
    521        if (userContext !== null) {
    522          lazy.assert.string(
    523            userContext,
    524            lazy.pprint`Expected partition "userContext" to be a string, got ${userContext}`
    525          );
    526 
    527          if (!lazy.UserContextManager.hasUserContextId(userContext)) {
    528            throw new lazy.error.NoSuchUserContextError(
    529              `User Context with id ${userContext} was not found`
    530            );
    531          }
    532        }
    533        break;
    534      }
    535 
    536      default: {
    537        throw new lazy.error.InvalidArgumentError(
    538          `Expected "partition.type" to be one of ${Object.values(
    539            PartitionType
    540          )}, got ${type}`
    541        );
    542      }
    543    }
    544  }
    545 
    546  #assertSameSite(sameSite, fieldName = "sameSite") {
    547    if (sameSite !== null) {
    548      const sameSiteTypeValue = Object.values(SameSiteType);
    549      lazy.assert.in(
    550        sameSite,
    551        sameSiteTypeValue,
    552        `Expected "${fieldName}" to be one of ${sameSiteTypeValue}, ` +
    553          lazy.pprint`got ${sameSite}`
    554      );
    555    }
    556  }
    557 
    558  #assertValue(value, fieldName = "value") {
    559    lazy.assert.object(
    560      value,
    561      `Expected "${fieldName}" to be an object, ` + lazy.pprint`got ${value}`
    562    );
    563 
    564    const { type, value: protocolBytesValue } = value;
    565 
    566    const bytesValueTypeValue = Object.values(lazy.BytesValueType);
    567    lazy.assert.in(
    568      type,
    569      bytesValueTypeValue,
    570      `Expected ${fieldName} "type" to be one of ${bytesValueTypeValue}, ` +
    571        lazy.pprint`got ${type}`
    572    );
    573 
    574    lazy.assert.string(
    575      protocolBytesValue,
    576      `Expected ${fieldName} "value" to be string, ` +
    577        lazy.pprint`got ${protocolBytesValue}`
    578    );
    579  }
    580 
    581  /**
    582   * Deserialize filter.
    583   *
    584   * @see https://w3c.github.io/webdriver-bidi/#deserialize-filter
    585   */
    586  #deserializeFilter(filter) {
    587    const deserializedFilter = {};
    588    for (const [fieldName, value] of Object.entries(filter)) {
    589      if (value === null) {
    590        continue;
    591      }
    592 
    593      const deserializedName = CookieFieldsMapping[fieldName];
    594      let deserializedValue;
    595 
    596      switch (deserializedName) {
    597        case "sameSite":
    598          deserializedValue = this.#getSameSitePlatformProperty(value);
    599          break;
    600 
    601        case "value":
    602          deserializedValue = lazy.deserializeBytesValue(value);
    603          break;
    604 
    605        case "expiry":
    606          deserializedValue = value * 1000;
    607          break;
    608 
    609        default:
    610          deserializedValue = value;
    611      }
    612 
    613      deserializedFilter[deserializedName] = deserializedValue;
    614    }
    615 
    616    return deserializedFilter;
    617  }
    618 
    619  /**
    620   * Build a partition key.
    621   *
    622   * @see https://w3c.github.io/webdriver-bidi/#expand-a-storage-partition-spec
    623   */
    624  #expandStoragePartitionSpec(partitionSpec) {
    625    if (partitionSpec === null) {
    626      partitionSpec = {};
    627    }
    628 
    629    if (partitionSpec.type === PartitionType.Context) {
    630      const { context: contextId } = partitionSpec;
    631      const browsingContext = this._getNavigable(contextId);
    632      const principal = Services.scriptSecurityManager.createContentPrincipal(
    633        browsingContext.currentURI,
    634        {}
    635      );
    636 
    637      // Define browsing context’s associated storage partition as combination of user context id
    638      // and the origin of the document in this browsing context. We also add here `isThirdPartyURI`
    639      // which is required to filter out third-party cookies in case they are not allowed.
    640      return {
    641        // In case we have the browsing context of an iframe here, we perform a check
    642        // if the URI of the top context is considered third-party to the URI of the iframe principal.
    643        // It's considered a third-party if base domains or hosts (in case one or both base domains
    644        // can not be determined) do not match.
    645        isThirdPartyURI: browsingContext.parent
    646          ? principal.isThirdPartyURI(browsingContext.top.currentURI)
    647          : false,
    648        sourceOrigin: browsingContext.currentURI.prePath,
    649        userContext: browsingContext.originAttributes.userContextId,
    650      };
    651    }
    652 
    653    const partitionKey = {};
    654    for (const keyName of PartitionKeyAttributes) {
    655      if (keyName in partitionSpec) {
    656        // Retrieve a platform user context id.
    657        if (keyName === "userContext") {
    658          partitionKey[keyName] = lazy.UserContextManager.getInternalIdById(
    659            partitionSpec.userContext
    660          );
    661        } else {
    662          partitionKey[keyName] = partitionSpec[keyName];
    663        }
    664      }
    665    }
    666 
    667    return partitionKey;
    668  }
    669 
    670  /**
    671   * Prepare the partition key in the right format for returning to a client.
    672   */
    673  #formatPartitionKey(partitionKey, originAttributes) {
    674    if ("userContext" in partitionKey) {
    675      // Exchange platform id for Webdriver BiDi id for the user context to return it to the client.
    676      partitionKey.userContext = lazy.UserContextManager.getIdByInternalId(
    677        partitionKey.userContext
    678      );
    679    }
    680 
    681    // If sourceOrigin matches the cookie domain we don't set the partitionKey
    682    // in the setCookie command. In that case we should also remove sourceOrigin
    683    // from the returned partitionKey.
    684    if (
    685      originAttributes &&
    686      "sourceOrigin" in partitionKey &&
    687      originAttributes.partitionKey === ""
    688    ) {
    689      delete partitionKey.sourceOrigin;
    690    }
    691 
    692    // This key is not used for partitioning and was required to only filter out third-party cookies.
    693    delete partitionKey.isThirdPartyURI;
    694 
    695    return partitionKey;
    696  }
    697 
    698  /**
    699   * Since cookies retrieved from the platform API
    700   * always contain expiry even for session cookies,
    701   * we should check ourselves if it's a session cookie
    702   * and do not return expiry in case it is.
    703   */
    704  #getCookieExpiry(cookie) {
    705    const { expiry, isSession } = cookie;
    706    return isSession ? null : expiry;
    707  }
    708 
    709  #getCookieSize(cookie) {
    710    const { name, value } = cookie;
    711    return name.length + value.length;
    712  }
    713 
    714  /**
    715   * Filter and serialize given cookies with provided filter.
    716   *
    717   * @see https://w3c.github.io/webdriver-bidi/#get-matching-cookies
    718   */
    719  #getMatchingCookies(cookieStore, filter) {
    720    const cookies = [];
    721    const deserializedFilter = this.#deserializeFilter(filter);
    722 
    723    for (const storedCookie of cookieStore) {
    724      if (this.#matchCookie(storedCookie, deserializedFilter)) {
    725        cookies.push(storedCookie);
    726      }
    727    }
    728    return cookies;
    729  }
    730 
    731  /**
    732   * Prepare the data in the required for platform API format.
    733   */
    734  #getOriginAttributes(partitionKey, domain) {
    735    const originAttributes = {};
    736 
    737    if (partitionKey.sourceOrigin) {
    738      if (
    739        "isThirdPartyURI" in partitionKey &&
    740        domain &&
    741        !this.#shouldIncludePartitionedCookies() &&
    742        partitionKey.sourceOrigin !== "about:"
    743      ) {
    744        // This is a workaround until CHIPS support is enabled (see Bug 1898253).
    745        // It handles the "context" type partitioning of the `setCookie` command
    746        // (when domain is provided) and if partitioned cookies are disabled,
    747        // but ignore `about` pаges.
    748        const principal =
    749          Services.scriptSecurityManager.createContentPrincipalFromOrigin(
    750            partitionKey.sourceOrigin
    751          );
    752 
    753        // Do not set partition key if the cookie domain matches the `sourceOrigin`.
    754        if (principal.host.endsWith(domain)) {
    755          originAttributes.partitionKey = "";
    756        } else {
    757          originAttributes.partitionKey = ChromeUtils.getPartitionKeyFromURL(
    758            partitionKey.sourceOrigin,
    759            "",
    760            false
    761          );
    762        }
    763      } else {
    764        originAttributes.partitionKey = ChromeUtils.getPartitionKeyFromURL(
    765          partitionKey.sourceOrigin,
    766          "",
    767          false
    768        );
    769      }
    770    }
    771    if ("userContext" in partitionKey) {
    772      originAttributes.userContextId = partitionKey.userContext;
    773    }
    774 
    775    return originAttributes;
    776  }
    777 
    778  #getSameSitePlatformProperty(sameSite) {
    779    switch (sameSite) {
    780      case "lax": {
    781        return Ci.nsICookie.SAMESITE_LAX;
    782      }
    783      case "strict": {
    784        return Ci.nsICookie.SAMESITE_STRICT;
    785      }
    786      case "none": {
    787        return Ci.nsICookie.SAMESITE_NONE;
    788      }
    789    }
    790 
    791    return Ci.nsICookie.SAMESITE_UNSET;
    792  }
    793 
    794  /**
    795   * Return a cookie store of the storage partition for a given storage partition key.
    796   *
    797   * The implementation differs here from the spec, since in gecko there is no
    798   * direct way to get all the cookies for a given partition key.
    799   *
    800   * @see https://w3c.github.io/webdriver-bidi/#get-the-cookie-store
    801   */
    802  #getTheCookieStore(storagePartitionKey) {
    803    let store = [];
    804 
    805    // Prepare the data in the format required for the platform API.
    806    const originAttributes = this.#getOriginAttributes(storagePartitionKey);
    807 
    808    // Retrieve the cookies which exactly match a built partition attributes.
    809    const cookiesWithOriginAttributes =
    810      Services.cookies.getCookiesWithOriginAttributes(
    811        JSON.stringify(originAttributes)
    812      );
    813 
    814    const isFirstPartyOrCrossSiteAllowed =
    815      !storagePartitionKey.isThirdPartyURI ||
    816      this.#shouldIncludeCrossSiteCookie();
    817 
    818    // Check if we accessing the first party storage or cross-site cookies are allowed.
    819    if (isFirstPartyOrCrossSiteAllowed) {
    820      // In case we want to get the cookies for a certain `sourceOrigin`,
    821      // we have to separately retrieve cookies for a hostname built from `sourceOrigin`,
    822      // and with `partitionKey` equal an empty string to retrieve the cookies that which were set
    823      // by this hostname but without `partitionKey`, e.g. with `document.cookie`.
    824      if (storagePartitionKey.sourceOrigin) {
    825        const url = new URL(storagePartitionKey.sourceOrigin);
    826        const hostname = url.hostname;
    827 
    828        const principal = Services.scriptSecurityManager.createContentPrincipal(
    829          url.URI,
    830          {}
    831        );
    832        const isSecureProtocol = principal.isOriginPotentiallyTrustworthy;
    833 
    834        // We want to keep `userContext` id here, if it's present,
    835        // but set the `partitionKey` to an empty string.
    836        const cookiesMatchingHostname =
    837          Services.cookies.getCookiesWithOriginAttributes(
    838            JSON.stringify({ ...originAttributes, partitionKey: "" }),
    839            hostname
    840          );
    841        for (const cookie of cookiesMatchingHostname) {
    842          // Ignore secure cookies for non-secure protocols.
    843          if (cookie.isSecure && !isSecureProtocol) {
    844            continue;
    845          }
    846          store.push(cookie);
    847        }
    848      }
    849 
    850      store = store.concat(cookiesWithOriginAttributes);
    851    }
    852    // If we're trying to access the store in the third party context and
    853    // the preferences imply that we shouldn't include cross site cookies,
    854    // but we should include partitioned cookies, add only partitioned cookies.
    855    else if (this.#shouldIncludePartitionedCookies()) {
    856      for (const cookie of cookiesWithOriginAttributes) {
    857        if (cookie.isPartitioned) {
    858          store.push(cookie);
    859        }
    860      }
    861    }
    862 
    863    return store;
    864  }
    865 
    866  /**
    867   * Match a provided cookie with provided filter.
    868   *
    869   * @see https://w3c.github.io/webdriver-bidi/#match-cookie
    870   */
    871  #matchCookie(storedCookie, filter) {
    872    for (const [fieldName, value] of Object.entries(filter)) {
    873      // Since we set `null` to not specified values, we have to check for `null` here
    874      // and not match on these values.
    875      if (value === null) {
    876        continue;
    877      }
    878 
    879      let storedCookieValue = storedCookie[fieldName];
    880 
    881      // The platform representation of cookie doesn't contain a size field,
    882      // so we have to calculate it to match.
    883      if (fieldName === "size") {
    884        storedCookieValue = this.#getCookieSize(storedCookie);
    885      }
    886 
    887      if (storedCookieValue !== value) {
    888        return false;
    889      }
    890    }
    891 
    892    return true;
    893  }
    894 
    895  /**
    896   * Serialize a cookie.
    897   *
    898   * @see https://w3c.github.io/webdriver-bidi/#serialize-cookie
    899   */
    900  #serializeCookie(storedCookie) {
    901    const cookie = {};
    902    for (const [serializedName, cookieName] of Object.entries(
    903      CookieFieldsMapping
    904    )) {
    905      switch (serializedName) {
    906        case "expiry": {
    907          const expiry = this.#getCookieExpiry(storedCookie);
    908          if (expiry !== null) {
    909            cookie.expiry = Math.round(expiry / 1000);
    910          }
    911          break;
    912        }
    913 
    914        case "sameSite":
    915          cookie.sameSite = SameSiteType[storedCookie.sameSite];
    916          break;
    917 
    918        case "size":
    919          cookie.size = this.#getCookieSize(storedCookie);
    920          break;
    921 
    922        case "value":
    923          // Bug 1879309. Add support for non-UTF8 cookies,
    924          // when a byte representation of value is available.
    925          // For now, use a value field, which is returned as a string.
    926          cookie.value = {
    927            type: lazy.BytesValueType.String,
    928            value: storedCookie.value,
    929          };
    930          break;
    931 
    932        default:
    933          cookie[serializedName] = storedCookie[cookieName];
    934      }
    935    }
    936 
    937    return cookie;
    938  }
    939 
    940  #shouldIncludeCrossSiteCookie() {
    941    const cookieBehavior = Services.prefs.getIntPref(PREF_COOKIE_BEHAVIOR);
    942 
    943    if (
    944      cookieBehavior === Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN ||
    945      cookieBehavior ===
    946        Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
    947    ) {
    948      return false;
    949    }
    950 
    951    return true;
    952  }
    953 
    954  #shouldIncludePartitionedCookies() {
    955    const cookieBehavior = Services.prefs.getIntPref(PREF_COOKIE_BEHAVIOR);
    956 
    957    return (
    958      cookieBehavior ===
    959        Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
    960      lazy.cookieCHIPSEnabled
    961    );
    962  }
    963 }
    964 
    965 export const storage = StorageModule;