summaryrefslogtreecommitdiffstats
path: root/browser/components/extensions/test/browser/browser_ExtensionControlledPopup.js
blob: c920bf75b5ee6612d1f5310da90b748d71e8f6a0 (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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */

"use strict";
const { sinon } = ChromeUtils.importESModule(
  "resource://testing-common/Sinon.sys.mjs"
);

ChromeUtils.defineESModuleGetters(this, {
  ExtensionControlledPopup:
    "resource:///modules/ExtensionControlledPopup.sys.mjs",
  ExtensionSettingsStore:
    "resource://gre/modules/ExtensionSettingsStore.sys.mjs",
});

function createMarkup(doc, popup) {
  let panel = ExtensionControlledPopup._getAndMaybeCreatePanel(doc);
  let popupnotification = doc.createXULElement("popupnotification");
  let attributes = {
    id: "extension-controlled-notification",
    class: "extension-controlled-notification",
    popupid: "extension-controlled",
    hidden: "true",
    label: "ExtControlled",
    buttonlabel: "Keep Changes",
    buttonaccesskey: "K",
    secondarybuttonlabel: "Restore Settings",
    secondarybuttonaccesskey: "R",
    closebuttonhidden: "true",
    dropmarkerhidden: "true",
    checkboxhidden: "true",
  };
  Object.entries(attributes).forEach(([key, value]) => {
    popupnotification.setAttribute(key, value);
  });
  let content = doc.createXULElement("popupnotificationcontent");
  content.setAttribute("orient", "vertical");
  let description = doc.createXULElement("description");
  description.setAttribute("id", "extension-controlled-description");
  content.appendChild(description);
  popupnotification.appendChild(content);
  panel.appendChild(popupnotification);

  registerCleanupFunction(function removePopup() {
    popupnotification.remove();
  });

  return { panel, popupnotification };
}

/*
 * This function is a unit test for ExtensionControlledPopup. It is also tested
 * where it is being used (currently New Tab and homepage). An empty extension
 * is used along with the expected markup as an example.
 */
add_task(async function testExtensionControlledPopup() {
  let id = "ext-controlled@mochi.test";
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      browser_specific_settings: { gecko: { id } },
      name: "Ext Controlled",
    },
    // We need to be able to find the extension using AddonManager.
    useAddonManager: "temporary",
  });

  await extension.startup();
  let addon = await AddonManager.getAddonByID(id);
  await ExtensionSettingsStore.initialize();

  let confirmedType = "extension-controlled-confirmed";
  let onObserverAdded = sinon.spy();
  let onObserverRemoved = sinon.spy();
  let observerTopic = "extension-controlled-event";
  let beforeDisableAddon = sinon.spy();
  let settingType = "extension-controlled";
  let settingKey = "some-key";
  let popup = new ExtensionControlledPopup({
    confirmedType,
    observerTopic,
    popupnotificationId: "extension-controlled-notification",
    settingType,
    settingKey,
    descriptionId: "extension-controlled-description",
    descriptionMessageId: "newTabControlled.message2",
    learnMoreMessageId: "newTabControlled.learnMore",
    learnMoreLink: "extension-controlled",
    onObserverAdded,
    onObserverRemoved,
    beforeDisableAddon,
  });

  let doc = Services.wm.getMostRecentWindow("navigator:browser").document;
  let { panel, popupnotification } = createMarkup(doc, popup);

  function openPopupWithEvent() {
    let popupShown = promisePopupShown(panel);
    Services.obs.notifyObservers(null, observerTopic);
    return popupShown;
  }

  function closePopupWithAction(action, extensionId) {
    let done;
    if (action == "ignore") {
      panel.hidePopup();
    } else if (action == "button") {
      done = TestUtils.waitForCondition(() => {
        return ExtensionSettingsStore.getSetting(confirmedType, id, id).value;
      });
      popupnotification.button.click();
    } else if (action == "secondarybutton") {
      done = awaitEvent("shutdown", id);
      popupnotification.secondaryButton.click();
    }
    return done;
  }

  // No callbacks are initially called.
  ok(!onObserverAdded.called, "No observer has been added");
  ok(!onObserverRemoved.called, "No observer has been removed");
  ok(!beforeDisableAddon.called, "Settings have not been restored");

  // Add the setting and observer.
  await ExtensionSettingsStore.addSetting(
    id,
    settingType,
    settingKey,
    "controlled",
    () => "init"
  );
  await popup.addObserver(id);

  // Ensure the panel isn't open.
  ok(onObserverAdded.called, "Observing the event");
  onObserverAdded.resetHistory();
  ok(!onObserverRemoved.called, "Observing the event");
  ok(!beforeDisableAddon.called, "Settings have not been restored");
  ok(panel.getAttribute("panelopen") != "true", "The panel is closed");
  is(popupnotification.hidden, true, "The popup is hidden");
  is(addon.userDisabled, false, "The extension is enabled");
  is(
    await popup.userHasConfirmed(id),
    false,
    "The user is not initially confirmed"
  );

  // The popup should opened based on the observer event.
  await openPopupWithEvent();

  ok(!onObserverAdded.called, "Only one observer has been registered");
  ok(onObserverRemoved.called, "The observer was removed");
  onObserverRemoved.resetHistory();
  ok(!beforeDisableAddon.called, "Settings have not been restored");
  is(panel.getAttribute("panelopen"), "true", "The panel is open");
  is(popupnotification.hidden, false, "The popup content is visible");
  is(await popup.userHasConfirmed(id), false, "The user has not confirmed yet");

  // Verify the description is populated.
  let description = doc.getElementById("extension-controlled-description");
  is(
    description.textContent,
    "An extension,  Ext Controlled, changed the page you see when you open a new tab.Learn more",
    "The extension name is in the description"
  );
  let link = description.querySelector("label");
  is(
    link.href,
    "http://127.0.0.1:8888/support-dummy/extension-controlled",
    "The link has the href set from learnMoreLink"
  );

  // Force close the popup, as if a user clicked away from it.
  await closePopupWithAction("ignore");

  // Nothing was recorded, but we won't show it again.
  ok(!onObserverAdded.called, "The observer hasn't changed");
  ok(!onObserverRemoved.called, "The observer hasn't changed");
  is(await popup.userHasConfirmed(id), false, "The user has not confirmed");
  is(addon.userDisabled, false, "The extension is still enabled");

  // Force add the observer again to keep changes.
  await popup.addObserver(id);
  ok(onObserverAdded.called, "The observer was added again");
  onObserverAdded.resetHistory();
  ok(!onObserverRemoved.called, "The observer is still registered");
  is(await popup.userHasConfirmed(id), false, "The user has not confirmed");

  // Wait for popup.
  await openPopupWithEvent();

  // Keep the changes.
  await closePopupWithAction("button");

  // The observer is removed, but the notification is saved.
  ok(!onObserverAdded.called, "The observer wasn't added");
  ok(onObserverRemoved.called, "The observer was removed");
  onObserverRemoved.resetHistory();
  is(await popup.userHasConfirmed(id), true, "The user has confirmed");
  is(addon.userDisabled, false, "The extension is still enabled");

  // Adding the observer again for this add-on won't work, since it is
  // confirmed.
  await popup.addObserver(id);
  ok(!onObserverAdded.called, "The observer isn't added");
  ok(!onObserverRemoved.called, "The observer isn't removed");
  is(await popup.userHasConfirmed(id), true, "The user has confirmed");

  // Clear that the user was notified.
  await popup.clearConfirmation(id);
  is(
    await popup.userHasConfirmed(id),
    false,
    "The user confirmation has been cleared"
  );

  // Force add the observer again to restore changes.
  await popup.addObserver(id);
  ok(onObserverAdded.called, "The observer was added a third time");
  onObserverAdded.resetHistory();
  ok(!onObserverRemoved.called, "The observer is still active");
  ok(!beforeDisableAddon.called, "We haven't disabled the add-on yet");
  is(await popup.userHasConfirmed(id), false, "The user has not confirmed");

  // Wait for popup.
  await openPopupWithEvent();

  // Restore the settings.
  await closePopupWithAction("secondarybutton");

  // The observer is removed and the add-on is now disabled.
  ok(!onObserverAdded.called, "There is no observer");
  ok(onObserverRemoved.called, "The observer has been removed");
  ok(beforeDisableAddon.called, "The beforeDisableAddon callback was fired");
  is(await popup.userHasConfirmed(id), false, "The user has not confirmed");
  is(addon.userDisabled, true, "The extension is now disabled");

  await extension.unload();
});