summaryrefslogtreecommitdiffstats
path: root/toolkit/components/cookiebanners/test/browser/browser_cookiebanner_telemetry.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/cookiebanners/test/browser/browser_cookiebanner_telemetry.js
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/cookiebanners/test/browser/browser_cookiebanner_telemetry.js')
-rw-r--r--toolkit/components/cookiebanners/test/browser/browser_cookiebanner_telemetry.js766
1 files changed, 766 insertions, 0 deletions
diff --git a/toolkit/components/cookiebanners/test/browser/browser_cookiebanner_telemetry.js b/toolkit/components/cookiebanners/test/browser/browser_cookiebanner_telemetry.js
new file mode 100644
index 0000000000..f35d4c81c8
--- /dev/null
+++ b/toolkit/components/cookiebanners/test/browser/browser_cookiebanner_telemetry.js
@@ -0,0 +1,766 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+const { MODE_DISABLED, MODE_REJECT, MODE_REJECT_OR_ACCEPT, MODE_UNSET } =
+ Ci.nsICookieBannerService;
+
+const TEST_MODES = [
+ MODE_DISABLED,
+ MODE_REJECT,
+ MODE_REJECT_OR_ACCEPT,
+ MODE_UNSET, // Should be recorded as invalid.
+ 99, // Invalid
+ -1, // Invalid
+];
+
+function convertModeToTelemetryString(mode) {
+ switch (mode) {
+ case MODE_DISABLED:
+ return "disabled";
+ case MODE_REJECT:
+ return "reject";
+ case MODE_REJECT_OR_ACCEPT:
+ return "reject_or_accept";
+ }
+
+ return "invalid";
+}
+
+/**
+ * A helper function to verify the cookie rule look up telemetry.
+ *
+ * @param {String} probe The telemetry probe that we want to verify
+ * @param {Array} expected An array of objects that describe the expected value.
+ */
+function verifyLookUpTelemetry(probe, expected) {
+ for (let telemetry of expected) {
+ Assert.equal(
+ telemetry.count,
+ Glean.cookieBanners[probe][telemetry.label].testGetValue()
+ );
+ }
+}
+
+/**
+ * A helper function to verify the reload telemetry.
+ *
+ * @param {Number} length The expected length of the telemetry array.
+ * @param {Number} idx The index of the telemetry to be verified.
+ * @param {Object} expected An object that describe the expected value.
+ */
+function verifyReloadTelemetry(length, idx, expected) {
+ let events = Glean.cookieBanners.reload.testGetValue();
+
+ is(events.length, length, "There is a expected number of reload events.");
+
+ let event = events[idx];
+
+ let { noRule, hasCookieRule, hasClickRule } = expected;
+ is(event.name, "reload", "The reload event has the correct name");
+ is(event.extra.no_rule, noRule, "The extra field 'no_rule' is expected");
+ is(
+ event.extra.has_cookie_rule,
+ hasCookieRule,
+ "The extra field 'has_cookie_rule' is expected"
+ );
+ is(
+ event.extra.has_click_rule,
+ hasClickRule,
+ "The extra field 'has_click_rule' is expected"
+ );
+}
+
+/**
+ * A helper function to reload the browser and wait until it loads.
+ *
+ * @param {Browser} browser The browser object.
+ * @param {String} url The URL to be loaded.
+ */
+async function reloadBrowser(browser, url) {
+ let reloaded = BrowserTestUtils.browserLoaded(browser, false, url);
+
+ // Reload as a user.
+ window.BrowserReload();
+
+ await reloaded;
+}
+/**
+ * A helper function to open the testing page for look up telemetry.
+ *
+ * @param {browser} browser The browser element
+ * @param {boolean} testInTop To indicate the page should be opened in top level
+ * @param {String} page The url of the testing page
+ * @param {String} domain The domain of the testing page
+ */
+async function openLookUpTelemetryTestPage(browser, testInTop, page, domain) {
+ let clickFinishPromise = promiseBannerClickingFinish(domain);
+
+ if (testInTop) {
+ BrowserTestUtils.loadURIString(browser, page);
+ } else {
+ BrowserTestUtils.loadURIString(browser, TEST_ORIGIN_C);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(browser, [page], async testURL => {
+ let iframe = content.document.createElement("iframe");
+ iframe.src = testURL;
+ content.document.body.appendChild(iframe);
+ await ContentTaskUtils.waitForEvent(iframe, "load");
+ });
+ }
+
+ await clickFinishPromise;
+}
+
+add_setup(async function () {
+ // Clear telemetry before starting telemetry test.
+ Services.fog.testResetFOG();
+
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("cookiebanners.service.mode");
+ Services.prefs.clearUserPref("cookiebanners.service.mode.privateBrowsing");
+ if (
+ Services.prefs.getIntPref("cookiebanners.service.mode") !=
+ Ci.nsICookieBannerService.MODE_DISABLED ||
+ Services.prefs.getIntPref("cookiebanners.service.mode.privateBrowsing") !=
+ Ci.nsICookieBannerService.MODE_DISABLED
+ ) {
+ // Restore original rules.
+ Services.cookieBanners.resetRules(true);
+ }
+
+ // Clear cookies that have been set during testing.
+ await SiteDataTestUtils.clear();
+ });
+
+ await clickTestSetup();
+});
+
+add_task(async function test_service_mode_telemetry() {
+ let service = Cc["@mozilla.org/cookie-banner-service;1"].getService(
+ Ci.nsIObserver
+ );
+
+ for (let mode of TEST_MODES) {
+ for (let modePBM of TEST_MODES) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", mode],
+ ["cookiebanners.service.mode.privateBrowsing", modePBM],
+ ],
+ });
+
+ // Trigger the idle-daily on the cookie banner service.
+ service.observe(null, "idle-daily", null);
+
+ // Verify the telemetry value.
+ for (let label of ["disabled", "reject", "reject_or_accept", "invalid"]) {
+ let expected = convertModeToTelemetryString(mode) == label;
+ let expectedPBM = convertModeToTelemetryString(modePBM) == label;
+
+ is(
+ Glean.cookieBanners.normalWindowServiceMode[label].testGetValue(),
+ expected,
+ `Has set label ${label} to ${expected} for mode ${mode}.`
+ );
+ is(
+ Glean.cookieBanners.privateWindowServiceMode[label].testGetValue(),
+ expectedPBM,
+ `Has set label '${label}' to ${expected} for mode ${modePBM}.`
+ );
+ }
+
+ await SpecialPowers.popPrefEnv();
+ }
+ }
+});
+
+add_task(async function test_rule_lookup_telemetry_no_rule() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+
+ // Clear out all rules.
+ Services.cookieBanners.resetRules(false);
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ for (let context of ["top", "iframe"]) {
+ let isTop = context === "top";
+
+ // Open a test domain. We should record a rule miss because there is no rule
+ // right now
+ info("Open a test domain.");
+ await openLookUpTelemetryTestPage(
+ tab.linkedBrowser,
+ isTop,
+ TEST_ORIGIN_A,
+ TEST_DOMAIN_A
+ );
+
+ let expectedTelemetryOnce = [
+ {
+ label: `${context}_miss`,
+ count: 1,
+ },
+ {
+ label: `${context}_cookie_miss`,
+ count: 1,
+ },
+ {
+ label: `${context}_click_miss`,
+ count: 1,
+ },
+ ];
+ verifyLookUpTelemetry("ruleLookupByLoad", expectedTelemetryOnce);
+ verifyLookUpTelemetry("ruleLookupByDomain", expectedTelemetryOnce);
+
+ info("Open the same domain again.");
+ // Load the same domain again, verify that the telemetry counts increases for
+ // load telemetry not not for domain telemetry.
+ await openLookUpTelemetryTestPage(
+ tab.linkedBrowser,
+ isTop,
+ TEST_ORIGIN_A,
+ TEST_DOMAIN_A
+ );
+
+ let expectedTelemetryTwice = [
+ {
+ label: `${context}_miss`,
+ count: 2,
+ },
+ {
+ label: `${context}_cookie_miss`,
+ count: 2,
+ },
+ {
+ label: `${context}_click_miss`,
+ count: 2,
+ },
+ ];
+ verifyLookUpTelemetry("ruleLookupByLoad", expectedTelemetryTwice);
+ verifyLookUpTelemetry("ruleLookupByDomain", expectedTelemetryOnce);
+ }
+
+ Services.fog.testResetFOG();
+ Services.cookieBanners.resetDomainTelemetryRecord();
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_rule_lookup_telemetry() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+ insertTestClickRules();
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ for (let type of ["click", "cookie"]) {
+ info(`Running the test for lookup telemetry for ${type} rules.`);
+ // Clear out all rules.
+ Services.cookieBanners.resetRules(false);
+
+ info("Insert rules.");
+ if (type === "click") {
+ insertTestClickRules();
+ } else {
+ let ruleA = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleA.domains = [TEST_DOMAIN_A];
+
+ Services.cookieBanners.insertRule(ruleA);
+ ruleA.addCookie(
+ true,
+ `cookieConsent_${TEST_DOMAIN_A}_1`,
+ "optOut1",
+ null,
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+ ruleA.addCookie(
+ false,
+ `cookieConsent_${TEST_DOMAIN_A}_2`,
+ "optIn2",
+ null,
+ "/",
+ 3600,
+ "",
+ false,
+ false,
+ false,
+ 0,
+ 0
+ );
+
+ let ruleB = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ ruleB.domains = [TEST_DOMAIN_B];
+
+ Services.cookieBanners.insertRule(ruleB);
+ ruleB.addCookie(
+ false,
+ `cookieConsent_${TEST_DOMAIN_B}_1`,
+ "optIn1",
+ null,
+ "/",
+ 3600,
+ "UNSET",
+ false,
+ false,
+ true,
+ 0,
+ 0
+ );
+ }
+
+ for (let context of ["top", "iframe"]) {
+ info(`Test in a ${context} context.`);
+ let isTop = context === "top";
+
+ info("Load a domain with opt-in and opt-out clicking rules.");
+ await openLookUpTelemetryTestPage(
+ tab.linkedBrowser,
+ isTop,
+ TEST_ORIGIN_A,
+ TEST_DOMAIN_A
+ );
+
+ let expectedTelemetry = [
+ {
+ label: `${context}_hit`,
+ count: 1,
+ },
+ {
+ label: `${context}_hit_opt_in`,
+ count: 1,
+ },
+ {
+ label: `${context}_hit_opt_out`,
+ count: 1,
+ },
+ {
+ label: `${context}_${type}_hit`,
+ count: 1,
+ },
+ {
+ label: `${context}_${type}_hit_opt_in`,
+ count: 1,
+ },
+ {
+ label: `${context}_${type}_hit_opt_out`,
+ count: 1,
+ },
+ ];
+ verifyLookUpTelemetry("ruleLookupByLoad", expectedTelemetry);
+ verifyLookUpTelemetry("ruleLookupByDomain", expectedTelemetry);
+
+ info("Load a domain with only opt-in clicking rules");
+ await openLookUpTelemetryTestPage(
+ tab.linkedBrowser,
+ isTop,
+ TEST_ORIGIN_B,
+ TEST_DOMAIN_B
+ );
+
+ expectedTelemetry = [
+ {
+ label: `${context}_hit`,
+ count: 2,
+ },
+ {
+ label: `${context}_hit_opt_in`,
+ count: 2,
+ },
+ {
+ label: `${context}_hit_opt_out`,
+ count: 1,
+ },
+ {
+ label: `${context}_${type}_hit`,
+ count: 2,
+ },
+ {
+ label: `${context}_${type}_hit_opt_in`,
+ count: 2,
+ },
+ {
+ label: `${context}_${type}_hit_opt_out`,
+ count: 1,
+ },
+ ];
+
+ verifyLookUpTelemetry("ruleLookupByLoad", expectedTelemetry);
+ verifyLookUpTelemetry("ruleLookupByDomain", expectedTelemetry);
+
+ info(
+ "Load a domain again to verify that we don't collect domain telemetry for this time."
+ );
+ await openLookUpTelemetryTestPage(
+ tab.linkedBrowser,
+ isTop,
+ TEST_ORIGIN_A,
+ TEST_DOMAIN_A
+ );
+
+ // The domain telemetry should't be changed.
+ verifyLookUpTelemetry("ruleLookupByDomain", expectedTelemetry);
+
+ expectedTelemetry = [
+ {
+ label: `${context}_hit`,
+ count: 3,
+ },
+ {
+ label: `${context}_hit_opt_in`,
+ count: 3,
+ },
+ {
+ label: `${context}_hit_opt_out`,
+ count: 2,
+ },
+ {
+ label: `${context}_${type}_hit`,
+ count: 3,
+ },
+ {
+ label: `${context}_${type}_hit_opt_in`,
+ count: 3,
+ },
+ {
+ label: `${context}_${type}_hit_opt_out`,
+ count: 2,
+ },
+ ];
+
+ // Verify that the load telemetry still increases.
+ verifyLookUpTelemetry("ruleLookupByLoad", expectedTelemetry);
+ }
+
+ Services.fog.testResetFOG();
+ Services.cookieBanners.resetDomainTelemetryRecord();
+ }
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_reload_telemetry_no_rule() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+
+ // Clear out all rules.
+ Services.cookieBanners.resetRules(false);
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_ORIGIN_A
+ );
+
+ // Make sure there is no reload event at the beginning.
+ let events = Glean.cookieBanners.reload.testGetValue();
+ ok(!events, "No reload event at the beginning.");
+
+ // Trigger the reload
+ await reloadBrowser(tab.linkedBrowser, TEST_ORIGIN_A + "/");
+
+ // Check the telemetry
+ verifyReloadTelemetry(1, 0, {
+ noRule: "true",
+ hasCookieRule: "false",
+ hasClickRule: "false",
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ Services.fog.testResetFOG();
+});
+
+add_task(async function test_reload_telemetry() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+ insertTestClickRules();
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_ORIGIN_A
+ );
+
+ // Make sure there is no reload event at the beginning.
+ let events = Glean.cookieBanners.reload.testGetValue();
+ ok(!events, "No reload event at the beginning.");
+
+ // Trigger the reload
+ await reloadBrowser(tab.linkedBrowser, TEST_ORIGIN_A + "/");
+
+ // Check the telemetry
+ verifyReloadTelemetry(1, 0, {
+ noRule: "false",
+ hasCookieRule: "false",
+ hasClickRule: "true",
+ });
+
+ // Add a both click rule and cookie rule for another domain.
+ let cookieRule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ cookieRule.domains = [TEST_DOMAIN_B];
+
+ Services.cookieBanners.insertRule(cookieRule);
+ cookieRule.addCookie(
+ false,
+ `cookieConsent_${TEST_DOMAIN_B}_1`,
+ "optIn1",
+ null,
+ "/",
+ 3600,
+ "UNSET",
+ false,
+ false,
+ true,
+ 0,
+ 0
+ );
+ cookieRule.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_ALL,
+ null,
+ null,
+ "button#optIn"
+ );
+
+ // Load the page with another origin.
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, TEST_ORIGIN_B);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ // Trigger the reload
+ await reloadBrowser(tab.linkedBrowser, TEST_ORIGIN_B + "/");
+
+ // Check the telemetry
+ verifyReloadTelemetry(2, 1, {
+ noRule: "false",
+ hasCookieRule: "true",
+ hasClickRule: "true",
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ Services.fog.testResetFOG();
+});
+
+add_task(async function test_reload_telemetry_mode_disabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ [
+ "cookiebanners.service.mode.privateBrowsing",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+ insertTestClickRules();
+
+ // Disable the cookie banner service in normal browsing.
+ // Keep it enabled in PBM so the service stays alive and can still collect telemetry.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_DISABLED],
+ ],
+ });
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_ORIGIN_A
+ );
+
+ // Trigger the reload
+ await reloadBrowser(tab.linkedBrowser, TEST_ORIGIN_A + "/");
+
+ // Check the telemetry. The reload telemetry should report no rule given that
+ // the service is disabled.
+ verifyReloadTelemetry(1, 0, {
+ noRule: "true",
+ hasCookieRule: "false",
+ hasClickRule: "false",
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ Services.fog.testResetFOG();
+});
+
+add_task(async function test_reload_telemetry_mode_reject() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", Ci.nsICookieBannerService.MODE_REJECT],
+ ],
+ });
+ insertTestClickRules();
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_ORIGIN_A
+ );
+
+ // Trigger the reload
+ await reloadBrowser(tab.linkedBrowser, TEST_ORIGIN_A + "/");
+
+ // Check the telemetry. The reload telemetry should report there is click rule
+ // for the domain has opt-out rule.
+ verifyReloadTelemetry(1, 0, {
+ noRule: "false",
+ hasCookieRule: "false",
+ hasClickRule: "true",
+ });
+
+ // Load the page with the domain only has opt-in click rule.
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, TEST_ORIGIN_B);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ // Trigger the reload
+ await reloadBrowser(tab.linkedBrowser, TEST_ORIGIN_B + "/");
+
+ // Check the telemetry. It should report there is no rule because the domain
+ // only has an opt-in click rule.
+ verifyReloadTelemetry(2, 1, {
+ noRule: "true",
+ hasCookieRule: "false",
+ hasClickRule: "false",
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ Services.fog.testResetFOG();
+});
+
+add_task(async function test_reload_telemetry_iframe() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "cookiebanners.service.mode",
+ Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT,
+ ],
+ ],
+ });
+
+ // Clear out all rules.
+ Services.cookieBanners.resetRules(false);
+
+ // Insert a click rule for an iframe case. And add a cookie rule for the same
+ // domain. We shouldn't report there is a cookie rule for iframe because
+ // cookie rules are top-level only.
+ let cookieRule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
+ Ci.nsICookieBannerRule
+ );
+ cookieRule.domains = [TEST_DOMAIN_A];
+ Services.cookieBanners.insertRule(cookieRule);
+
+ cookieRule.addClickRule(
+ "div#banner",
+ false,
+ Ci.nsIClickRule.RUN_CHILD,
+ null,
+ null,
+ "button#optIn"
+ );
+ cookieRule.addCookie(
+ false,
+ `cookieConsent_${TEST_DOMAIN_A}_1`,
+ "optIn1",
+ null,
+ "/",
+ 3600,
+ "UNSET",
+ false,
+ false,
+ true,
+ 0,
+ 0
+ );
+
+ // Open a tab for testing.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_ORIGIN_C + TEST_PATH + "file_iframe_banner.html"
+ );
+
+ // Trigger the reload
+ await reloadBrowser(
+ tab.linkedBrowser,
+ TEST_ORIGIN_C + TEST_PATH + "file_iframe_banner.html"
+ );
+
+ // Check the telemetry
+ verifyReloadTelemetry(1, 0, {
+ noRule: "false",
+ hasCookieRule: "false",
+ hasClickRule: "true",
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ Services.fog.testResetFOG();
+});
+
+add_task(async function test_service_detectOnly_telemetry() {
+ let service = Cc["@mozilla.org/cookie-banner-service;1"].getService(
+ Ci.nsIObserver
+ );
+
+ for (let detectOnly of [true, false, true]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["cookiebanners.service.detectOnly", detectOnly]],
+ });
+
+ // Trigger the idle-daily on the cookie banner service.
+ service.observe(null, "idle-daily", null);
+
+ is(
+ Glean.cookieBanners.serviceDetectOnly.testGetValue(),
+ detectOnly,
+ `Has set detect-only metric to ${detectOnly}.`
+ );
+
+ await SpecialPowers.popPrefEnv();
+ }
+});