summaryrefslogtreecommitdiff
path: root/toolkit/components/perfmonitoring/tests/browser/head.js
blob: 92258fd1b9d6cc8ae2ade504becf558a4fa1e403 (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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

var { utils: Cu, interfaces: Ci, classes: Cc } = Components;

Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/AddonManager.jsm", this);
Cu.import("resource://gre/modules/AddonWatcher.jsm", this);
Cu.import("resource://gre/modules/PerformanceWatcher.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://testing-common/ContentTaskUtils.jsm", this);

/**
 * Base class for simulating slow addons/webpages.
 */
function CPUBurner(url, jankThreshold) {
  info(`CPUBurner: Opening tab for ${url}\n`);
  this.url = url;
  this.tab = gBrowser.addTab(url);
  this.jankThreshold = jankThreshold;
  let browser = this.tab.linkedBrowser;
  this._browser = browser;
  ContentTask.spawn(this._browser, null, CPUBurner.frameScript);
  this.promiseInitialized = BrowserTestUtils.browserLoaded(browser);
}
CPUBurner.prototype = {
  get windowId() {
    return this._browser.outerWindowID;
  },
  /**
   * Burn CPU until it triggers a listener with the specified jank threshold.
   */
  run: Task.async(function*(burner, max, listener) {
    listener.reset();
    for (let i = 0; i < max; ++i) {
      yield new Promise(resolve => setTimeout(resolve, 50));
      try {
        yield this[burner]();
      } catch (ex) {
        return false;
      }
      if (listener.triggered && listener.result >= this.jankThreshold) {
        return true;
      }
    }
    return false;
  }),
  dispose: function() {
    info(`CPUBurner: Closing tab for ${this.url}\n`);
    gBrowser.removeTab(this.tab);
  }
};
// This function is injected in all frames
CPUBurner.frameScript = function() {
  try {
    "use strict";

    const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
    let sandboxes = new Map();
    let getSandbox = function(addonId) {
      let sandbox = sandboxes.get(addonId);
      if (!sandbox) {
        sandbox = Components.utils.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), { addonId  });
        sandboxes.set(addonId, sandbox);
      }
      return sandbox;
    };

    let burnCPU = function() {
      var start = Date.now();
      var ignored = [];
      while (Date.now() - start < 500) {
        ignored[ignored.length % 2] = ignored.length;
      }
    };
    let burnCPUInSandbox = function(addonId) {
      let sandbox = getSandbox(addonId);
      Cu.evalInSandbox(burnCPU.toSource() + "()", sandbox);
    };

    {
      let topic = "test-performance-watcher:burn-content-cpu";
        addMessageListener(topic, function(msg) {
        try {
          if (msg.data && msg.data.addonId) {
            burnCPUInSandbox(msg.data.addonId);
          } else {
            burnCPU();
          }
          sendAsyncMessage(topic, {});
        } catch (ex) {
          dump(`This is the content attempting to burn CPU: error ${ex}\n`);
          dump(`${ex.stack}\n`);
        }
      });
    }

    // Bind the function to the global context or it might be GC'd during test
    // causing failures (bug 1230027)
    this.burnCPOWInSandbox = function(addonId) {
      try {
        burnCPUInSandbox(addonId);
      } catch (ex) {
        dump(`This is the addon attempting to burn CPOW: error ${ex}\n`);
        dump(`${ex.stack}\n`);
      }
    }

    sendAsyncMessage("test-performance-watcher:cpow-init", {}, {
      burnCPOWInSandbox: this.burnCPOWInSandbox
    });

  } catch (ex) {
    Cu.reportError("This is the addon: error " + ex);
    Cu.reportError(ex.stack);
  }
};

/**
 * Base class for listening to slow group alerts
 */
function AlertListener(accept, {register, unregister}) {
  this.listener = (...args) => {
    if (this._unregistered) {
      throw new Error("Listener was unregistered");
    }
    let result = accept(...args);
    if (!result) {
      return;
    }
    this.result = result;
    this.triggered = true;
    return;
  };
  this.triggered = false;
  this.result = null;
  this._unregistered = false;
  this._unregister = unregister;
  registerCleanupFunction(() => {
    this.unregister();
  });
  register();
}
AlertListener.prototype = {
  unregister: function() {
    this.reset();
    if (this._unregistered) {
      info(`head.js: No need to unregister, we're already unregistered.\n`);
      return;
    }
    info(`head.js: Unregistering listener.\n`);
    this._unregistered = true;
    this._unregister();
    info(`head.js: Unregistration complete.\n`);
  },
  reset: function() {
    this.triggered = false;
    this.result = null;
  },
};

