summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/plugins/browser_CTP_crashreporting.js
blob: bb52d5704dbc270bf7a9754951b0e1bbd5326a6a (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
var rootDir = getRootDirectory(gTestPath);
const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
const PLUGIN_PAGE = gTestRoot + "plugin_big.html";
const PLUGIN_SMALL_PAGE = gTestRoot + "plugin_small.html";

/**
 * Takes an nsIPropertyBag and converts it into a JavaScript Object. It
 * will also convert any nsIPropertyBag's within the nsIPropertyBag
 * recursively.
 *
 * @param aBag
 *        The nsIPropertyBag to convert.
 * @return Object
 *        Keyed on the names of the nsIProperty's within the nsIPropertyBag,
 *        and mapping to their values.
 */
function convertPropertyBag(aBag) {
  let result = {};
  let enumerator = aBag.enumerator;
  while (enumerator.hasMoreElements()) {
    let { name, value } = enumerator.getNext().QueryInterface(Ci.nsIProperty);
    if (value instanceof Ci.nsIPropertyBag) {
      value = convertPropertyBag(value);
    }
    result[name] = value;
  }
  return result;
}

add_task(function* setup() {
  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");

  // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables plugin
  // crash reports.  This test needs them enabled.  The test also needs a mock
  // report server, and fortunately one is already set up by toolkit/
  // crashreporter/test/Makefile.in.  Assign its URL to MOZ_CRASHREPORTER_URL,
  // which CrashSubmit.jsm uses as a server override.
  let env = Cc["@mozilla.org/process/environment;1"].
            getService(Components.interfaces.nsIEnvironment);
  let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
  let serverURL = env.get("MOZ_CRASHREPORTER_URL");
  env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
  env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);

  Services.prefs.setBoolPref("plugins.click_to_play", true);
  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);

  registerCleanupFunction(function cleanUp() {
    clearAllPluginPermissions();
    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
    env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
    env.set("MOZ_CRASHREPORTER_URL", serverURL);
    Services.prefs.clearUserPref("plugins.click_to_play");
    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
    window.focus();
  });
});

/**
 * Test that plugin crash submissions still work properly after
 * click-to-play activation.
 */
add_task(function*() {
  yield BrowserTestUtils.withNewTab({
    gBrowser,
    url: PLUGIN_PAGE,
  }, function* (browser) {
    // Work around for delayed PluginBindingAttached
    yield promiseUpdatePluginBindings(browser);

    let pluginInfo = yield promiseForPluginInfo("test", browser);
    ok(!pluginInfo.activated, "Plugin should not be activated");

    // Simulate clicking the "Allow Always" button.
    let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
    yield promiseForNotificationShown(notification, browser);
    PopupNotifications.panel.firstChild._primaryButton.click();

    // Prepare a crash report topic observer that only returns when
    // the crash report has been successfully sent.
    let crashReportChecker = (subject, data) => {
      return (data == "success");
    };
    let crashReportPromise = TestUtils.topicObserved("crash-report-status",
                                                     crashReportChecker);

    yield ContentTask.spawn(browser, null, function*() {
      let plugin = content.document.getElementById("test");
      plugin.QueryInterface(Ci.nsIObjectLoadingContent);

      yield ContentTaskUtils.waitForCondition(() => {
        return plugin.activated;
      }, "Waited too long for plugin to activate.");

      try {
        Components.utils.waiveXrays(plugin).crash();
      } catch (e) {
      }

      let doc = plugin.ownerDocument;

      let getUI = (anonid) => {
        return doc.getAnonymousElementByAttribute(plugin, "anonid", anonid);
      };

      // Now wait until the plugin crash report UI shows itself, which is
      // asynchronous.
      let statusDiv;

      yield ContentTaskUtils.waitForCondition(() => {
        statusDiv = getUI("submitStatus");
        return statusDiv.getAttribute("status") == "please";
      }, "Waited too long for plugin to show crash report UI");

      // Make sure the UI matches our expectations...
      let style = content.getComputedStyle(getUI("pleaseSubmit"));
      if (style.display != "block") {
        throw new Error(`Submission UI visibility is not correct. ` +
                        `Expected block style, got ${style.display}.`);
      }

      // Fill the crash report in with some test values that we'll test for in
      // the parent.
      getUI("submitComment").value = "a test comment";
      let optIn = getUI("submitURLOptIn");
      if (!optIn.checked) {
        throw new Error("URL opt-in should default to true.");
      }

      // Submit the report.
      optIn.click();
      getUI("submitButton").click();

      // And wait for the parent to say that the crash report was submitted
      // successfully.
      yield ContentTaskUtils.waitForCondition(() => {
        return statusDiv.getAttribute("status") == "success";
      }, "Timed out waiting for plugin binding to be in success state");
    });

    let [subject, ] = yield crashReportPromise;

    ok(subject instanceof Ci.nsIPropertyBag,
       "The crash report subject should be an nsIPropertyBag.");

    let crashData = convertPropertyBag(subject);
    ok(crashData.serverCrashID, "Should have a serverCrashID set.");

    // Remove the submitted report file after ensuring it exists.
    let file = Cc["@mozilla.org/file/local;1"]
                 .createInstance(Ci.nsILocalFile);
    file.initWithPath(Services.crashmanager._submittedDumpsDir);
    file.append(crashData.serverCrashID + ".txt");
    ok(file.exists(), "Submitted report file should exist");
    file.remove(false);

    ok(crashData.extra, "Extra data should exist");
    is(crashData.extra.PluginUserComment, "a test comment",
       "Comment in extra data should match comment in textbox");

    is(crashData.extra.PluginContentURL, undefined,
       "URL should be absent from extra data when opt-in not checked");
  });
});

