tor-browser

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

test_taskbarTabs_pin.js (10811B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 ChromeUtils.defineESModuleGetters(this, {
      7  FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs",
      8  MockRegistrar: "resource://testing-common/MockRegistrar.sys.mjs",
      9  sinon: "resource://testing-common/Sinon.sys.mjs",
     10  ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs",
     11  TaskbarTabsPin: "resource:///modules/taskbartabs/TaskbarTabsPin.sys.mjs",
     12  TaskbarTabsRegistry:
     13    "resource:///modules/taskbartabs/TaskbarTabsRegistry.sys.mjs",
     14  XPCOMUtils: "resource://gre/modules/XPCOMUtils.sys.mjs",
     15 });
     16 
     17 XPCOMUtils.defineLazyServiceGetters(this, {
     18  imgTools: ["@mozilla.org/image/tools;1", Ci.imgITools],
     19 });
     20 
     21 // We want to mock the native XPCOM interfaces of the initialized
     22 // `ShellService.shellService`, but those interfaces are frozen. Instead we
     23 // proxy `ShellService.shellService` and mock it.
     24 let gCreateWindowsIcon = ShellService.shellService.createWindowsIcon;
     25 let gOverrideWindowsIconFileOnce;
     26 const kMockNativeShellService = {
     27  ...ShellService.shellService,
     28  createWindowsIcon: sinon
     29    .stub()
     30    .callsFake(async (aIconFile, aImgContainer) => {
     31      if (gOverrideWindowsIconFileOnce) {
     32        await gCreateWindowsIcon(gOverrideWindowsIconFileOnce, aImgContainer);
     33        gOverrideWindowsIconFileOnce = null;
     34      }
     35    }),
     36  createShortcut: sinon.stub().resolves("dummy_path"),
     37  deleteShortcut: sinon.stub().resolves("dummy_path"),
     38  pinShortcutToTaskbar: sinon.stub().resolves(),
     39  getTaskbarTabShortcutPath: sinon
     40    .stub()
     41    .returns(FileTestUtils.getTempFile().parent.path),
     42  unpinShortcutFromTaskbar: sinon.stub(),
     43 };
     44 
     45 sinon.stub(ShellService, "shellService").value(kMockNativeShellService);
     46 
     47 sinon.stub(TaskbarTabsPin, "_getLocalization").returns({
     48  formatValue(msg) {
     49    // Slash must also be sanitized, so it should appear as '_' in paths.
     50    return `[formatValue/${msg}]`;
     51  },
     52 });
     53 
     54 registerCleanupFunction(() => {
     55  sinon.restore();
     56 });
     57 
     58 // Favicons are written to the profile directory, ensure it exists.
     59 do_get_profile();
     60 
     61 const kFaviconService = Cc[
     62  "@mozilla.org/browser/favicon-service;1"
     63 ].createInstance(Ci.nsIFaviconService);
     64 
     65 let gPngFavicon;
     66 let gSvgFavicon;
     67 add_setup(async () => {
     68  const pngFile = do_get_file("favicon-normal16.png");
     69  const pngData = await IOUtils.read(pngFile.path);
     70  gPngFavicon = { rawData: pngData.buffer, mimeType: "image/png" };
     71 
     72  const svgFile = do_get_file("icon.svg");
     73  gSvgFavicon = {
     74    dataURI: Services.io.newFileURI(svgFile),
     75    mimeType: "image/svg+xml",
     76  };
     77 });
     78 
     79 let gFavicon;
     80 
     81 const kMockFaviconService = {
     82  QueryInterface: ChromeUtils.generateQI(["nsIFaviconService"]),
     83  getFaviconForPage: sinon.stub().callsFake(async () => {
     84    return gFavicon;
     85  }),
     86  get defaultFavicon() {
     87    return kFaviconService.defaultFavicon;
     88  },
     89 };
     90 const kDefaultIconSpy = sinon.spy(kMockFaviconService, "defaultFavicon", [
     91  "get",
     92 ]);
     93 
     94 function shellPinCalled(aTaskbarTab) {
     95  ok(
     96    kMockNativeShellService.createWindowsIcon.calledOnce,
     97    `Icon creation should have been called.`
     98  );
     99  ok(
    100    kMockNativeShellService.createShortcut.calledOnce,
    101    `Shortcut creation should have been called.`
    102  );
    103  ok(
    104    kMockNativeShellService.pinShortcutToTaskbar.calledOnce,
    105    `Pin to taskbar should have been called.`
    106  );
    107  Assert.equal(
    108    kMockNativeShellService.pinShortcutToTaskbar.firstCall.args[1],
    109    kMockNativeShellService.createShortcut.firstCall.args[6],
    110    `The created and pinned shortcuts should be in the same folder.`
    111  );
    112  Assert.equal(
    113    kMockNativeShellService.pinShortcutToTaskbar.firstCall.args[2],
    114    kMockNativeShellService.createShortcut.firstCall.args[7],
    115    `The created and pinned shortcuts should be the same file.`
    116  );
    117  Assert.equal(
    118    kMockNativeShellService.pinShortcutToTaskbar.firstCall.args[2],
    119    aTaskbarTab.shortcutRelativePath,
    120    `The pinned shortcut should be the saved shortcut.`
    121  );
    122 }
    123 
    124 function shellUnpinCalled() {
    125  ok(
    126    kMockNativeShellService.deleteShortcut.calledOnce,
    127    `Unpin from taskbar should have been called.`
    128  );
    129  ok(
    130    kMockNativeShellService.unpinShortcutFromTaskbar.calledOnce,
    131    `Unpin from taskbar should have been called.`
    132  );
    133 }
    134 
    135 MockRegistrar.register(
    136  "@mozilla.org/browser/favicon-service;1",
    137  kMockFaviconService
    138 );
    139 
    140 async function testWrittenIconFile(aIconFile) {
    141  const data = await IOUtils.read(aIconFile.path);
    142  const imgContainer = imgTools.decodeImageFromArrayBuffer(
    143    data.buffer,
    144    "image/vnd.microsoft.icon"
    145  );
    146  equal(
    147    imgContainer.width,
    148    256,
    149    "Image written to disk should be 256px width."
    150  );
    151  equal(
    152    imgContainer.height,
    153    256,
    154    "Image written to disk should be 256px height."
    155  );
    156 }
    157 
    158 const url = Services.io.newURI("https://www.test.com");
    159 const userContextId = 0;
    160 
    161 const registry = new TaskbarTabsRegistry();
    162 const taskbarTab = createTaskbarTab(registry, url, userContextId);
    163 
    164 const patchedSpy = sinon.stub();
    165 registry.on(TaskbarTabsRegistry.events.patched, patchedSpy);
    166 
    167 function getTempFile() {
    168  let path = do_get_tempdir();
    169  let filename = Services.uuid.generateUUID().toString().slice(1, -1);
    170  path.append(filename + ".ico");
    171  return path;
    172 }
    173 
    174 add_task(async function test_pin_existing_favicon_raster() {
    175  sinon.resetHistory();
    176  gFavicon = gPngFavicon;
    177 
    178  let iconFile = getTempFile();
    179  gOverrideWindowsIconFileOnce = iconFile;
    180 
    181  await TaskbarTabsPin.pinTaskbarTab(taskbarTab, registry);
    182 
    183  ok(
    184    kMockFaviconService.getFaviconForPage.calledOnce,
    185    "The favicon for the page should have attempted to be retrieved."
    186  );
    187  const imgContainer =
    188    kMockNativeShellService.createWindowsIcon.firstCall.args[1];
    189  equal(imgContainer.width, 256, "Image should be scaled to 256px width.");
    190  equal(imgContainer.height, 256, "Image should be scaled to 256px height.");
    191  ok(
    192    kDefaultIconSpy.get.notCalled,
    193    "The default icon should not be used when a favicon exists for the page."
    194  );
    195 
    196  await testWrittenIconFile(iconFile);
    197 
    198  shellPinCalled(taskbarTab);
    199 });
    200 
    201 add_task(async function test_pin_existing_favicon_vector() {
    202  sinon.resetHistory();
    203  gFavicon = gSvgFavicon;
    204 
    205  let iconFile = getTempFile();
    206  gOverrideWindowsIconFileOnce = iconFile;
    207 
    208  await TaskbarTabsPin.pinTaskbarTab(taskbarTab, registry);
    209 
    210  ok(
    211    kDefaultIconSpy.get.notCalled,
    212    "The default icon should not be used when a vector favicon exists for the page."
    213  );
    214 
    215  await testWrittenIconFile(iconFile);
    216 
    217  shellPinCalled(taskbarTab);
    218 });
    219 
    220 add_task(async function test_pin_missing_favicon() {
    221  sinon.resetHistory();
    222  gFavicon = null;
    223  await TaskbarTabsPin.pinTaskbarTab(taskbarTab, registry);
    224 
    225  ok(
    226    kMockFaviconService.getFaviconForPage.calledOnce,
    227    "The favicon for the page should have attempted to be retrieved."
    228  );
    229  ok(
    230    kDefaultIconSpy.get.called,
    231    "The default icon should be used when a favicon does not exist for the page."
    232  );
    233 
    234  shellPinCalled(taskbarTab);
    235 });
    236 
    237 add_task(async function test_pin_location() {
    238  sinon.resetHistory();
    239 
    240  await TaskbarTabsPin.pinTaskbarTab(taskbarTab, registry);
    241  const spy = kMockNativeShellService.createShortcut;
    242  ok(spy.calledOnce, "A shortcut was created");
    243  Assert.equal(
    244    spy.firstCall.args[6],
    245    "Programs",
    246    "The shortcut went into the Start Menu folder"
    247  );
    248  Assert.equal(
    249    spy.firstCall.args[7],
    250    `[formatValue_taskbar-tab-shortcut-folder]\\${taskbarTab.name}.lnk`,
    251    "The shortcut should be in a subdirectory and have a default name"
    252  );
    253 
    254  Assert.equal(
    255    taskbarTab.shortcutRelativePath,
    256    spy.firstCall.args[7],
    257    "Correct relative path was saved to the taskbar tab"
    258  );
    259  Assert.equal(patchedSpy.callCount, 1, "A single patched event was emitted");
    260 });
    261 
    262 add_task(async function test_pin_location_dos_name() {
    263  const parsedURI = Services.io.newURI("https://aux.test");
    264  const invalidTaskbarTab = createTaskbarTab(registry, parsedURI, 0);
    265  sinon.resetHistory();
    266 
    267  await TaskbarTabsPin.pinTaskbarTab(invalidTaskbarTab, registry);
    268  const spy = kMockNativeShellService.createShortcut;
    269  ok(spy.calledOnce, "A shortcut was created");
    270  Assert.equal(
    271    spy.firstCall.args[6],
    272    "Programs",
    273    "The shortcut went into the Start Menu folder"
    274  );
    275  // 'Untitled' is the default selected by the MIME code, since
    276  // AUX is a reserved name on Windows.
    277  Assert.equal(
    278    spy.firstCall.args[7],
    279    "[formatValue_taskbar-tab-shortcut-folder]\\Untitled.lnk",
    280    "The shortcut should be in a subdirectory and have a default name"
    281  );
    282  Assert.equal(
    283    invalidTaskbarTab.shortcutRelativePath,
    284    spy.firstCall.args[7],
    285    "Correct relative path was saved to the taskbar tab"
    286  );
    287  Assert.equal(patchedSpy.callCount, 1, "A single patched event was emitted");
    288 
    289  registry.removeTaskbarTab(invalidTaskbarTab.id);
    290 });
    291 
    292 add_task(async function test_pin_location_bad_characters() {
    293  const parsedURI = Services.io.newURI("https://another.test");
    294  const invalidTaskbarTab = createTaskbarTab(registry, parsedURI, 0, {
    295    manifest: {
    296      name: "** :\t\r\n \\\\ >> Not a valid. filename??! << // |||: **.",
    297    },
    298  });
    299  sinon.resetHistory();
    300 
    301  await TaskbarTabsPin.pinTaskbarTab(invalidTaskbarTab);
    302  const spy = kMockNativeShellService.createShortcut;
    303  ok(spy.calledOnce, "A shortcut was created");
    304  Assert.equal(
    305    spy.firstCall.args[6],
    306    "Programs",
    307    "The shortcut went into the Start Menu folder"
    308  );
    309  Assert.equal(
    310    spy.firstCall.args[7],
    311    "[formatValue_taskbar-tab-shortcut-folder]\\__ ____ __ __ Not a valid. filename__! __ __ ____ __..lnk",
    312    "The shortcut should have invalid characters filtered out."
    313  );
    314  registry.removeTaskbarTab(invalidTaskbarTab.id);
    315 });
    316 
    317 add_task(async function test_pin_location_lnk_extension() {
    318  const parsedURI = Services.io.newURI("https://another.test");
    319  const invalidTaskbarTab = createTaskbarTab(registry, parsedURI, 0, {
    320    manifest: {
    321      name: "coolstartup.lnk",
    322    },
    323  });
    324  sinon.resetHistory();
    325 
    326  await TaskbarTabsPin.pinTaskbarTab(invalidTaskbarTab);
    327  const spy = kMockNativeShellService.createShortcut;
    328  ok(spy.calledOnce, "A shortcut was created");
    329  Assert.equal(
    330    spy.firstCall.args[6],
    331    "Programs",
    332    "The shortcut went into the Start Menu folder"
    333  );
    334  Assert.equal(
    335    spy.firstCall.args[7],
    336    "[formatValue_taskbar-tab-shortcut-folder]\\coolstartup.lnk.lnk",
    337    "The shortcut should keep the .lnk intact."
    338  );
    339 
    340  registry.removeTaskbarTab(invalidTaskbarTab.id);
    341 });
    342 
    343 add_task(async function test_unpin() {
    344  sinon.resetHistory();
    345  await TaskbarTabsPin.unpinTaskbarTab(taskbarTab, registry);
    346 
    347  shellUnpinCalled();
    348  Assert.equal(
    349    taskbarTab.shortcutRelativePath,
    350    null,
    351    "Shortcut relative path was removed from the taskbar tab"
    352  );
    353  Assert.equal(patchedSpy.callCount, 1, "A single patched event was emitted");
    354 });