/**
 * Simulate a slow add-on.
 */
function AddonBurner(addonId = "fake add-on id: " + Math.random()) {
  this.jankThreshold = 200000;
  CPUBurner.call(this, `http://example.com/?uri=${addonId}`, this.jankThreshold);
  this._addonId = addonId;
  this._sandbox = Components.utils.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), { addonId: this._addonId });
  this._CPOWBurner = null;

  this._promiseCPOWBurner = new Promise(resolve => {
    this._browser.messageManager.addMessageListener("test-performance-watcher:cpow-init", msg => {
      // Note that we cannot resolve Promises with CPOWs now that they
      // have been outlawed in bug 1233497, so we stash it in the
      // AddonBurner instance instead.
      this._CPOWBurner = msg.objects.burnCPOWInSandbox;
      resolve();
    });
  });
}
AddonBurner.prototype = Object.create(CPUBurner.prototype);
Object.defineProperty(AddonBurner.prototype, "addonId", {
  get: function() {
    return this._addonId;
  }
});

/**
 * Simulate slow code being executed by the add-on in the chrome.
 */
AddonBurner.prototype.burnCPU = function() {
  Cu.evalInSandbox(AddonBurner.burnCPU.toSource() + "()", this._sandbox);
};

/**
 * Simulate slow code being executed by the add-on in a CPOW.
 */
AddonBurner.prototype.promiseBurnCPOW = Task.async(function*() {
  yield this._promiseCPOWBurner;
  ok(this._CPOWBurner, "Got the CPOW burner");
  let burner = this._CPOWBurner;
  info("Parent: Preparing to burn CPOW");
  try {
    yield burner(this._addonId);
    info("Parent: Done burning CPOW");
  } catch (ex) {
    info(`Parent: Error burning CPOW: ${ex}\n`);
    info(ex.stack + "\n");
  }
});

/**
 * Simulate slow code being executed by the add-on in the content.
 */
AddonBurner.prototype.promiseBurnContentCPU = function() {
  return promiseContentResponse(this._browser, "test-performance-watcher:burn-content-cpu", {addonId: this._addonId});
};
AddonBurner.burnCPU = function() {
  var start = Date.now();
  var ignored = [];
  while (Date.now() - start < 500) {
    ignored[ignored.length % 2] = ignored.length;
  }
};


function AddonListener(addonId, accept) {
  let target = {addonId};
  AlertListener.call(this, accept, {
    register: () => {
      info(`AddonListener: registering ${JSON.stringify(target, null, "\t")}`);
      PerformanceWatcher.addPerformanceListener({addonId}, this.listener);
    },
    unregister: () => {
      info(`AddonListener: unregistering ${JSON.stringify(target, null, "\t")}`);
      PerformanceWatcher.removePerformanceListener({addonId}, this.listener);
    }
  });
}
AddonListener.prototype = Object.create(AlertListener.prototype);

function promiseContentResponse(browser, name, message) {
  let mm = browser.messageManager;
  let promise = new Promise(resolve => {
    function removeListener() {
      mm.removeMessageListener(name, listener);
    }

    function listener(msg) {
      removeListener();
      resolve(msg.data);
    }

    mm.addMessageListener(name, listener);
    registerCleanupFunction(removeListener);
  });
  mm.sendAsyncMessage(name, message);
  return promise;
}
function promiseContentResponseOrNull(browser, name, message) {
  if (!browser.messageManager) {
    return null;
  }
  return promiseContentResponse(browser, name, message);
}

/**
 * `true` if we are running an OS in which the OS performance
 * clock has a low precision and might unpredictably
 * never be updated during the execution of the test.
 */
function hasLowPrecision() {
  let [sysName, sysVersion] = [Services.sysinfo.getPropertyAsAString("name"), Services.sysinfo.getPropertyAsDouble("version")];
  info(`Running ${sysName} version ${sysVersion}`);

  if (sysName == "Windows_NT" && sysVersion < 6) {
    info("Running old Windows, need to deactivate tests due to bad precision.");
    return true;
  }
  if (sysName == "Linux" && sysVersion <= 2.6) {
    info("Running old Linux, need to deactivate tests due to bad precision.");
    return true;
  }
  info("This platform has good precision.")
  return false;
}