/**
 * Test that plugin crash submissions still work properly after
 * click-to-play with the notification bar.
 */
add_task(function*() {
  yield BrowserTestUtils.withNewTab({
    gBrowser,
    url: PLUGIN_SMALL_PAGE,
  }, function* (browser) {
    // Work around for delayed PluginBindingAttached
    yield promiseUpdatePluginBindings(browser);

    let pluginInfo = yield promiseForPluginInfo("test", browser);
    ok(pluginInfo.activated, "Plugin should be activated from previous test");

    // Prepare a crash report topic observer that only returns when
    // the crash report has been successfully sent.
    let crashReportChecker = (subject, data) => {
      return (data == "success");
    };
    let crashReportPromise = TestUtils.topicObserved("crash-report-status",
                                                     crashReportChecker);

    yield ContentTask.spawn(browser, null, function*() {
      let plugin = content.document.getElementById("test");
      plugin.QueryInterface(Ci.nsIObjectLoadingContent);

      yield ContentTaskUtils.waitForCondition(() => {
        return plugin.activated;
      }, "Waited too long for plugin to activate.");

      try {
        Components.utils.waiveXrays(plugin).crash();
      } catch (e) {}
    });

    // Wait for the notification bar to be displayed.
    let notification = yield waitForNotificationBar("plugin-crashed", browser);

    // Then click the button to submit the crash report.
    let buttons = notification.querySelectorAll(".notification-button");
    is(buttons.length, 2, "Should have two buttons.");

    // The "Submit Crash Report" button should be the second one.
    let submitButton = buttons[1];
    submitButton.click();

    let [subject, ] = yield crashReportPromise;

    ok(subject instanceof Ci.nsIPropertyBag,
       "The crash report subject should be an nsIPropertyBag.");

    let crashData = convertPropertyBag(subject);
    ok(crashData.serverCrashID, "Should have a serverCrashID set.");

    // Remove the submitted report file after ensuring it exists.
    let file = Cc["@mozilla.org/file/local;1"]
                 .createInstance(Ci.nsILocalFile);
    file.initWithPath(Services.crashmanager._submittedDumpsDir);
    file.append(crashData.serverCrashID + ".txt");
    ok(file.exists(), "Submitted report file should exist");
    file.remove(false);

    is(crashData.extra.PluginContentURL, undefined,
       "URL should be absent from extra data when opt-in not checked");
  });
});