tor-browser

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

browser_UITour.js (22090B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 var gTestTab;
      7 var gContentAPI;
      8 
      9 ChromeUtils.defineESModuleGetters(this, {
     10  ProfileAge: "resource://gre/modules/ProfileAge.sys.mjs",
     11  UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
     12  CustomizableUITestUtils:
     13    "resource://testing-common/CustomizableUITestUtils.sys.mjs",
     14 });
     15 
     16 let gCUITestUtils = new CustomizableUITestUtils(window);
     17 
     18 function test() {
     19  UITourTest();
     20 }
     21 
     22 var tests = [
     23  function test_untrusted_host(done) {
     24    loadUITourTestPage(function () {
     25      CustomizableUI.addWidgetToArea(
     26        "bookmarks-menu-button",
     27        CustomizableUI.AREA_NAVBAR,
     28        0
     29      );
     30      registerCleanupFunction(() =>
     31        CustomizableUI.removeWidgetFromArea("bookmarks-menu-button")
     32      );
     33      let bookmarksMenu = document.getElementById("bookmarks-menu-button");
     34      is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
     35 
     36      gContentAPI.showMenu("bookmarks");
     37      is(
     38        bookmarksMenu.open,
     39        false,
     40        "Bookmark menu should not open on a untrusted host"
     41      );
     42 
     43      done();
     44    }, "http://mochi.test:8888/");
     45  },
     46  function test_testing_host(done) {
     47    // Add two testing origins intentionally surrounded by whitespace to be ignored.
     48    Services.prefs.setCharPref(
     49      "browser.uitour.testingOrigins",
     50      "https://test1.example.org, https://test2.example.org:443 "
     51    );
     52 
     53    registerCleanupFunction(() => {
     54      Services.prefs.clearUserPref("browser.uitour.testingOrigins");
     55    });
     56    function callback(result) {
     57      ok(result, "Callback should be called on a testing origin");
     58      done();
     59    }
     60 
     61    loadUITourTestPage(function () {
     62      gContentAPI.getConfiguration("appinfo", callback);
     63    }, "https://test2.example.org/");
     64  },
     65  function test_unsecure_host(done) {
     66    loadUITourTestPage(function () {
     67      let bookmarksMenu = document.getElementById("bookmarks-menu-button");
     68      is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
     69 
     70      gContentAPI.showMenu("bookmarks");
     71      is(
     72        bookmarksMenu.open,
     73        false,
     74        "Bookmark menu should not open on a unsecure host"
     75      );
     76 
     77      done();
     78    }, "http://example.org/");
     79  },
     80  function test_disabled(done) {
     81    Services.prefs.setBoolPref("browser.uitour.enabled", false);
     82 
     83    let bookmarksMenu = document.getElementById("bookmarks-menu-button");
     84    is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
     85 
     86    gContentAPI.showMenu("bookmarks").then(() => {
     87      is(
     88        bookmarksMenu.open,
     89        false,
     90        "Bookmark menu should not open when feature is disabled"
     91      );
     92 
     93      Services.prefs.setBoolPref("browser.uitour.enabled", true);
     94    });
     95    done();
     96  },
     97  function test_highlight(done) {
     98    function test_highlight_2() {
     99      let highlight = document.getElementById("UITourHighlight");
    100      gContentAPI.hideHighlight();
    101 
    102      waitForElementToBeHidden(
    103        highlight,
    104        test_highlight_3,
    105        "Highlight should be hidden after hideHighlight()"
    106      );
    107    }
    108    function test_highlight_3() {
    109      is_element_hidden(
    110        highlight,
    111        "Highlight should be hidden after hideHighlight()"
    112      );
    113 
    114      gContentAPI.showHighlight("urlbar");
    115      waitForElementToBeVisible(
    116        highlight,
    117        test_highlight_4,
    118        "Highlight should be shown after showHighlight()"
    119      );
    120    }
    121    function test_highlight_4() {
    122      let highlight = document.getElementById("UITourHighlight");
    123      gContentAPI.showHighlight("backForward");
    124      waitForElementToBeVisible(
    125        highlight,
    126        done,
    127        "Highlight should be shown after showHighlight()"
    128      );
    129    }
    130 
    131    let highlight = document.getElementById("UITourHighlight");
    132    is_element_hidden(highlight, "Highlight should initially be hidden");
    133 
    134    gContentAPI.showHighlight("urlbar");
    135    waitForElementToBeVisible(
    136      highlight,
    137      test_highlight_2,
    138      "Highlight should be shown after showHighlight()"
    139    );
    140  },
    141  function test_highlight_toolbar_button(done) {
    142    function check_highlight_size() {
    143      let panel = highlight.parentElement;
    144      let anchor = panel.anchorNode;
    145      let anchorRect = anchor.getBoundingClientRect();
    146      info(
    147        "addons target: width: " +
    148          anchorRect.width +
    149          " height: " +
    150          anchorRect.height
    151      );
    152      let dimension = anchorRect.width;
    153      let highlightRect = highlight.getBoundingClientRect();
    154      info(
    155        "highlight: width: " +
    156          highlightRect.width +
    157          " height: " +
    158          highlightRect.height
    159      );
    160      is(
    161        Math.round(highlightRect.width),
    162        dimension,
    163        "The width of the highlight should be equal to the width of the target"
    164      );
    165      is(
    166        Math.round(highlightRect.height),
    167        dimension,
    168        "The height of the highlight should be equal to the width of the target"
    169      );
    170      is(
    171        highlight.classList.contains("rounded-highlight"),
    172        true,
    173        "Highlight should be rounded-rectangle styled"
    174      );
    175      CustomizableUI.removeWidgetFromArea("home-button");
    176      done();
    177    }
    178    info("Adding home button.");
    179    CustomizableUI.addWidgetToArea("home-button", "nav-bar");
    180    // Force the button to get layout so we can show the highlight.
    181    document.getElementById("home-button").clientHeight;
    182    let highlight = document.getElementById("UITourHighlight");
    183    is_element_hidden(highlight, "Highlight should initially be hidden");
    184 
    185    gContentAPI.showHighlight("home");
    186    waitForElementToBeVisible(
    187      highlight,
    188      check_highlight_size,
    189      "Highlight should be shown after showHighlight()"
    190    );
    191  },
    192  function test_highlight_addons_auto_open_close(done) {
    193    let highlight = document.getElementById("UITourHighlight");
    194    gContentAPI.showHighlight("addons");
    195    waitForElementToBeVisible(
    196      highlight,
    197      function checkPanelIsOpen() {
    198        isnot(PanelUI.panel.state, "closed", "Panel should have opened");
    199        isnot(
    200          highlight.classList.contains("rounded-highlight"),
    201          true,
    202          "Highlight should not be round-rectangle styled."
    203        );
    204 
    205        let hiddenPromise = promisePanelElementHidden(window, PanelUI.panel);
    206        // Move the highlight outside which should close the app menu.
    207        gContentAPI.showHighlight("appMenu");
    208        hiddenPromise.then(() => {
    209          waitForElementToBeVisible(
    210            highlight,
    211            function checkPanelIsClosed() {
    212              isnot(
    213                PanelUI.panel.state,
    214                "open",
    215                "Panel should have closed after the highlight moved elsewhere."
    216              );
    217              done();
    218            },
    219            "Highlight should move to the appMenu button"
    220          );
    221        });
    222      },
    223      "Highlight should be shown after showHighlight() for fixed panel items"
    224    );
    225  },
    226  function test_highlight_addons_manual_open_close(done) {
    227    let highlight = document.getElementById("UITourHighlight");
    228    // Manually open the app menu then show a highlight there. The menu should remain open.
    229    let shownPromise = promisePanelShown(window);
    230    gContentAPI.showMenu("appMenu");
    231    shownPromise
    232      .then(() => {
    233        isnot(PanelUI.panel.state, "closed", "Panel should have opened");
    234        gContentAPI.showHighlight("addons");
    235 
    236        waitForElementToBeVisible(
    237          highlight,
    238          function checkPanelIsStillOpen() {
    239            isnot(PanelUI.panel.state, "closed", "Panel should still be open");
    240 
    241            // Move the highlight outside which shouldn't close the app menu since it was manually opened.
    242            gContentAPI.showHighlight("appMenu");
    243            waitForElementToBeVisible(
    244              highlight,
    245              function () {
    246                isnot(
    247                  PanelUI.panel.state,
    248                  "closed",
    249                  "Panel should remain open since UITour didn't open it in the first place"
    250                );
    251                gContentAPI.hideMenu("appMenu");
    252                done();
    253              },
    254              "Highlight should move to the appMenu button"
    255            );
    256          },
    257          "Highlight should be shown after showHighlight() for fixed panel items"
    258        );
    259      })
    260      .catch(console.error);
    261  },
    262  function test_highlight_effect(done) {
    263    function waitForHighlightWithEffect(highlightEl, effect, next, error) {
    264      return waitForCondition(
    265        () => highlightEl.getAttribute("active") == effect,
    266        next,
    267        error
    268      );
    269    }
    270    function checkDefaultEffect() {
    271      is(
    272        highlight.getAttribute("active"),
    273        "none",
    274        "The default should be no effect"
    275      );
    276 
    277      gContentAPI.showHighlight("urlbar", "none");
    278      waitForHighlightWithEffect(
    279        highlight,
    280        "none",
    281        checkZoomEffect,
    282        "There should be no effect"
    283      );
    284    }
    285    function checkZoomEffect() {
    286      gContentAPI.showHighlight("urlbar", "zoom");
    287      waitForHighlightWithEffect(
    288        highlight,
    289        "zoom",
    290        () => {
    291          let style = window.getComputedStyle(highlight);
    292          is(
    293            style.animationName,
    294            "uitour-zoom",
    295            "The animation-name should be uitour-zoom"
    296          );
    297          checkSameEffectOnDifferentTarget();
    298        },
    299        "There should be a zoom effect"
    300      );
    301    }
    302    function checkSameEffectOnDifferentTarget() {
    303      gContentAPI.showHighlight("appMenu", "wobble");
    304      waitForHighlightWithEffect(
    305        highlight,
    306        "wobble",
    307        () => {
    308          highlight.addEventListener(
    309            "animationstart",
    310            function () {
    311              ok(
    312                true,
    313                "Animation occurred again even though the effect was the same"
    314              );
    315              checkRandomEffect();
    316            },
    317            { once: true }
    318          );
    319          gContentAPI.showHighlight("backForward", "wobble");
    320        },
    321        "There should be a wobble effect"
    322      );
    323    }
    324    function checkRandomEffect() {
    325      function waitForActiveHighlight(highlightEl, next, error) {
    326        return waitForCondition(
    327          () => highlightEl.hasAttribute("active"),
    328          next,
    329          error
    330        );
    331      }
    332 
    333      gContentAPI.hideHighlight();
    334      gContentAPI.showHighlight("urlbar", "random");
    335      waitForActiveHighlight(
    336        highlight,
    337        () => {
    338          ok(
    339            highlight.hasAttribute("active"),
    340            "The highlight should be active"
    341          );
    342          isnot(
    343            highlight.getAttribute("active"),
    344            "none",
    345            "A random effect other than none should have been chosen"
    346          );
    347          isnot(
    348            highlight.getAttribute("active"),
    349            "random",
    350            "The random effect shouldn't be 'random'"
    351          );
    352          isnot(
    353            UITour.highlightEffects.indexOf(highlight.getAttribute("active")),
    354            -1,
    355            "Check that a supported effect was randomly chosen"
    356          );
    357          done();
    358        },
    359        "There should be an active highlight with a random effect"
    360      );
    361    }
    362 
    363    let highlight = document.getElementById("UITourHighlight");
    364    is_element_hidden(highlight, "Highlight should initially be hidden");
    365 
    366    gContentAPI.showHighlight("urlbar");
    367    waitForElementToBeVisible(
    368      highlight,
    369      checkDefaultEffect,
    370      "Highlight should be shown after showHighlight()"
    371    );
    372  },
    373  function test_highlight_effect_unsupported(done) {
    374    function checkUnsupportedEffect() {
    375      is(
    376        highlight.getAttribute("active"),
    377        "none",
    378        "No effect should be used when an unsupported effect is requested"
    379      );
    380      done();
    381    }
    382 
    383    let highlight = document.getElementById("UITourHighlight");
    384    is_element_hidden(highlight, "Highlight should initially be hidden");
    385 
    386    gContentAPI.showHighlight("urlbar", "__UNSUPPORTED__");
    387    waitForElementToBeVisible(
    388      highlight,
    389      checkUnsupportedEffect,
    390      "Highlight should be shown after showHighlight()"
    391    );
    392  },
    393  function test_info_1(done) {
    394    let popup = document.getElementById("UITourTooltip");
    395    let title = document.getElementById("UITourTooltipTitle");
    396    let desc = document.getElementById("UITourTooltipDescription");
    397    let icon = document.getElementById("UITourTooltipIcon");
    398    let buttons = document.getElementById("UITourTooltipButtons");
    399 
    400    popup.addEventListener(
    401      "popupshown",
    402      function () {
    403        is(
    404          popup.anchorNode,
    405          document.getElementById("urlbar"),
    406          "Popup should be anchored to the urlbar"
    407        );
    408        is(title.textContent, "test title", "Popup should have correct title");
    409        is(
    410          desc.textContent,
    411          "test text",
    412          "Popup should have correct description text"
    413        );
    414        is(icon.src, "", "Popup should have no icon");
    415        is(buttons.hasChildNodes(), false, "Popup should have no buttons");
    416 
    417        popup.addEventListener(
    418          "popuphidden",
    419          function () {
    420            popup.addEventListener(
    421              "popupshown",
    422              function () {
    423                done();
    424              },
    425              { once: true }
    426            );
    427 
    428            gContentAPI.showInfo("urlbar", "test title", "test text");
    429          },
    430          { once: true }
    431        );
    432        gContentAPI.hideInfo();
    433      },
    434      { once: true }
    435    );
    436 
    437    gContentAPI.showInfo("urlbar", "test title", "test text");
    438  },
    439  taskify(async function test_info_2() {
    440    let popup = document.getElementById("UITourTooltip");
    441    let title = document.getElementById("UITourTooltipTitle");
    442    let desc = document.getElementById("UITourTooltipDescription");
    443    let icon = document.getElementById("UITourTooltipIcon");
    444    let buttons = document.getElementById("UITourTooltipButtons");
    445 
    446    await showInfoPromise("urlbar", "urlbar title", "urlbar text");
    447 
    448    is(
    449      popup.anchorNode,
    450      document.getElementById("urlbar"),
    451      "Popup should be anchored to the urlbar"
    452    );
    453    is(title.textContent, "urlbar title", "Popup should have correct title");
    454    is(
    455      desc.textContent,
    456      "urlbar text",
    457      "Popup should have correct description text"
    458    );
    459    is(icon.src, "", "Popup should have no icon");
    460    is(buttons.hasChildNodes(), false, "Popup should have no buttons");
    461 
    462    // Place the search bar in the navigation toolbar temporarily.
    463    await gCUITestUtils.addSearchBar();
    464 
    465    await showInfoPromise("search", "search title", "search text");
    466 
    467    is(
    468      popup.anchorNode,
    469      Services.prefs.getBoolPref("browser.search.widget.new")
    470        ? document.getElementById("searchbar-new")
    471        : document.getElementById("searchbar"),
    472      "Popup should be anchored to the searchbar"
    473    );
    474    is(title.textContent, "search title", "Popup should have correct title");
    475    is(
    476      desc.textContent,
    477      "search text",
    478      "Popup should have correct description text"
    479    );
    480 
    481    gCUITestUtils.removeSearchBar();
    482  }),
    483  function test_getConfigurationVersion(done) {
    484    function callback(result) {
    485      Assert.notStrictEqual(
    486        typeof result.version,
    487        "undefined",
    488        "Check version isn't undefined."
    489      );
    490      is(
    491        result.version,
    492        Services.appinfo.version,
    493        "Should have the same version property."
    494      );
    495      is(
    496        result.defaultUpdateChannel,
    497        UpdateUtils.getUpdateChannel(false),
    498        "Should have the correct update channel."
    499      );
    500      done();
    501    }
    502 
    503    gContentAPI.getConfiguration("appinfo", callback);
    504  },
    505  function test_getConfigurationDistribution(done) {
    506    gContentAPI.getConfiguration("appinfo", result => {
    507      Assert.notStrictEqual(
    508        typeof result.distribution,
    509        "undefined",
    510        "Check distribution isn't undefined."
    511      );
    512      // distribution id defaults to "default" for most builds, and
    513      // "mozilla-MSIX" for MSIX builds.
    514      is(
    515        result.distribution,
    516        AppConstants.platform === "win" &&
    517          Services.sysinfo.getProperty("hasWinPackageId")
    518          ? "mozilla-MSIX"
    519          : "default",
    520        'Should be "default" without preference set.'
    521      );
    522 
    523      let defaults = Services.prefs.getDefaultBranch("distribution.");
    524      let testDistributionID = "TestDistribution";
    525      defaults.setCharPref("id", testDistributionID);
    526      gContentAPI.getConfiguration("appinfo", result2 => {
    527        Assert.notStrictEqual(
    528          typeof result2.distribution,
    529          "undefined",
    530          "Check distribution isn't undefined."
    531        );
    532        is(
    533          result2.distribution,
    534          testDistributionID,
    535          "Should have the distribution as set in preference."
    536        );
    537 
    538        done();
    539      });
    540    });
    541  },
    542  function test_getConfigurationProfileAge(done) {
    543    gContentAPI.getConfiguration("appinfo", result => {
    544      Assert.strictEqual(
    545        typeof result.profileCreatedWeeksAgo,
    546        "number",
    547        "profileCreatedWeeksAgo should be number."
    548      );
    549      Assert.strictEqual(
    550        result.profileResetWeeksAgo,
    551        null,
    552        "profileResetWeeksAgo should be null."
    553      );
    554 
    555      // Set profile reset date to 15 days ago.
    556      ProfileAge().then(profileAccessor => {
    557        profileAccessor.recordProfileReset(
    558          Date.now() - 15 * 24 * 60 * 60 * 1000
    559        );
    560        gContentAPI.getConfiguration("appinfo", result2 => {
    561          Assert.strictEqual(
    562            typeof result2.profileResetWeeksAgo,
    563            "number",
    564            "profileResetWeeksAgo should be number."
    565          );
    566          is(
    567            result2.profileResetWeeksAgo,
    568            2,
    569            "profileResetWeeksAgo should be 2."
    570          );
    571          done();
    572        });
    573      });
    574    });
    575  },
    576  function test_addToolbarButton(done) {
    577    let placement = CustomizableUI.getPlacementOfWidget("panic-button");
    578    is(placement, null, "default UI has panic button in the palette");
    579 
    580    gContentAPI.getConfiguration("availableTargets", data => {
    581      let available = data.targets.includes("forget");
    582      ok(!available, "Forget button should not be available by default");
    583 
    584      gContentAPI.addNavBarWidget("forget", () => {
    585        info("addNavBarWidget callback successfully called");
    586 
    587        let updatedPlacement =
    588          CustomizableUI.getPlacementOfWidget("panic-button");
    589        is(updatedPlacement.area, CustomizableUI.AREA_NAVBAR);
    590 
    591        gContentAPI.getConfiguration("availableTargets", data2 => {
    592          let updatedAvailable = data2.targets.includes("forget");
    593          ok(updatedAvailable, "Forget button should now be available");
    594 
    595          // Cleanup
    596          CustomizableUI.removeWidgetFromArea("panic-button");
    597          done();
    598        });
    599      });
    600    });
    601  },
    602  taskify(async function test_search() {
    603    let defaultEngine = await Services.search.getDefault();
    604    let visibleEngines = await Services.search.getVisibleEngines();
    605    let expectedEngines = visibleEngines
    606      .filter(engine => engine.isAppProvided)
    607      .map(engine => "searchEngine-" + engine.id);
    608 
    609    let data = await new Promise(resolve =>
    610      gContentAPI.getConfiguration("search", resolve)
    611    );
    612    let engines = data.engines;
    613    ok(Array.isArray(engines), "data.engines should be an array");
    614    is(
    615      engines.sort().toString(),
    616      expectedEngines.sort().toString(),
    617      "Engines should be as expected"
    618    );
    619 
    620    is(
    621      data.searchEngineIdentifier,
    622      defaultEngine.id,
    623      "the searchEngineIdentifier property should contain the defaultEngine's id"
    624    );
    625 
    626    let someOtherEngineID = data.engines.filter(
    627      t => t != "searchEngine-" + defaultEngine.id
    628    )[0];
    629    someOtherEngineID = someOtherEngineID.replace(/^searchEngine-/, "");
    630 
    631    Services.telemetry.clearEvents();
    632    Services.fog.testResetFOG();
    633 
    634    await new Promise(resolve => {
    635      let observe = function (subject, topic, verb) {
    636        Services.obs.removeObserver(observe, "browser-search-engine-modified");
    637        info("browser-search-engine-modified: " + verb);
    638        if (verb == "engine-default") {
    639          is(
    640            Services.search.defaultEngine.id,
    641            someOtherEngineID,
    642            "correct engine was switched to"
    643          );
    644          resolve();
    645        }
    646      };
    647      Services.obs.addObserver(observe, "browser-search-engine-modified");
    648      registerCleanupFunction(async () => {
    649        await Services.search.setDefault(
    650          defaultEngine,
    651          Ci.nsISearchService.CHANGE_REASON_UNKNOWN
    652        );
    653      });
    654 
    655      gContentAPI.setDefaultSearchEngine(someOtherEngineID);
    656    });
    657 
    658    let engine = Services.search.getEngineById(someOtherEngineID);
    659 
    660    let submissionUrl = engine
    661      .getSubmission("dummy")
    662      .uri.spec.replace("dummy", "");
    663 
    664    let snapshot = await Glean.searchEngineDefault.changed.testGetValue();
    665    delete snapshot[0].timestamp;
    666    Assert.deepEqual(
    667      snapshot[0],
    668      {
    669        category: "search.engine.default",
    670        name: "changed",
    671        extra: {
    672          change_reason: "uitour",
    673          previous_engine_id: defaultEngine.telemetryId,
    674          new_engine_id: engine.telemetryId,
    675          new_display_name: engine.name,
    676          new_load_path: engine.wrappedJSObject._loadPath,
    677          // Glean has a limit of 100 characters.
    678          new_submission_url: submissionUrl.slice(0, 100),
    679        },
    680      },
    681      "Should have received the correct event details"
    682    );
    683  }),
    684  taskify(async function test_treatment_tag() {
    685    await gContentAPI.setTreatmentTag("foobar", "baz");
    686    await gContentAPI.getTreatmentTag("foobar", data => {
    687      is(data.value, "baz", "set and retrieved treatmentTag");
    688    });
    689  }),
    690 
    691  // Make sure this test is last in the file so the appMenu gets left open and done will confirm it got tore down.
    692  taskify(async function cleanupMenus() {
    693    let shownPromise = promisePanelShown(window);
    694    gContentAPI.showMenu("appMenu");
    695    await shownPromise;
    696  }),
    697 ];