summaryrefslogtreecommitdiffstats
path: root/browser/components/downloads/test/browser/browser_downloads_panel_block.js
blob: d1791a586279b40da9b517dc519a4d2394bf15f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/* Any copyright is dedicated to the Public Domain.
 * https://creativecommons.org/publicdomain/zero/1.0/ */

/* eslint-disable mozilla/no-arbitrary-setTimeout */
"use strict";

add_task(async function mainTest() {
  await task_resetState();

  let verdicts = [
    Downloads.Error.BLOCK_VERDICT_UNCOMMON,
    Downloads.Error.BLOCK_VERDICT_MALWARE,
    Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED,
    Downloads.Error.BLOCK_VERDICT_INSECURE,
  ];
  await task_addDownloads(verdicts.map(v => makeDownload(v)));

  // Check that the richlistitem for each download is correct.
  for (let i = 0; i < verdicts.length; i++) {
    await openPanel();

    // Handle items backwards, using lastElementChild, to ensure there's no
    // code wrongly resetting the selection to the first item during the process.
    let item = DownloadsView.richListBox.lastElementChild;

    info("Open the panel and click the item to show the subview.");
    let viewPromise = promiseViewShown(DownloadsBlockedSubview.subview);
    EventUtils.synthesizeMouseAtCenter(item, {});
    await viewPromise;

    // Items are listed in newest-to-oldest order, so e.g. the first item's
    // verdict is the last element in the verdicts array.
    Assert.ok(
      DownloadsBlockedSubview.subview.getAttribute("verdict"),
      verdicts[verdicts.count - i - 1]
    );

    info("Go back to the main view.");
    viewPromise = promiseViewShown(DownloadsBlockedSubview.mainView);
    DownloadsBlockedSubview.panelMultiView.goBack();
    await viewPromise;

    info("Show the subview again.");
    viewPromise = promiseViewShown(DownloadsBlockedSubview.subview);
    EventUtils.synthesizeMouseAtCenter(item, {});
    await viewPromise;

    info("Click the Open button.");
    // The download should be unblocked and then opened,
    // i.e., unblockAndOpenDownload() should be called on the item.  The panel
    // should also be closed as a result, so wait for that too.
    let unblockPromise = promiseUnblockAndSaveCalled(item);
    let hidePromise = promisePanelHidden();
    // Simulate a mousemove to ensure it's not wrongly being handled by the
    // panel as the user changing download selection.
    EventUtils.synthesizeMouseAtCenter(
      DownloadsBlockedSubview.elements.unblockButton,
      { type: "mousemove" }
    );
    EventUtils.synthesizeMouseAtCenter(
      DownloadsBlockedSubview.elements.unblockButton,
      {}
    );
    info("waiting for unblockOpen");
    await unblockPromise;
    info("waiting for hide panel");
    await hidePromise;

    window.focus();
    await SimpleTest.promiseFocus(window);

    info("Reopen the panel and show the subview again.");
    await openPanel();
    viewPromise = promiseViewShown(DownloadsBlockedSubview.subview);
    EventUtils.synthesizeMouseAtCenter(item, {});
    await viewPromise;

    info("Click the Remove button.");
    // The panel should close and the item should be removed from it.
    hidePromise = promisePanelHidden();
    EventUtils.synthesizeMouseAtCenter(
      DownloadsBlockedSubview.elements.deleteButton,
      {}
    );
    info("Waiting for hide panel");
    await hidePromise;

    info("Open the panel again and check the item is gone.");
    await openPanel();
    Assert.ok(!item.parentNode);

    hidePromise = promisePanelHidden();
    DownloadsPanel.hidePanel();
    await hidePromise;
  }

  await task_resetState();
});

async function openPanel() {
  // This function is insane but something intermittently causes the panel to be
  // closed as soon as it's opening on Linux ASAN.  Maybe it would also happen
  // on other build machines if the test ran often enough.  Not only is the
  // panel closed, it's closed while it's opening, leaving DownloadsPanel._state
  // such that when you try to open the panel again, it thinks it's already
  // open, but it's not.  The result is that the test times out.
  //
  // What this does is call DownloadsPanel.showPanel over and over again until
  // the panel is really open.  There are a few wrinkles:
  //
  // (1) When panel.state is "open", check four more times (for a total of five)
  // before returning to make the panel stays open.
  // (2) If the panel is not open, check the _state.  It should be either
  // kStateUninitialized or kStateHidden.  If it's not, then the panel is in the
  // process of opening -- or maybe it's stuck in that process -- so reset the
  // _state to kStateHidden.
  // (3) If the _state is not kStateUninitialized or kStateHidden, then it may
  // actually be properly opening and not stuck at all.  To avoid always closing
  // the panel while it's properly opening, use an exponential backoff mechanism
  // for retries.
  //
  // If all that fails, then the test will time out, but it would have timed out
  // anyway.

  await promiseFocus();
  await new Promise(resolve => {
    let verifyCount = 5;
    let backoff = 0;
    let iBackoff = 0;
    let interval = setInterval(() => {
      if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") {
        if (verifyCount > 0) {
          verifyCount--;
        } else {
          clearInterval(interval);
          resolve();
        }
      } else if (iBackoff < backoff) {
        // Keep backing off before trying again.
        iBackoff++;
      } else {
        // Try (or retry) opening the panel.
        verifyCount = 5;
        backoff = Math.max(1, 2 * backoff);
        iBackoff = 0;
        if (DownloadsPanel._state != DownloadsPanel.kStateUninitialized) {
          DownloadsPanel._state = DownloadsPanel.kStateHidden;
        }
        DownloadsPanel.showPanel();
      }
    }, 100);
  });
}

function promisePanelHidden() {
  return BrowserTestUtils.waitForEvent(DownloadsPanel.panel, "popuphidden");
}

function makeDownload(verdict) {
  return {
    state: DownloadsCommon.DOWNLOAD_DIRTY,
    hasBlockedData: true,
    errorObj: {
      result: Cr.NS_ERROR_FAILURE,
      message: "Download blocked.",
      becauseBlocked: true,
      becauseBlockedByReputationCheck: true,
      reputationCheckVerdict: verdict,
    },
  };
}

function promiseViewShown(view) {
  return BrowserTestUtils.waitForEvent(view, "ViewShown");
}

function promiseUnblockAndSaveCalled(item) {
  return new Promise(resolve => {
    let realFn = item._shell.unblockAndSave;
    item._shell.unblockAndSave = async () => {
      item._shell.unblockAndSave = realFn;
      resolve();
    };
  });
}