import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

206
toolkit/.eslintrc.js Normal file
View file

@ -0,0 +1,206 @@
"use strict";
module.exports = {
// When adding items to this file please check for effects on all of toolkit
// and browser
"rules": {
// Braces only needed for multi-line arrow function blocks
// "arrow-body-style": ["error", "as-needed"],
// Require spacing around =>
"arrow-spacing": "error",
// Always require spacing around a single line block
"block-spacing": "error",
// No newline before open brace for a block
// "brace-style": "error",
// No space before always a space after a comma
"comma-spacing": ["error", {"before": false, "after": true}],
// Commas at the end of the line not the start
// "comma-style": "error",
// Don't require spaces around computed properties
"computed-property-spacing": ["error", "never"],
// Functions must always return something or nothing
"consistent-return": "error",
// Require braces around blocks that start a new line
// Note that this rule is likely to be overridden on a per-directory basis
// very frequently.
// "curly": ["error", "multi-line"],
// Always require a trailing EOL
"eol-last": "error",
// Require function* name()
// "generator-star-spacing": ["error", {"before": false, "after": true}],
// Two space indent
// "indent": ["error", 2, { "SwitchCase": 1 }],
// Space after colon not before in property declarations
// "key-spacing": ["error", { "beforeColon": false, "afterColon": true, "mode": "minimum" }],
// Require spaces before and after keywords
"keyword-spacing": "error",
// Unix linebreaks
"linebreak-style": ["error", "unix"],
// Always require parenthesis for new calls
// "new-parens": "error",
// Use [] instead of Array()
// "no-array-constructor": "error",
// No duplicate arguments in function declarations
"no-dupe-args": "error",
// No duplicate keys in object declarations
"no-dupe-keys": "error",
// No duplicate cases in switch statements
"no-duplicate-case": "error",
// No labels
"no-labels": "error",
// If an if block ends with a return no need for an else block
"no-else-return": "error",
// No empty statements
"no-empty": ["error", {"allowEmptyCatch": true}],
// No empty character classes in regex
"no-empty-character-class": "error",
// Disallow empty destructuring
"no-empty-pattern": "error",
// No assiging to exception variable
"no-ex-assign": "error",
// No using !! where casting to boolean is already happening
"no-extra-boolean-cast": "error",
// No double semicolon
"no-extra-semi": "error",
// No overwriting defined functions
"no-func-assign": "error",
// No invalid regular expresions
"no-invalid-regexp": "error",
// No odd whitespace characters
"no-irregular-whitespace": "error",
// No single if block inside an else block
"no-lonely-if": "error",
// No mixing spaces and tabs in indent
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
// No unnecessary spacing
// "no-multi-spaces": ["error", { exceptions: { "AssignmentExpression": true, "VariableDeclarator": true, "ArrayExpression": true, "ObjectExpression": true } }],
// No reassigning native JS objects
"no-native-reassign": "error",
// No (!foo in bar)
"no-negated-in-lhs": "error",
// Nested ternary statements are confusing
"no-nested-ternary": "error",
// Use {} instead of new Object()
"no-new-object": "error",
// No Math() or JSON()
"no-obj-calls": "error",
// No octal literals
"no-octal": "error",
// No redeclaring variables
"no-redeclare": "error",
// No unnecessary comparisons
"no-self-compare": "error",
// No declaring variables from an outer scope
// "no-shadow": "error",
// No declaring variables that hide things like arguments
"no-shadow-restricted-names": "error",
// No spaces between function name and parentheses
// "no-spaced-func": "error",
// No trailing whitespace
"no-trailing-spaces": "error",
// No using undeclared variables
// "no-undef": "error",
// Error on newline where a semicolon is needed
"no-unexpected-multiline": "error",
// No unreachable statements
"no-unreachable": "error",
// No declaring variables that are never used
// "no-unused-vars": ["error", {
// "vars": "local",
// "varsIgnorePattern": "^Cc|Ci|Cu|Cr|EXPORTED_SYMBOLS",
// "args": "none",
// }],
// No using variables before defined
// "no-use-before-define": ["error", "nofunc"],
// No using with
"no-with": "error",
// Always require semicolon at end of statement
// "semi": ["error", "always"],
// Require space before blocks
"space-before-blocks": "error",
// Never use spaces before function parentheses
// "space-before-function-paren": ["error", { "anonymous": "always", "named": "never" }],
// No space padding in parentheses
// "space-in-parens": ["error", "never"],
// Require spaces around operators
// "space-infix-ops": "error",
// ++ and -- should not need spacing
// "space-unary-ops": ["error", { "words": true, "nonwords": false }],
// Requires or disallows a whitespace (space or tab) beginning a comment
"spaced-comment": "error",
// No comparisons to NaN
"use-isnan": "error",
// Only check typeof against valid results
"valid-typeof": "error",
},
"env": {
"es6": true,
"browser": true,
},
"globals": {
"Components": false,
"dump": true,
"openDialog": false,
"sizeToContent": false,
}
};

View file

@ -0,0 +1,11 @@
"use strict";
module.exports = {
"rules": {
"no-unused-vars": ["error", {
"vars": "local",
"varsIgnorePattern": "^Cc|Ci|Cu|Cr|EXPORTED_SYMBOLS",
"args": "none",
}]
}
};

View file

@ -0,0 +1,44 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// First, parse and save the incoming arguments ("?storage=name&context=key")
// Note: window.location.search doesn't work with nsSimpleURIs used for about:* addresses.
var search = window.location.href.match(/^.*\?(.*)$/);
var searchParams = new URLSearchParams(search ? search[1] : '');
var storage = searchParams.get('storage');
var cacheContext = searchParams.get('context');
// The context is in a format as used by the HTTP cache v2 back end
if (cacheContext)
var [context, isAnon, isInBrowser, appId, isPrivate] = cacheContext.match(/(a,)?(b,)?(i\d+,)?(p,)?/);
if (appId)
appId = appId.match(/i(\d+),/)[1];
function $(id) { return document.getElementById(id) || {}; }
// Initialize the context UI controls at the start according what we got in the "context=" argument
addEventListener('DOMContentLoaded', function() {
$('anon').checked = !!isAnon;
$('inbrowser').checked = !!isInBrowser;
$('appid').value = appId || '';
$('priv').checked = !!isPrivate;
}, false);
// When user presses the [Update] button, we build a new context key according the UI control
// values and navigate to a new about:cache?storage=<name>&context=<key> URL.
function navigate()
{
context = '';
if ($('anon').checked)
context += 'a,';
if ($('inbrowser').checked)
context += 'b,';
if ($('appid').value)
context += 'i' + $('appid').value + ',';
if ($('priv').checked)
context += 'p,';
window.location.href = 'about:cache?storage=' + storage + '&context=' + context;
}

View file

@ -0,0 +1,6 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
toolkit.jar:
content/global/aboutCache.js (content/aboutCache.js)

View file

@ -0,0 +1,7 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
JAR_MANIFESTS += ['jar.mn']

View file

@ -0,0 +1,49 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
table.listing {
width: 100%;
}
table th, table td {
padding: 5px;
border: inset 2px black;
margin: 0px;
width: 50%;
vertical-align: top;
}
hr {
clear: both;
margin: 10px;
}
iframe {
width: 100%;
height: 900px;
}
#player, #raw {
width: 800px;
margin-left: auto;
margin-right: auto;
}
#controls {
text-align: center;
}
#canvas {
border: solid 1px black;
}
#active {
width: 100%;
border: solid 1px black;
margin-top: 0;
}
#trace {
width: 100%;
}

View file

@ -0,0 +1,276 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var trace;
var service;
var reports;
function onLoad() {
trace = document.getElementById('trace');
service = new CheckerboardReportService();
updateEnabled();
reports = service.getReports();
for (var i = 0; i < reports.length; i++) {
let text = "Severity " + reports[i].severity + " at " + new Date(reports[i].timestamp).toString();
let link = document.createElement('a');
link.href = 'javascript:showReport(' + i + ')';
link.textContent = text;
let bullet = document.createElement('li');
bullet.appendChild(link);
document.getElementById(reports[i].reason).appendChild(bullet);
}
}
function updateEnabled() {
let enabled = document.getElementById('enabled');
if (service.isRecordingEnabled()) {
enabled.textContent = 'enabled';
enabled.style.color = 'green';
} else {
enabled.textContent = 'disabled';
enabled.style.color = 'red';
}
}
function toggleEnabled() {
service.setRecordingEnabled(!service.isRecordingEnabled());
updateEnabled();
}
function flushReports() {
service.flushActiveReports();
}
function showReport(index) {
trace.value = reports[index].log;
loadData();
}
// -- Code to load and render the trace --
const CANVAS_USE_RATIO = 0.75;
const FRAME_INTERVAL_MS = 50;
const VECTOR_NORMALIZED_MAGNITUDE = 30.0;
var renderData = new Array();
var currentFrame = 0;
var playing = false;
var timerId = 0;
var minX = undefined;
var minY = undefined;
var maxX = undefined;
var maxY = undefined;
function log(x) {
if (console) {
console.log(x);
}
}
function getFlag(flag) {
return document.getElementById(flag).checked;
}
// parses the lines in the textarea, ignoring anything that doesn't have RENDERTRACE.
// for each matching line, tokenizes on whitespace and ignores all tokens prior to
// RENDERTRACE. Additional info can be included at the end of the line, and will be
// displayed but not parsed. Allowed syntaxes:
// <junk> RENDERTRACE <timestamp> rect <color> <x> <y> <width> <height> [extraInfo]
function loadData() {
stopPlay();
renderData = new Array();
currentFrame = 0;
minX = undefined;
minY = undefined;
maxX = undefined;
maxY = undefined;
var charPos = 0;
var lastLineLength = 0;
var lines = trace.value.split(/\r|\n/);
for (var i = 0; i < lines.length; i++) {
charPos += lastLineLength;
lastLineLength = lines[i].length + 1;
// skip lines without RENDERTRACE
if (! /RENDERTRACE/.test(lines[i])) {
continue;
}
var tokens = lines[i].split(/\s+/);
var j = 0;
// skip tokens until RENDERTRACE
while (j < tokens.length && tokens[j++] != "RENDERTRACE"); // empty loop body
if (j >= tokens.length - 2) {
log("Error parsing line: " + lines[i]);
continue;
}
var timestamp = tokens[j++];
var destIndex = renderData.length;
if (destIndex == 0) {
// create the initial frame
renderData.push({
timestamp: timestamp,
rects: {},
});
} else if (renderData[destIndex - 1].timestamp == timestamp) {
// timestamp hasn't changed use, so update the previous object
destIndex--;
} else {
// clone a new copy of the last frame and update timestamp
renderData.push(JSON.parse(JSON.stringify(renderData[destIndex - 1])));
renderData[destIndex].timestamp = timestamp;
}
switch (tokens[j++]) {
case "rect":
if (j > tokens.length - 5) {
log("Error parsing line: " + lines[i]);
continue;
}
var rect = {};
var color = tokens[j++];
renderData[destIndex].rects[color] = rect;
rect.x = parseFloat(tokens[j++]);
rect.y = parseFloat(tokens[j++]);
rect.width = parseFloat(tokens[j++]);
rect.height = parseFloat(tokens[j++]);
rect.dataText = trace.value.substring(charPos, charPos + lines[i].length);
if (!getFlag('excludePageFromZoom') || color != 'brown') {
if (typeof minX == "undefined") {
minX = rect.x;
minY = rect.y;
maxX = rect.x + rect.width;
maxY = rect.y + rect.height;
} else {
minX = Math.min(minX, rect.x);
minY = Math.min(minY, rect.y);
maxX = Math.max(maxX, rect.x + rect.width);
maxY = Math.max(maxY, rect.y + rect.height);
}
}
break;
default:
log("Error parsing line " + lines[i]);
break;
}
}
if (! renderFrame()) {
alert("No data found; nothing to render!");
}
}
// render the current frame (i.e. renderData[currentFrame])
// returns false if currentFrame is out of bounds, true otherwise
function renderFrame() {
var frame = currentFrame;
if (frame < 0 || frame >= renderData.length) {
log("Invalid frame index");
return false;
}
var canvas = document.getElementById('canvas');
if (! canvas.getContext) {
log("No canvas context");
}
var context = canvas.getContext('2d');
// midpoint of the bounding box
var midX = (minX + maxX) / 2.0;
var midY = (minY + maxY) / 2.0;
// midpoint of the canvas
var cmx = canvas.width / 2.0;
var cmy = canvas.height / 2.0;
// scale factor
var scale = CANVAS_USE_RATIO * Math.min(canvas.width / (maxX - minX), canvas.height / (maxY - minY));
function projectX(value) {
return cmx + ((value - midX) * scale);
}
function projectY(value) {
return cmy + ((value - midY) * scale);
}
function drawRect(color, rect) {
context.strokeStyle = color;
context.strokeRect(
projectX(rect.x),
projectY(rect.y),
rect.width * scale,
rect.height * scale);
}
// clear canvas
context.fillStyle = 'white';
context.fillRect(0, 0, canvas.width, canvas.height);
var activeData = '';
// draw rects
for (var i in renderData[frame].rects) {
drawRect(i, renderData[frame].rects[i]);
activeData += "\n" + renderData[frame].rects[i].dataText;
}
// draw timestamp and frame counter
context.fillStyle = 'black';
context.fillText((frame + 1) + "/" + renderData.length + ": " + renderData[frame].timestamp, 5, 15);
document.getElementById('active').textContent = activeData;
return true;
}
// -- Player controls --
function reset(beginning) {
currentFrame = (beginning ? 0 : renderData.length - 1);
renderFrame();
}
function step(backwards) {
if (playing) {
togglePlay();
}
currentFrame += (backwards ? -1 : 1);
if (! renderFrame()) {
currentFrame -= (backwards ? -1 : 1);
}
}
function pause() {
clearInterval(timerId);
playing = false;
}
function togglePlay() {
if (playing) {
pause();
} else {
timerId = setInterval(function() {
currentFrame++;
if (! renderFrame()) {
currentFrame--;
togglePlay();
}
}, FRAME_INTERVAL_MS);
playing = true;
}
}
function stopPlay() {
if (playing) {
togglePlay();
}
currentFrame = 0;
renderFrame();
}

View file

@ -0,0 +1,55 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width"/>
<link rel="stylesheet" href="chrome://global/content/aboutCheckerboard.css" type="text/css"/>
<script type="text/javascript;version=1.8" src="chrome://global/content/aboutCheckerboard.js"></script>
</head>
<body onload="onLoad()">
<p>Checkerboard recording is <span id="enabled" style="color: red">undetermined</span>.
<button onclick="toggleEnabled()">Toggle it!</button>.</p>
<p>If there are active reports in progress, you can stop and flush them by clicking here:
<button onclick="flushReports()">Flush active reports</button></p>
<table class="listing" cellspacing="0">
<tr>
<th>Most severe checkerboarding reports</th>
<th>Most recent checkerboarding reports</th>
</tr>
<tr>
<td><ul id="severe"></ul></td>
<td><ul id="recent"></ul></td>
</tr>
</table>
<hr/>
<div id="player">
<div id="controls">
<button onclick="reset(true)">&#171;</button><!-- rewind button -->
<button onclick="step(true)">&lt;</button><!-- step back button -->
<button onclick="togglePlay()">|| &#9654;</button><!-- pause button -->
<button onclick="stopPlay()">&#9744;</button><!-- stop button -->
<button onclick="step(false)">&gt;</button><!-- step forward button -->
<button onclick="reset(false)">&#187;</button><!-- forward button -->
</div>
<canvas id="canvas" width="800" height="600">Canvas not supported!</canvas>
<pre id="active">(Details for currently visible replay frame)</pre>
</div>
<hr/>
<div id="raw">
Raw log:<br/>
<textarea id="trace" rows="10"></textarea>
<div>
<input type="checkbox" id="excludePageFromZoom" onclick="loadData()"/><label for="excludePageFromZoom">Exclude page coordinates from zoom calculations</label><br/>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,8 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
toolkit.jar:
content/global/aboutCheckerboard.js (content/aboutCheckerboard.js)
content/global/aboutCheckerboard.xhtml (content/aboutCheckerboard.xhtml)
content/global/aboutCheckerboard.css (content/aboutCheckerboard.css)

View file

@ -0,0 +1,10 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
JAR_MANIFESTS += ['jar.mn']
with Files('**'):
BUG_COMPONENT = ('Core', 'Panning and Zooming')

View file

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>about:compartments</title>
<meta name="viewport" content="width=device-width"/>
</head>
<body>about:compartments no longer exists. The lists of compartments and
ghost windows can now be found in the "Other Measurements" section of
about:memory.</body>
</html>

View file

@ -0,0 +1,154 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* The version used for mobile is located at
* mobile/android/themes/core/aboutMemory.css.
* Desktop-specific stuff is at the bottom of this file.
*/
html {
background: -moz-Dialog;
font: message-box;
}
body {
padding: 0 2em;
margin: 0;
min-width: 45em;
margin: auto;
}
div.ancillary {
margin: 0.5em 0;
-moz-user-select: none;
}
div.section {
padding: 2em;
margin: 1em 0em;
border: 1px solid ThreeDShadow;
border-radius: 10px;
background: -moz-Field;
}
div.opsRow {
padding: 0.5em;
margin-right: 0.5em;
margin-top: 0.5em;
border: 1px solid ThreeDShadow;
border-radius: 10px;
background: -moz-Field;
display: inline-block;
}
div.opsRowLabel {
display: block;
margin-bottom: 0.2em;
font-weight: bold;
}
.opsRowLabel label {
margin-left: 1em;
font-weight: normal;
}
div.non-verbose pre.entries {
overflow-x: hidden;
text-overflow: ellipsis;
}
h1 {
padding: 0;
margin: 0;
display: inline; /* allow subsequent text to the right of the heading */
}
h2 {
background: #ddd;
padding-left: .1em;
}
h3 {
display: inline; /* allow subsequent text to the right of the heading */
}
a.upDownArrow {
font-size: 130%;
text-decoration: none;
-moz-user-select: none; /* no need to include this when cutting+pasting */
}
.accuracyWarning {
color: #d22;
}
.badInputWarning {
color: #f00;
}
.treeline {
color: #888;
}
.mrValue {
font-weight: bold;
color: #400;
}
.mrPerc {
}
.mrSep {
}
.mrName {
color: #004;
}
.mrNote {
color: #604;
}
.hasKids {
cursor: pointer;
}
.hasKids:hover {
text-decoration: underline;
}
.noselect {
-moz-user-select: none; /* no need to include this when cutting+pasting */
}
.option {
font-size: 80%;
-moz-user-select: none; /* no need to include this when cutting+pasting */
}
.legend {
font-size: 80%;
-moz-user-select: none; /* no need to include this when cutting+pasting */
}
.debug {
font-size: 80%;
}
.hidden {
display: none;
}
.invalid {
color: #fff;
background-color: #f00;
}
/* Desktop-specific parts go here. */
.hasKids:hover {
text-decoration: underline;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width"/>
<link rel="stylesheet" href="chrome://global/skin/aboutMemory.css" type="text/css"/>
<script type="text/javascript;version=1.8" src="chrome://global/content/aboutMemory.js"/>
</head>
<body onload="onLoad()" onunload="onUnload()"></body>
</html>

View file

@ -0,0 +1,8 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
toolkit.jar:
content/global/aboutMemory.js (content/aboutMemory.js)
content/global/aboutMemory.xhtml (content/aboutMemory.xhtml)
content/global/aboutMemory.css (content/aboutMemory.css)

View file

@ -0,0 +1,12 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
JAR_MANIFESTS += ['jar.mn']
with Files('**'):
BUG_COMPONENT = ('Toolkit', 'about:memory')

View file

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"../../../../testing/mochitest/chrome.eslintrc.js"
]
};

View file

@ -0,0 +1,28 @@
[DEFAULT]
skip-if = os == 'android'
support-files =
crash-dump-diff1.json
crash-dump-diff2.json
crash-dump-good.json
memory-reports-bad.json
memory-reports-diff1.json
memory-reports-diff2.json
memory-reports-good.json
remote.xul
[test_aboutmemory.xul]
subsuite = clipboard
[test_aboutmemory2.xul]
subsuite = clipboard
[test_aboutmemory3.xul]
subsuite = clipboard
[test_aboutmemory4.xul]
subsuite = clipboard
[test_aboutmemory5.xul]
subsuite = clipboard
skip-if = asan # Bug 1116230
[test_aboutmemory6.xul]
[test_memoryReporters.xul]
[test_memoryReporters2.xul]
[test_sqliteMultiReporter.xul]
[test_dumpGCAndCCLogsToFile.xul]

View file

@ -0,0 +1,11 @@
{
"foo": 1,
"blah": 2,
"memory_report": {
"version": 1,
"hasMozMallocUsableSize": true,
"reports": [
{"process": "Main Process (pid NNN)", "path": "heap-allocated", "kind": 2, "units": 0, "amount": 262144000, "description": "Heap allocated."}
]
}
}

View file

@ -0,0 +1,11 @@
{
"foo": 3,
"blah": 4,
"memory_report": {
"version": 1,
"hasMozMallocUsableSize": true,
"reports": [
{"process": "Main Process (pid NNN)", "path": "heap-allocated", "kind": 2, "units": 0, "amount": 262144001, "description": "Heap allocated."}
]
}
}

View file

@ -0,0 +1,14 @@
{
"foo": 1,
"blah": 2,
"memory_report": {
"version": 1,
"hasMozMallocUsableSize": true,
"reports": [
{"process": "Main Process (pid NNN)", "path": "heap-allocated", "kind": 2, "units": 0, "amount": 262144000, "description": "Heap allocated."},
{"process": "Main Process (pid NNN)", "path": "other/b", "kind": 2, "units": 0, "amount": 104857, "description": "Other b."},
{"process": "Main Process (pid NNN)", "path": "other/a", "kind": 2, "units": 0, "amount": 209715, "description": "Other a."},
{"process": "Main Process (pid NNN)", "path": "explicit/a/b", "kind": 1, "units": 0, "amount": 52428800, "description": "A b."}
]
}
}

View file

@ -0,0 +1,3 @@
{
"version": 1
}

View file

@ -0,0 +1,45 @@
{
"version": 1,
"hasMozMallocUsableSize": true,
"reports": [
{"process": "P", "path": "explicit/xpcom/category-manager", "kind": 1, "units": 0, "amount": 56848, "description": "Desc."},
{"process": "P", "path": "explicit/storage/prefixset/goog-phish-shavar", "kind": 1, "units": 0, "amount": 680000, "description": "Desc."},
{"process": "P", "path": "explicit/spell-check", "kind": 1, "units": 0, "amount": 4, "description": "Desc."},
{"process": "P", "path": "explicit/spell-check", "kind": 1, "units": 0, "amount": 5, "description": "Desc."},
{"process": "P", "path": "page-faults-soft", "kind": 2, "units": 2, "amount": 61013, "description": "Desc."},
{"process": "P", "path": "foobar", "kind": 2, "units": 0, "amount": 100, "description": "Desc."},
{"process": "P", "path": "zero1", "kind": 2, "units": 0, "amount": 0, "description": "Desc."},
{"process": "P", "path": "a/b", "kind": 2, "units": 0, "amount": 1000000, "description": "Desc."},
{"process": "P", "path": "a/c/d", "kind": 2, "units": 0, "amount": 2000000, "description": "Desc."},
{"process": "P", "path": "a/c/e", "kind": 2, "units": 0, "amount": 2000000, "description": "Desc."},
{"process": "P", "path": "a/c/f", "kind": 2, "units": 0, "amount": 3000000, "description": "Desc."},
{"process": "P", "path": "a/c/g", "kind": 2, "units": 0, "amount": 3000000, "description": "Desc."},
{"process": "P", "path": "a/h", "kind": 2, "units": 0, "amount": 1000, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p1 (pid 123)", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p2 (blah, pid=123)", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p3/zone(0x1234)/p3", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p4/js-zone(0x1234)/p4", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p5/worker(foo.com, 0x1234)/p5", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "explicit/window-objects/top(bar.com, id=123)/...", "kind": 0, "units": 0, "amount": 33, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p6/z-moz-nullprincipal:{85e250f3-57ae-46c4-a11e-4176dd39d9c5}/p6", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p7/js-main-runtime-compartments/system/jar:file:\\\\\\temp_xyz\\firefox\\omni.ja!/p7", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
{"process": "P3", "path": "p3", "kind": 2, "units": 0, "amount": 55, "description": "Desc."},
{"process": "P5", "path": "p5", "kind": 2, "units": 0, "amount": 0, "description": "Desc."},
{"process": "P7", "path": "p7", "kind": 2, "units": 0, "amount": 5, "description": "Desc."},
{"process": "P8", "path": "p8/a/b/c/d", "kind": 2, "units": 0, "amount": 3, "description": "Desc."},
{"process": "P8", "path": "p8/a/b/c/e", "kind": 2, "units": 0, "amount": 4, "description": "Desc."},
{"process": "P8", "path": "p8/a/b/f", "kind": 2, "units": 0, "amount": 5, "description": "Desc."},
{"process": "P8", "path": "p8/a/g/h", "kind": 2, "units": 0, "amount": 6, "description": "Desc."},
{"process": "P8", "path": "p8/a/g/i", "kind": 2, "units": 0, "amount": 7, "description": "Desc."}
]
}

View file

@ -0,0 +1,44 @@
{
"version": 1,
"hasMozMallocUsableSize": true,
"reports": [
{"process": "P", "path": "explicit/xpcom/category-manager", "kind": 1, "units": 0, "amount": 56849, "description": "Desc."},
{"process": "P", "path": "explicit/storage/prefixset/goog-phish-shavar", "kind": 1, "units": 0, "amount": 670000, "description": "Desc."},
{"process": "P", "path": "explicit/spell-check", "kind": 1, "units": 0, "amount": 3, "description": "Desc."},
{"process": "P", "path": "page-faults-soft", "kind": 2, "units": 2, "amount": 61013, "description": "Desc."},
{"process": "P", "path": "canvas-2d-pixel-bytes", "kind": 2, "units": 0, "amount": 1000, "description": "Desc."},
{"process": "P", "path": "canvas-2d-pixel-bytes", "kind": 2, "units": 0, "amount": 2000, "description": "Desc."},
{"process": "P", "path": "foobaz", "kind": 2, "units": 0, "amount": 0, "description": "Desc."},
{"process": "P", "path": "a/b", "kind": 2, "units": 0, "amount": 2000000, "description": "Desc."},
{"process": "P", "path": "a/c/d", "kind": 2, "units": 0, "amount": 2998000, "description": "Desc."},
{"process": "P", "path": "a/c/e", "kind": 2, "units": 0, "amount": 1001000, "description": "Desc."},
{"process": "P", "path": "a/c/f", "kind": 2, "units": 0, "amount": 3001000, "description": "Desc."},
{"process": "P", "path": "a/c/g", "kind": 2, "units": 0, "amount": 3001000, "description": "Desc."},
{"process": "P", "path": "a/h", "kind": 2, "units": 0, "amount": 2000, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p1 (pid 456)", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p2 (blah, pid=456)", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p3/zone(0x5678)/p3", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p4/js-zone(0x5678)/p4", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p5/worker(foo.com, 0x5678)/p5", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "explicit/window-objects/top(bar.com, id=456)/...", "kind": 0, "units": 0, "amount": 44, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p6/z-moz-nullprincipal:{161effaa-c1f7-4010-a08e-e7c9aea01aed}/p6", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
{"process": "P2 (pid 22)", "path": "p7/js-main-runtime-compartments/system/jar:file:\\\\\\temp_abc\\firefox\\omni.ja!/p7", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
{"process": "P4", "path": "p4", "kind": 2, "units": 0, "amount": 66, "description": "Desc."},
{"process": "P6", "path": "p6", "kind": 2, "units": 0, "amount": 0, "description": "Desc."},
{"process": "P7", "path": "p7/b", "kind": 2, "units": 0, "amount": 3, "description": "Desc."},
{"process": "P7", "path": "p7/c", "kind": 2, "units": 0, "amount": 4, "description": "Desc."},
{"process": "P8", "path": "p8/a/b", "kind": 2, "units": 0, "amount": 1, "description": "Desc."},
{"process": "P8", "path": "p8/a/g", "kind": 2, "units": 0, "amount": 2, "description": "Desc."}
]
}

View file

@ -0,0 +1,29 @@
{
"version": 1,
"hasMozMallocUsableSize": true,
"reports": [
{"process": "Main Process (pid NNN)", "path": "heap-allocated", "kind": 2, "units": 0, "amount": 262144000, "description": "Heap allocated."},
{"process": "Main Process (pid NNN)", "path": "other/b", "kind": 2, "units": 0, "amount": 104857, "description": "Other b."},
{"process": "Main Process (pid NNN)", "path": "other/a", "kind": 2, "units": 0, "amount": 209715, "description": "Other a."},
{"process": "Main Process (pid NNN)", "path": "explicit/a/b", "kind": 1, "units": 0, "amount": 52428800, "description": "A b."},
{"process": "Main Process (pid NNN)", "path": "size/a", "kind": 1, "units": 0, "amount": 1024, "description": "non-sentence"},
{"process": "Main Process (pid NNN)", "path": "rss/a", "kind": 1, "units": 0, "amount": 1024, "description": "non-sentence"},
{"process": "Main Process (pid NNN)", "path": "pss/a", "kind": 1, "units": 0, "amount": 1024, "description": "non-sentence"},
{"process": "Main Process (pid NNN)", "path": "swap/a", "kind": 1, "units": 0, "amount": 1024, "description": "non-sentence"},
{"process": "Main Process (pid NNN)", "path": "compartments/system/a", "kind": 1, "units": 0, "amount": 1024, "description": ""},
{"process": "Main Process (pid NNN)", "path": "ghost-windows/a", "kind": 1, "units": 0, "amount": 1024, "description": ""},
{"process": "Main Process (pid NNN)", "path": "redundant/should-be-ignored", "kind": 1, "units": 0, "amount": 1024, "description": ""},
{"process": "Heap-unclassified process", "path": "heap-allocated", "kind": 2, "units": 0, "amount": 262144000, "description": "Heap allocated."},
{"process": "Heap-unclassified process", "path": "explicit/a/b", "kind": 1, "units": 0, "amount": 52428800, "description": "A b."},
{"process": "Heap-unclassified process", "path": "explicit/heap-unclassified", "kind": 1, "units": 0, "amount": 209715200, "description": "Heap unclassified"},
{"process": "Explicit-only process", "path": "explicit/a/b", "kind": 1, "units": 0, "amount": 100000, "description": "A b."},
{"process": "Other-only process", "path": "a/b", "kind": 1, "units": 0, "amount": 100000, "description": "A b."},
{"process": "Other-only process", "path": "a/c", "kind": 1, "units": 0, "amount": 100000, "description": "A c."},
{"process": "Other-only process", "path": "heap-allocated", "kind": 1, "units": 0, "amount": 500000, "description": "D."}
]
}

View file

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="Remote browser"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<!-- test results are displayed in the html:body -->
<p>Remote browser</p>
<browser type="content" src="about:blank" id="remote" remote="true"/>
</window>

View file

@ -0,0 +1,602 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<window title="about:memory"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<!-- This file uses fake memory reporters to test the presentation of memory
reports in about:memory. test_memoryReporters.xul uses the real
memory reporters to test whether the memory reporters are producing
sensible results. -->
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml"></body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
"use strict";
SimpleTest.expectAssertions(27);
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
// Hide all the real reporters; we'll restore them at the end.
mgr.blockRegistrationAndHideExistingReporters();
// Setup various fake-but-deterministic reporters.
const KB = 1024;
const MB = KB * KB;
const NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
const HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
const COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
const PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
let fakeReporters = [
{ collectReports: function(aCbObj, aClosure, aAnonymize) {
function f(aP, aK, aU, aA) {
aCbObj.callback("", aP, aK, aU, aA, "Desc.", aClosure);
}
f("heap-allocated", OTHER, BYTES, 500 * MB);
f("heap-unallocated", OTHER, BYTES, 100 * MB);
f("explicit/a", HEAP, BYTES, 222 * MB);
f("explicit/b/a", HEAP, BYTES, 85 * MB);
f("explicit/b/b", HEAP, BYTES, 75 * MB);
f("explicit/b/c/a", HEAP, BYTES, 70 * MB);
f("explicit/b/c/b", HEAP, BYTES, 2 * MB); // omitted
f("explicit/g/a", HEAP, BYTES, 6 * MB);
f("explicit/g/b", HEAP, BYTES, 5 * MB);
f("explicit/g/other", HEAP, BYTES, 4 * MB);
// A degenerate tree with the same name as a non-degenerate tree should
// work ok.
f("explicit", OTHER, BYTES, 888 * MB);
f("other1/a/b", OTHER, BYTES, 111 * MB);
f("other1/c/d", OTHER, BYTES, 22 * MB);
f("other1/c/e", OTHER, BYTES, 33 * MB);
f("other4", OTHER, COUNT_CUMULATIVE, 777);
f("other4", OTHER, COUNT_CUMULATIVE, 111);
f("other3/a/b/c/d/e", OTHER, PERCENTAGE, 2000);
f("other3/a/b/c/d/f", OTHER, PERCENTAGE, 10);
f("other3/a/b/c/d/g", OTHER, PERCENTAGE, 5);
f("other3/a/b/c/d/g", OTHER, PERCENTAGE, 5);
// Check that a rounded-up-to-100.00% value is shown as "100.0%" (i.e. one
// decimal point).
f("other6/big", OTHER, COUNT, 99999);
f("other6/small", OTHER, COUNT, 1);
// Check that a 0 / 0 is handled correctly.
f("other7/zero", OTHER, BYTES, 0);
// These compartments ones shouldn't be displayed.
f("compartments/user/foo", OTHER, COUNT, 1);
f("compartments/system/foo", OTHER, COUNT, 1);
}
},
{ collectReports: function(aCbObj, aClosure, aAnonymize) {
function f(aP, aK, aU, aA) {
aCbObj.callback("", aP, aK, aU, aA, "Desc.", aClosure);
}
f("explicit/c/d", NONHEAP, BYTES, 13 * MB);
f("explicit/c/d", NONHEAP, BYTES, 10 * MB); // dup
f("explicit/c/other", NONHEAP, BYTES, 77 * MB);
f("explicit/cc", NONHEAP, BYTES, 13 * MB);
f("explicit/cc", NONHEAP, BYTES, 10 * MB); // dup
f("explicit/d", NONHEAP, BYTES, 499 * KB); // omitted
f("explicit/e", NONHEAP, BYTES, 100 * KB); // omitted
f("explicit/f/g/h/i", HEAP, BYTES, 10 * MB);
f("explicit/f/g/h/j", HEAP, BYTES, 10 * MB);
}
},
{ collectReports: function(aCbObj, aClosure, aAnonymize) {
function f(aP, aK, aU, aA) {
aCbObj.callback("", aP, aK, aU, aA, "Desc.", aClosure);
}
f("other3", OTHER, COUNT, 777);
f("other2", OTHER, BYTES, 222 * MB);
f("perc2", OTHER, PERCENTAGE, 10000);
f("perc1", OTHER, PERCENTAGE, 4567);
f("compartments/user/https:\\\\very-long-url.com\\very-long\\oh-so-long\\really-quite-long.html?a=2&b=3&c=4&d=5&e=abcdefghijklmnopqrstuvwxyz&f=123456789123456789123456789", OTHER, COUNT, 1);
}
},
{ collectReports: function(aCbObj, aClosure, aAnonymize) {
function f(aP) {
aCbObj.callback("", aP, OTHER, COUNT, 1, "Desc.", aClosure);
}
f("compartments/user/bar");
f("compartments/system/bar");
}
}
];
for (let i = 0; i < fakeReporters.length; i++) {
mgr.registerStrongReporterEvenIfBlocked(fakeReporters[i]);
}
// The main process always comes first when we display about:memory. The
// remaining processes are sorted by their |resident| values (starting with
// the largest). Processes without a |resident| memory reporter are saved
// for the end.
let fakeReporters2 = [
{ collectReports: function(aCbObj, aClosure, aAnonymize) {
function f(aP1, aP2, aK, aU, aA) {
aCbObj.callback(aP1, aP2, aK, aU, aA, "Desc.", aClosure);
}
f("2nd", "heap-allocated", OTHER, BYTES,1000* MB);
f("2nd", "heap-unallocated",OTHER, BYTES,100 * MB);
f("2nd", "explicit/a/b/c", HEAP, BYTES,497 * MB);
f("2nd", "explicit/a/b/c", HEAP, BYTES, 1 * MB); // dup: merge
f("2nd", "explicit/a/b/c", HEAP, BYTES, 1 * MB); // dup: merge
f("2nd", "explicit/flip\\the\\backslashes",
HEAP, BYTES,200 * MB);
f("2nd", "explicit/compartment(compartment-url)",
HEAP, BYTES,200 * MB);
f("2nd", "other0", OTHER, BYTES,666 * MB);
f("2nd", "other1", OTHER, BYTES,111 * MB);
// Check that we can handle "heap-allocated" not being present.
f("3rd", "explicit/a/b", HEAP, BYTES,333 * MB);
f("3rd", "explicit/a/c", HEAP, BYTES,444 * MB);
f("3rd", "other1", OTHER, BYTES, 1 * MB);
f("3rd", "resident", OTHER, BYTES,100 * MB);
// Invalid values (negative, too-big) should be identified.
f("4th", "heap-allocated", OTHER, BYTES,100 * MB);
f("4th", "resident", OTHER, BYTES,200 * MB);
f("4th", "explicit/js/compartment(http:\\\\too-big.com\\)/stuff",
HEAP, BYTES,150 * MB);
f("4th", "explicit/ok", HEAP, BYTES, 5 * MB);
f("4th", "explicit/neg1", NONHEAP, BYTES, -2 * MB);
// -111 becomes "-0.00MB" in non-verbose mode, and getting the negative
// sign in there correctly is non-trivial.
f("4th", "other1", OTHER, BYTES,-111);
f("4th", "other2", OTHER, BYTES,-222 * MB);
f("4th", "other3", OTHER, COUNT, -333);
f("4th", "other4", OTHER, COUNT_CUMULATIVE, -444);
f("4th", "other5", OTHER, PERCENTAGE, -555);
f("4th", "other6", OTHER, PERCENTAGE, 66666);
// If a negative value is within a collapsed sub-tree in non-verbose mode,
// we should get the warning at the top and the relevant sub-trees should
// be expanded, even in non-verbose mode.
f("5th", "heap-allocated", OTHER, BYTES,100 * MB);
f("5th", "explicit/big", HEAP, BYTES, 99 * MB);
f("5th", "explicit/a/pos", HEAP, BYTES, 40 * KB);
f("5th", "explicit/a/neg1", NONHEAP, BYTES,-20 * KB);
f("5th", "explicit/a/neg2", NONHEAP, BYTES,-10 * KB);
f("5th", "explicit/b/c/d/e", NONHEAP, BYTES, 20 * KB);
f("5th", "explicit/b/c/d/f", NONHEAP, BYTES,-60 * KB);
f("5th", "explicit/b/c/g/h", NONHEAP, BYTES, 10 * KB);
f("5th", "explicit/b/c/i/j", NONHEAP, BYTES, 5 * KB);
}
}
];
for (let i = 0; i < fakeReporters2.length; i++) {
mgr.registerStrongReporterEvenIfBlocked(fakeReporters2[i]);
}
fakeReporters = fakeReporters.concat(fakeReporters2);
]]>
</script>
<iframe id="amFrame" height="300" src="about:memory"></iframe>
<!-- vary the capitalization to make sure that works -->
<iframe id="amvFrame" height="300" src="About:Memory"></iframe>
<script type="application/javascript">
<![CDATA[
let amExpectedText =
"\
Main Process\n\
Explicit Allocations\n\
\n\
623.58 MB (100.0%) -- explicit\n\
├──232.00 MB (37.20%) -- b\n\
│ ├───85.00 MB (13.63%) ── a\n\
│ ├───75.00 MB (12.03%) ── b\n\
│ └───72.00 MB (11.55%) -- c\n\
│ ├──70.00 MB (11.23%) ── a\n\
│ └───2.00 MB (00.32%) ── b\n\
├──222.00 MB (35.60%) ── a\n\
├──100.00 MB (16.04%) -- c\n\
│ ├───77.00 MB (12.35%) ── other\n\
│ └───23.00 MB (03.69%) ── d [2]\n\
├───23.00 MB (03.69%) ── cc [2]\n\
├───20.00 MB (03.21%) -- f/g/h\n\
│ ├──10.00 MB (01.60%) ── i\n\
│ └──10.00 MB (01.60%) ── j\n\
├───15.00 MB (02.41%) ++ g\n\
├───11.00 MB (01.76%) ── heap-unclassified\n\
└────0.58 MB (00.09%) ++ (2 tiny)\n\
\n\
Other Measurements\n\
\n\
5 (100.0%) -- compartments\n\
├──3 (60.00%) -- user\n\
│ ├──1 (20.00%) ── bar\n\
│ ├──1 (20.00%) ── foo\n\
│ └──1 (20.00%) ── https://very-long-url.com/very-long/oh-so-long/really-quite-long.html?a=2&b=3&c=4&d=5&e=abcdefghijklmnopqrstuvwxyz&f=123456789123456789123456789\n\
└──2 (40.00%) -- system\n\
├──1 (20.00%) ── bar\n\
└──1 (20.00%) ── foo\n\
\n\
166.00 MB (100.0%) -- other1\n\
├──111.00 MB (66.87%) ── a/b\n\
└───55.00 MB (33.13%) -- c\n\
├──33.00 MB (19.88%) ── e\n\
└──22.00 MB (13.25%) ── d\n\
\n\
20.20% (100.0%) -- other3\n\
└──20.20% (100.0%) -- a/b/c/d\n\
├──20.00% (99.01%) ── e\n\
└───0.20% (00.99%) ++ (2 tiny)\n\
\n\
100,000 (100.0%) -- other6\n\
├───99,999 (100.0%) ── big\n\
└────────1 (00.00%) ── small\n\
\n\
0.00 MB (100.0%) -- other7\n\
└──0.00 MB (100.0%) ── zero\n\
\n\
888.00 MB ── explicit\n\
500.00 MB ── heap-allocated\n\
100.00 MB ── heap-unallocated\n\
222.00 MB ── other2\n\
777 ── other3\n\
888 ── other4 [2]\n\
45.67% ── perc1\n\
100.00% ── perc2\n\
\n\
End of Main Process\n\
4th\n\
\n\
WARNING: the following values are negative or unreasonably large.\n\
\n\
explicit/js/compartment(http://too-big.com/)/stuff\n\
explicit/(2 tiny)\n\
explicit/(2 tiny)/neg1\n\
explicit/(2 tiny)/heap-unclassified\n\
other1\n\
other2\n\
other3\n\
other4\n\
other5 \n\
\n\
This indicates a defect in one or more memory reporters. The invalid values are highlighted.\n\
Explicit Allocations\n\
\n\
98.00 MB (100.0%) -- explicit\n\
├──150.00 MB (153.06%) ── js/compartment(http://too-big.com/)/stuff [?!]\n\
├───5.00 MB (05.10%) ── ok\n\
└──-57.00 MB (-58.16%) -- (2 tiny) [?!]\n\
├───-2.00 MB (-2.04%) ── neg1 [?!]\n\
└──-55.00 MB (-56.12%) ── heap-unclassified [?!]\n\
\n\
Other Measurements\n\
\n\
100.00 MB ── heap-allocated\n\
-0.00 MB ── other1 [?!]\n\
-222.00 MB ── other2 [?!]\n\
-333 ── other3 [?!]\n\
-444 ── other4 [?!]\n\
-5.55% ── other5 [?!]\n\
666.66% ── other6\n\
200.00 MB ── resident\n\
\n\
End of 4th\n\
3rd\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
777.00 MB (100.0%) -- explicit\n\
└──777.00 MB (100.0%) -- a\n\
├──444.00 MB (57.14%) ── c\n\
└──333.00 MB (42.86%) ── b\n\
\n\
Other Measurements\n\
\n\
1.00 MB ── other1\n\
100.00 MB ── resident\n\
\n\
End of 3rd\n\
2nd\n\
Explicit Allocations\n\
\n\
1,000.00 MB (100.0%) -- explicit\n\
├────499.00 MB (49.90%) ── a/b/c [3]\n\
├────200.00 MB (20.00%) ── compartment(compartment-url)\n\
├────200.00 MB (20.00%) ── flip/the/backslashes\n\
└────101.00 MB (10.10%) ── heap-unclassified\n\
\n\
Other Measurements\n\
\n\
1,000.00 MB ── heap-allocated\n\
100.00 MB ── heap-unallocated\n\
666.00 MB ── other0\n\
111.00 MB ── other1\n\
\n\
End of 2nd\n\
5th\n\
\n\
WARNING: the following values are negative or unreasonably large.\n\
\n\
explicit/(3 tiny)/a/neg2\n\
explicit/(3 tiny)/a/neg1\n\
explicit/(3 tiny)/b/c\n\
explicit/(3 tiny)/b/c/d\n\
explicit/(3 tiny)/b/c/d/f \n\
\n\
This indicates a defect in one or more memory reporters. The invalid values are highlighted.\n\
Explicit Allocations\n\
\n\
99.95 MB (100.0%) -- explicit\n\
├──99.00 MB (99.05%) ── big\n\
└───0.95 MB (00.95%) -- (3 tiny)\n\
├──0.96 MB (00.96%) ── heap-unclassified\n\
├──0.01 MB (00.01%) -- a\n\
│ ├──0.04 MB (00.04%) ── pos\n\
│ ├──-0.01 MB (-0.01%) ── neg2 [?!]\n\
│ └──-0.02 MB (-0.02%) ── neg1 [?!]\n\
└──-0.02 MB (-0.02%) -- b/c [?!]\n\
├───0.01 MB (00.01%) ── g/h\n\
├───0.00 MB (00.00%) ── i/j\n\
└──-0.04 MB (-0.04%) -- d [?!]\n\
├───0.02 MB (00.02%) ── e\n\
└──-0.06 MB (-0.06%) ── f [?!]\n\
\n\
Other Measurements\n\
\n\
100.00 MB ── heap-allocated\n\
\n\
End of 5th\n\
";
let amvExpectedText =
"\
Main Process\n\
Explicit Allocations\n\
\n\
653,876,224 B (100.0%) -- explicit\n\
├──243,269,632 B (37.20%) -- b\n\
│ ├───89,128,960 B (13.63%) ── a\n\
│ ├───78,643,200 B (12.03%) ── b\n\
│ └───75,497,472 B (11.55%) -- c\n\
│ ├──73,400,320 B (11.23%) ── a\n\
│ └───2,097,152 B (00.32%) ── b\n\
├──232,783,872 B (35.60%) ── a\n\
├──104,857,600 B (16.04%) -- c\n\
│ ├───80,740,352 B (12.35%) ── other\n\
│ └───24,117,248 B (03.69%) ── d [2]\n\
├───24,117,248 B (03.69%) ── cc [2]\n\
├───20,971,520 B (03.21%) -- f/g/h\n\
│ ├──10,485,760 B (01.60%) ── i\n\
│ └──10,485,760 B (01.60%) ── j\n\
├───15,728,640 B (02.41%) -- g\n\
│ ├───6,291,456 B (00.96%) ── a\n\
│ ├───5,242,880 B (00.80%) ── b\n\
│ └───4,194,304 B (00.64%) ── other\n\
├───11,534,336 B (01.76%) ── heap-unclassified\n\
├──────510,976 B (00.08%) ── d\n\
└──────102,400 B (00.02%) ── e\n\
\n\
Other Measurements\n\
\n\
5 (100.0%) -- compartments\n\
├──3 (60.00%) -- user\n\
│ ├──1 (20.00%) ── bar\n\
│ ├──1 (20.00%) ── foo\n\
│ └──1 (20.00%) ── https://very-long-url.com/very-long/oh-so-long/really-quite-long.html?a=2&b=3&c=4&d=5&e=abcdefghijklmnopqrstuvwxyz&f=123456789123456789123456789\n\
└──2 (40.00%) -- system\n\
├──1 (20.00%) ── bar\n\
└──1 (20.00%) ── foo\n\
\n\
174,063,616 B (100.0%) -- other1\n\
├──116,391,936 B (66.87%) ── a/b\n\
└───57,671,680 B (33.13%) -- c\n\
├──34,603,008 B (19.88%) ── e\n\
└──23,068,672 B (13.25%) ── d\n\
\n\
20.20% (100.0%) -- other3\n\
└──20.20% (100.0%) -- a/b/c/d\n\
├──20.00% (99.01%) ── e\n\
├───0.10% (00.50%) ── f\n\
└───0.10% (00.50%) ── g [2]\n\
\n\
100,000 (100.0%) -- other6\n\
├───99,999 (100.0%) ── big\n\
└────────1 (00.00%) ── small\n\
\n\
0 B (100.0%) -- other7\n\
└──0 B (100.0%) ── zero\n\
\n\
931,135,488 B ── explicit\n\
524,288,000 B ── heap-allocated\n\
104,857,600 B ── heap-unallocated\n\
232,783,872 B ── other2\n\
777 ── other3\n\
888 ── other4 [2]\n\
45.67% ── perc1\n\
100.00% ── perc2\n\
\n\
End of Main Process\n\
4th\n\
\n\
WARNING: the following values are negative or unreasonably large.\n\
\n\
explicit/js/compartment(http://too-big.com/)/stuff\n\
explicit/neg1\n\
explicit/heap-unclassified\n\
other1\n\
other2\n\
other3\n\
other4\n\
other5 \n\
\n\
This indicates a defect in one or more memory reporters. The invalid values are highlighted.\n\
Explicit Allocations\n\
\n\
102,760,448 B (100.0%) -- explicit\n\
├──157,286,400 B (153.06%) ── js/compartment(http://too-big.com/)/stuff [?!]\n\
├────5,242,880 B (05.10%) ── ok\n\
├───-2,097,152 B (-2.04%) ── neg1 [?!]\n\
└──-57,671,680 B (-56.12%) ── heap-unclassified [?!]\n\
\n\
Other Measurements\n\
\n\
104,857,600 B ── heap-allocated\n\
-111 B ── other1 [?!]\n\
-232,783,872 B ── other2 [?!]\n\
-333 ── other3 [?!]\n\
-444 ── other4 [?!]\n\
-5.55% ── other5 [?!]\n\
666.66% ── other6\n\
209,715,200 B ── resident\n\
\n\
End of 4th\n\
3rd\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
814,743,552 B (100.0%) -- explicit\n\
└──814,743,552 B (100.0%) -- a\n\
├──465,567,744 B (57.14%) ── c\n\
└──349,175,808 B (42.86%) ── b\n\
\n\
Other Measurements\n\
\n\
1,048,576 B ── other1\n\
104,857,600 B ── resident\n\
\n\
End of 3rd\n\
2nd\n\
Explicit Allocations\n\
\n\
1,048,576,000 B (100.0%) -- explicit\n\
├────523,239,424 B (49.90%) ── a/b/c [3]\n\
├────209,715,200 B (20.00%) ── compartment(compartment-url)\n\
├────209,715,200 B (20.00%) ── flip/the/backslashes\n\
└────105,906,176 B (10.10%) ── heap-unclassified\n\
\n\
Other Measurements\n\
\n\
1,048,576,000 B ── heap-allocated\n\
104,857,600 B ── heap-unallocated\n\
698,351,616 B ── other0\n\
116,391,936 B ── other1\n\
\n\
End of 2nd\n\
5th\n\
\n\
WARNING: the following values are negative or unreasonably large.\n\
\n\
explicit/a/neg2\n\
explicit/a/neg1\n\
explicit/b/c\n\
explicit/b/c/d\n\
explicit/b/c/d/f \n\
\n\
This indicates a defect in one or more memory reporters. The invalid values are highlighted.\n\
Explicit Allocations\n\
\n\
104,801,280 B (100.0%) -- explicit\n\
├──103,809,024 B (99.05%) ── big\n\
├────1,007,616 B (00.96%) ── heap-unclassified\n\
├───────10,240 B (00.01%) -- a\n\
│ ├──40,960 B (00.04%) ── pos\n\
│ ├──-10,240 B (-0.01%) ── neg2 [?!]\n\
│ └──-20,480 B (-0.02%) ── neg1 [?!]\n\
└──────-25,600 B (-0.02%) -- b/c [?!]\n\
├───10,240 B (00.01%) ── g/h\n\
├────5,120 B (00.00%) ── i/j\n\
└──-40,960 B (-0.04%) -- d [?!]\n\
├───20,480 B (00.02%) ── e\n\
└──-61,440 B (-0.06%) ── f [?!]\n\
\n\
Other Measurements\n\
\n\
104,857,600 B ── heap-allocated\n\
\n\
End of 5th\n\
";
function finish()
{
mgr.unblockRegistrationAndRestoreOriginalReporters();
SimpleTest.finish();
}
// Cut+paste the entire page and check that the cut text matches what we
// expect. This tests the output in general and also that the cutting and
// pasting works as expected.
function test(aFrameId, aVerbose, aExpected, aNext) {
SimpleTest.executeSoon(function() {
ok(document.title === "about:memory", "document.title is correct");
let mostRecentActual;
let frame = document.getElementById(aFrameId);
frame.focus();
// Set the verbose checkbox value and click the go button.
let doc = frame.contentWindow.document;
let measureButton = doc.getElementById("measureButton");
let verbose = doc.getElementById("verbose");
verbose.checked = aVerbose;
measureButton.click();
SimpleTest.waitForClipboard(
function(aActual) {
mostRecentActual = aActual;
let rslt = aActual.trim() === aExpected.trim();
if (!rslt) {
// Try copying again.
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
}
return rslt;
},
function() {
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
},
aNext,
function() {
ok(false, "pasted text doesn't match for " + aFrameId);
dump("******EXPECTED******\n");
dump("<<<" + aExpected + ">>>\n");
dump("*******ACTUAL*******\n");
dump("<<<" + mostRecentActual + ">>>\n");
dump("********************\n");
finish();
}
);
});
}
SimpleTest.waitForFocus(function() {
test(
"amFrame",
/* verbose = */ false,
amExpectedText,
function() {
test(
"amvFrame",
/* verbose = */ true,
amvExpectedText,
function() {
finish()
}
)
}
);
});
SimpleTest.waitForExplicitFinish();
]]>
</script>
</window>

View file

@ -0,0 +1,423 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<window title="about:memory"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<!-- This file tests the collapsing and expanding of sub-trees in
about:memory. -->
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml"></body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
// Hide all the real reporters; we'll restore them at the end.
mgr.blockRegistrationAndHideExistingReporters();
// Setup various fake-but-deterministic reporters.
const KB = 1024;
const MB = KB * KB;
const HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
let hiPath = "explicit/h/i";
let hi2Path = "explicit/h/i2";
let jkPath = "explicit/j/k";
let jk2Path = "explicit/j/k2";
let fakeReporters = [
{ collectReports: function(aCbObj, aClosure, aAnonymize) {
function f(aP, aK, aA) {
aCbObj.callback("", aP, aK, BYTES, aA, "Desc.", aClosure);
}
f("heap-allocated", OTHER, 250 * MB);
f("explicit/a/b", HEAP, 50 * MB);
f("explicit/a/c/d", HEAP, 25 * MB);
f("explicit/a/c/e", HEAP, 15 * MB);
f("explicit/a/f", HEAP, 30 * MB);
f("explicit/g", HEAP, 100 * MB);
f(hiPath, HEAP, 10 * MB);
f(hi2Path, HEAP, 9 * MB);
f(jkPath, HEAP, 0.5 * MB);
f(jk2Path, HEAP, 0.3 * MB);
f("explicit/a/l/m", HEAP, 0.1 * MB);
f("explicit/a/l/n", HEAP, 0.1 * MB);
}
}
];
for (let i = 0; i < fakeReporters.length; i++) {
mgr.registerStrongReporterEvenIfBlocked(fakeReporters[i]);
}
]]>
</script>
<iframe id="amFrame" height="500" src="about:memory"></iframe>
<script type="application/javascript">
<![CDATA[
function finish()
{
mgr.unblockRegistrationAndRestoreOriginalReporters();
SimpleTest.finish();
}
// Click on the identified element, then cut+paste the entire page and
// check that the cut text matches what we expect.
function test(aId, aSwap, aExpected, aNext) {
let win = document.getElementById("amFrame").contentWindow;
if (aId) {
let node = win.document.getElementById(aId);
// Yuk: clicking a button is easy; but for tree entries we need to
// click on a child of the span identified via |id|.
if (node.nodeName === "button") {
if (aSwap) {
// We swap hipath/hi2Path and jkPath/jk2Path just before updating, to
// test what happens when significant nodes become insignificant and
// vice versa.
hiPath = "explicit/j/k";
hi2Path = "explicit/j/k2";
jkPath = "explicit/h/i";
jk2Path = "explicit/h/i2";
}
node.click();
} else {
node.childNodes[0].click();
}
}
SimpleTest.executeSoon(function() {
let mostRecentActual;
document.getElementById("amFrame").focus();
SimpleTest.waitForClipboard(
function(aActual) {
mostRecentActual = aActual;
let rslt = aActual.trim() === aExpected.trim();
if (!rslt) {
// Try copying again.
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
}
return rslt;
},
function() {
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
},
aNext,
function() {
ok(false, "pasted text doesn't match");
dump("******EXPECTED******\n");
dump(aExpected);
dump("*******ACTUAL*******\n");
dump(mostRecentActual);
dump("********************\n");
finish();
}
);
});
}
// Returns a function that chains together one test() call per id.
function chain(aIds) {
let x = aIds.shift();
if (x) {
return function() { test(x.id, x.swap, x.expected, chain(aIds)); }
} else {
return function() { finish(); };
}
}
let startExpected =
"\
Main Process\n\
Explicit Allocations\n\
\n\
250.00 MB (100.0%) -- explicit\n\
├──120.20 MB (48.08%) -- a\n\
│ ├───50.00 MB (20.00%) ── b\n\
│ ├───40.00 MB (16.00%) -- c\n\
│ │ ├──25.00 MB (10.00%) ── d\n\
│ │ └──15.00 MB (06.00%) ── e\n\
│ ├───30.00 MB (12.00%) ── f\n\
│ └────0.20 MB (00.08%) ++ l\n\
├──100.00 MB (40.00%) ── g\n\
├───19.00 MB (07.60%) -- h\n\
│ ├──10.00 MB (04.00%) ── i\n\
│ └───9.00 MB (03.60%) ── i2\n\
├───10.00 MB (04.00%) ── heap-unclassified\n\
└────0.80 MB (00.32%) ++ j\n\
\n\
Other Measurements\n\
\n\
250.00 MB ── heap-allocated\n\
\n\
End of Main Process\n\
";
let acCollapsedExpected =
"\
Main Process\n\
Explicit Allocations\n\
\n\
250.00 MB (100.0%) -- explicit\n\
├──120.20 MB (48.08%) -- a\n\
│ ├───50.00 MB (20.00%) ── b\n\
│ ├───40.00 MB (16.00%) ++ c\n\
│ ├───30.00 MB (12.00%) ── f\n\
│ └────0.20 MB (00.08%) ++ l\n\
├──100.00 MB (40.00%) ── g\n\
├───19.00 MB (07.60%) -- h\n\
│ ├──10.00 MB (04.00%) ── i\n\
│ └───9.00 MB (03.60%) ── i2\n\
├───10.00 MB (04.00%) ── heap-unclassified\n\
└────0.80 MB (00.32%) ++ j\n\
\n\
Other Measurements\n\
\n\
250.00 MB ── heap-allocated\n\
\n\
End of Main Process\n\
";
let alExpandedExpected =
"\
Main Process\n\
Explicit Allocations\n\
\n\
250.00 MB (100.0%) -- explicit\n\
├──120.20 MB (48.08%) -- a\n\
│ ├───50.00 MB (20.00%) ── b\n\
│ ├───40.00 MB (16.00%) ++ c\n\
│ ├───30.00 MB (12.00%) ── f\n\
│ └────0.20 MB (00.08%) -- l\n\
│ ├──0.10 MB (00.04%) ── m\n\
│ └──0.10 MB (00.04%) ── n\n\
├──100.00 MB (40.00%) ── g\n\
├───19.00 MB (07.60%) -- h\n\
│ ├──10.00 MB (04.00%) ── i\n\
│ └───9.00 MB (03.60%) ── i2\n\
├───10.00 MB (04.00%) ── heap-unclassified\n\
└────0.80 MB (00.32%) ++ j\n\
\n\
Other Measurements\n\
\n\
250.00 MB ── heap-allocated\n\
\n\
End of Main Process\n\
";
let aCollapsedExpected =
"\
Main Process\n\
Explicit Allocations\n\
\n\
250.00 MB (100.0%) -- explicit\n\
├──120.20 MB (48.08%) ++ a\n\
├──100.00 MB (40.00%) ── g\n\
├───19.00 MB (07.60%) -- h\n\
│ ├──10.00 MB (04.00%) ── i\n\
│ └───9.00 MB (03.60%) ── i2\n\
├───10.00 MB (04.00%) ── heap-unclassified\n\
└────0.80 MB (00.32%) ++ j\n\
\n\
Other Measurements\n\
\n\
250.00 MB ── heap-allocated\n\
\n\
End of Main Process\n\
";
let hCollapsedExpected =
"\
Main Process\n\
Explicit Allocations\n\
\n\
250.00 MB (100.0%) -- explicit\n\
├──120.20 MB (48.08%) ++ a\n\
├──100.00 MB (40.00%) ── g\n\
├───19.00 MB (07.60%) ++ h\n\
├───10.00 MB (04.00%) ── heap-unclassified\n\
└────0.80 MB (00.32%) ++ j\n\
\n\
Other Measurements\n\
\n\
250.00 MB ── heap-allocated\n\
\n\
End of Main Process\n\
";
let jExpandedExpected =
"\
Main Process\n\
Explicit Allocations\n\
\n\
250.00 MB (100.0%) -- explicit\n\
├──120.20 MB (48.08%) ++ a\n\
├──100.00 MB (40.00%) ── g\n\
├───19.00 MB (07.60%) ++ h\n\
├───10.00 MB (04.00%) ── heap-unclassified\n\
└────0.80 MB (00.32%) -- j\n\
├──0.50 MB (00.20%) ── k\n\
└──0.30 MB (00.12%) ── k2\n\
\n\
Other Measurements\n\
\n\
250.00 MB ── heap-allocated\n\
\n\
End of Main Process\n\
";
// The important thing here is that two values have been swapped.
// explicit/h/i should remain collapsed, and explicit/j/k should remain
// expanded. See bug 724863.
let updatedExpected =
"\
Main Process\n\
Explicit Allocations\n\
\n\
250.00 MB (100.0%) -- explicit\n\
├──120.20 MB (48.08%) ++ a\n\
├──100.00 MB (40.00%) ── g\n\
├───19.00 MB (07.60%) -- j\n\
│ ├──10.00 MB (04.00%) ── k\n\
│ └───9.00 MB (03.60%) ── k2\n\
├───10.00 MB (04.00%) ── heap-unclassified\n\
└────0.80 MB (00.32%) ++ h\n\
\n\
Other Measurements\n\
\n\
250.00 MB ── heap-allocated\n\
\n\
End of Main Process\n\
";
let aExpandedExpected =
"\
Main Process\n\
Explicit Allocations\n\
\n\
250.00 MB (100.0%) -- explicit\n\
├──120.20 MB (48.08%) -- a\n\
│ ├───50.00 MB (20.00%) ── b\n\
│ ├───40.00 MB (16.00%) ++ c\n\
│ ├───30.00 MB (12.00%) ── f\n\
│ └────0.20 MB (00.08%) -- l\n\
│ ├──0.10 MB (00.04%) ── m\n\
│ └──0.10 MB (00.04%) ── n\n\
├──100.00 MB (40.00%) ── g\n\
├───19.00 MB (07.60%) -- j\n\
│ ├──10.00 MB (04.00%) ── k\n\
│ └───9.00 MB (03.60%) ── k2\n\
├───10.00 MB (04.00%) ── heap-unclassified\n\
└────0.80 MB (00.32%) ++ h\n\
\n\
Other Measurements\n\
\n\
250.00 MB ── heap-allocated\n\
\n\
End of Main Process\n\
";
let acExpandedExpected =
"\
Main Process\n\
Explicit Allocations\n\
\n\
250.00 MB (100.0%) -- explicit\n\
├──120.20 MB (48.08%) -- a\n\
│ ├───50.00 MB (20.00%) ── b\n\
│ ├───40.00 MB (16.00%) -- c\n\
│ │ ├──25.00 MB (10.00%) ── d\n\
│ │ └──15.00 MB (06.00%) ── e\n\
│ ├───30.00 MB (12.00%) ── f\n\
│ └────0.20 MB (00.08%) -- l\n\
│ ├──0.10 MB (00.04%) ── m\n\
│ └──0.10 MB (00.04%) ── n\n\
├──100.00 MB (40.00%) ── g\n\
├───19.00 MB (07.60%) -- j\n\
│ ├──10.00 MB (04.00%) ── k\n\
│ └───9.00 MB (03.60%) ── k2\n\
├───10.00 MB (04.00%) ── heap-unclassified\n\
└────0.80 MB (00.32%) ++ h\n\
\n\
Other Measurements\n\
\n\
250.00 MB ── heap-allocated\n\
\n\
End of Main Process\n\
";
let alCollapsedExpected =
"\
Main Process\n\
Explicit Allocations\n\
\n\
250.00 MB (100.0%) -- explicit\n\
├──120.20 MB (48.08%) -- a\n\
│ ├───50.00 MB (20.00%) ── b\n\
│ ├───40.00 MB (16.00%) -- c\n\
│ │ ├──25.00 MB (10.00%) ── d\n\
│ │ └──15.00 MB (06.00%) ── e\n\
│ ├───30.00 MB (12.00%) ── f\n\
│ └────0.20 MB (00.08%) ++ l\n\
├──100.00 MB (40.00%) ── g\n\
├───19.00 MB (07.60%) -- j\n\
│ ├──10.00 MB (04.00%) ── k\n\
│ └───9.00 MB (03.60%) ── k2\n\
├───10.00 MB (04.00%) ── heap-unclassified\n\
└────0.80 MB (00.32%) ++ h\n\
\n\
Other Measurements\n\
\n\
250.00 MB ── heap-allocated\n\
\n\
End of Main Process\n\
";
// Test the following cases:
// - explicit/a/c is significant, we collapse it, it's unchanged upon
// update, we re-expand it
// - explicit/a/l is insignificant, we expand it, it's unchanged upon
// update, we re-collapse it
// - explicit/a is significant, we collapse it (which hides its
// sub-trees), it's unchanged upon update, we re-expand it
// - explicit/h is significant, we collapse it, it becomes insignificant
// upon update (and should remain collapsed)
// - explicit/j is insignificant, we expand it, it becomes significant
// upon update (and should remain expanded)
//
let idsToClick = [
{ id: "measureButton", swap: 0, expected: startExpected },
{ id: "Main Process:explicit/a/c", swap: 0, expected: acCollapsedExpected },
{ id: "Main Process:explicit/a/l", swap: 0, expected: alExpandedExpected },
{ id: "Main Process:explicit/a", swap: 0, expected: aCollapsedExpected },
{ id: "Main Process:explicit/h", swap: 0, expected: hCollapsedExpected },
{ id: "Main Process:explicit/j", swap: 0, expected: jExpandedExpected },
{ id: "measureButton", swap: 1, expected: updatedExpected },
{ id: "Main Process:explicit/a", swap: 0, expected: aExpandedExpected },
{ id: "Main Process:explicit/a/c", swap: 0, expected: acExpandedExpected },
{ id: "Main Process:explicit/a/l", swap: 0, expected: alCollapsedExpected }
];
SimpleTest.waitForFocus(chain(idsToClick));
SimpleTest.waitForExplicitFinish();
]]>
</script>
</window>

View file

@ -0,0 +1,515 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<window title="about:memory"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<!-- This file tests the saving and loading of memory reports to/from file in
about:memory. -->
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml"></body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
// Hide all the real reporters; we'll restore them at the end.
mgr.blockRegistrationAndHideExistingReporters();
// Setup a minimal number of fake reporters.
const KB = 1024;
const MB = KB * KB;
const HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
let fakeReporters = [
{ collectReports: function(aCbObj, aClosure, aAnonymize) {
function f(aP, aK, aA, aD) {
aCbObj.callback("", aP, aK, BYTES, aA, aD, aClosure);
}
f("heap-allocated", OTHER, 250 * MB, "Heap allocated.");
f("explicit/a/b", HEAP, 50 * MB, "A b.");
f("other/a", OTHER, 0.2 * MB, "Other a.");
f("other/b", OTHER, 0.1 * MB, "Other b.");
}
}
];
for (let i = 0; i < fakeReporters.length; i++) {
mgr.registerStrongReporterEvenIfBlocked(fakeReporters[i]);
}
]]>
</script>
<iframe id="amFrame" height="400" src="about:memory"></iframe>
<script type="application/javascript">
<![CDATA[
function finish()
{
mgr.unblockRegistrationAndRestoreOriginalReporters();
SimpleTest.finish();
}
// Load the given file into the frame, then copy+paste the entire frame and
// check that the cut text matches what we expect.
function test(aFilename, aFilename2, aExpected, aDumpFirst, aVerbose, aNext) {
let frame = document.getElementById("amFrame");
frame.focus();
let doc = frame.contentWindow.document;
let verbosity = doc.getElementById("verbose");
verbosity.checked = aVerbose;
function getFilePath(aFilename) {
let file = Cc["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("CurWorkD", Components.interfaces.nsIFile);
file.append("chrome");
file.append("toolkit");
file.append("components");
file.append("aboutmemory");
file.append("tests");
file.append(aFilename);
return file.path;
}
let filePath = getFilePath(aFilename);
let e = document.createEvent('Event');
e.initEvent('change', true, true);
function check() {
// Initialize the clipboard contents.
SpecialPowers.clipboardCopyString("initial clipboard value");
let numFailures = 0, maxFailures = 30;
// Because the file load is async, we don't know when it will finish and
// the output will show up. So we poll.
function copyPasteAndCheck() {
// Copy and paste frame contents, and filter out non-deterministic
// differences.
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
let actual = SpecialPowers.getClipboardData("text/unicode");
actual = actual.replace(/\(pid \d+\)/g, "(pid NNN)");
if (actual.trim() === aExpected.trim()) {
SimpleTest.ok(true, "Clipboard has the expected contents");
aNext();
} else {
numFailures++;
if (numFailures === maxFailures) {
ok(false, "pasted text doesn't match");
dump("******EXPECTED******\n");
dump(aExpected);
dump("*******ACTUAL*******\n");
dump(actual);
dump("********************\n");
finish();
} else {
setTimeout(copyPasteAndCheck, 100);
}
}
}
copyPasteAndCheck();
}
if (!aFilename2) {
function loadAndCheck() {
let fileInput1 =
frame.contentWindow.document.getElementById("fileInput1");
fileInput1.value = filePath; // this works because it's a chrome test
fileInput1.dispatchEvent(e);
check();
}
if (aDumpFirst) {
let dumper = Cc["@mozilla.org/memory-info-dumper;1"].
getService(Ci.nsIMemoryInfoDumper);
dumper.dumpMemoryReportsToNamedFile(filePath, loadAndCheck, null,
/* anonymize = */ false);
} else {
loadAndCheck();
}
} else {
let fileInput2 =
frame.contentWindow.document.getElementById("fileInput2");
fileInput2.value = filePath; // this works because it's a chrome test
// Hack alert: fileInput2's onchange handler calls fileInput2.click().
// But we don't want that to happen, because we want to bypass the file
// picker for the test. So we set |e.skipClick|, which causes
// fileInput2.click() to be skipped, and dispatch the second change event
// directly ourselves.
e.skipClick = true;
fileInput2.dispatchEvent(e);
let filePath2 = getFilePath(aFilename2);
fileInput2.value = filePath2; // this works because it's a chrome test
let e2 = document.createEvent('Event');
e2.initEvent('change', true, true);
fileInput2.dispatchEvent(e);
check();
}
}
// Returns a function that chains together multiple test() calls.
function chain(aPieces) {
let x = aPieces.shift();
if (x) {
return function() { test(x.filename, x.filename2, x.expected, x.dumpFirst, x.verbose, chain(aPieces)); }
} else {
return function() { finish(); };
}
}
let expectedGood =
"\
Explicit-only process\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
100,000 B (100.0%) -- explicit\n\
└──100,000 B (100.0%) ── a/b\n\
\n\
Other Measurements\n\
\n\
End of Explicit-only process\n\
Heap-unclassified process\n\
Explicit Allocations\n\
\n\
262,144,000 B (100.0%) -- explicit\n\
├──209,715,200 B (80.00%) ── heap-unclassified\n\
└───52,428,800 B (20.00%) ── a/b\n\
\n\
Other Measurements\n\
\n\
262,144,000 B ── heap-allocated\n\
\n\
End of Heap-unclassified process\n\
Main Process (pid NNN)\n\
Explicit Allocations\n\
\n\
262,144,000 B (100.0%) -- explicit\n\
├──209,715,200 B (80.00%) ── heap-unclassified\n\
└───52,428,800 B (20.00%) ── a/b\n\
\n\
Other Measurements\n\
\n\
1,024 B (100.0%) -- compartments\n\
└──1,024 B (100.0%) ── system/a\n\
\n\
1,024 B (100.0%) -- ghost-windows\n\
└──1,024 B (100.0%) ── a\n\
\n\
314,572 B (100.0%) -- other\n\
├──209,715 B (66.67%) ── a\n\
└──104,857 B (33.33%) ── b\n\
\n\
1,024 B (100.0%) -- pss\n\
└──1,024 B (100.0%) ── a\n\
\n\
1,024 B (100.0%) -- rss\n\
└──1,024 B (100.0%) ── a\n\
\n\
1,024 B (100.0%) -- size\n\
└──1,024 B (100.0%) ── a\n\
\n\
1,024 B (100.0%) -- swap\n\
└──1,024 B (100.0%) ── a\n\
\n\
262,144,000 B ── heap-allocated\n\
\n\
End of Main Process (pid NNN)\n\
Other-only process\n\
Other Measurements\n\
\n\
200,000 B (100.0%) -- a\n\
├──100,000 B (50.00%) ── b\n\
└──100,000 B (50.00%) ── c\n\
\n\
500,000 B ── heap-allocated\n\
\n\
End of Other-only process\n\
";
let expectedGood2 =
"\
Main Process (pid NNN)\n\
Explicit Allocations\n\
\n\
262,144,000 B (100.0%) -- explicit\n\
├──209,715,200 B (80.00%) ── heap-unclassified\n\
└───52,428,800 B (20.00%) ── a/b\n\
\n\
Other Measurements\n\
\n\
314,572 B (100.0%) -- other\n\
├──209,715 B (66.67%) ── a\n\
└──104,857 B (33.33%) ── b\n\
\n\
262,144,000 B ── heap-allocated\n\
\n\
End of Main Process (pid NNN)\n\
";
// This is the output for a malformed data file.
let expectedBad =
"\
Error: Invalid memory report(s): missing 'hasMozMallocUsableSize' property\
";
// This is the output for a non-verbose diff.
let expectedDiffNonVerbose =
"\
P\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
-0.01 MB (100.0%) -- explicit\n\
├──-0.01 MB (99.95%) ── storage/prefixset/goog-phish-shavar\n\
└──-0.00 MB (00.05%) ++ (2 tiny)\n\
\n\
Other Measurements\n\
\n\
0.96 MB (100.0%) -- a\n\
├──0.95 MB (99.80%) ── b\n\
├──0.00 MB (00.10%) -- c\n\
│ ├──-0.95 MB (-99.70%) ── e\n\
│ ├──0.95 MB (99.60%) ── d\n\
│ └──0.00 MB (00.20%) ++ (2 tiny)\n\
└──0.00 MB (00.10%) ── h\n\
\n\
0.00 MB ── canvas-2d-pixel-bytes [2] [+]\n\
-0.00 MB ── foobar [-]\n\
\n\
End of P\n\
P2 (pid NNN)\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
0.00 MB (100.0%) -- explicit\n\
└──0.00 MB (100.0%) ── window-objects/top(bar.com, id=NNN)/...\n\
\n\
Other Measurements\n\
\n\
0.00 MB (100.0%) -- p3\n\
└──0.00 MB (100.0%) ── zone(0xNNN)/p3\n\
\n\
0.00 MB (100.0%) -- p4\n\
└──0.00 MB (100.0%) ── js-zone(0xNNN)/p4\n\
\n\
0.00 MB (100.0%) -- p5\n\
└──0.00 MB (100.0%) ── worker(foo.com, 0xNNN)/p5\n\
\n\
0.00 MB (100.0%) -- p6\n\
└──0.00 MB (100.0%) ── z-moz-nullprincipal:{NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN}/p6\n\
\n\
0.00 MB (100.0%) -- p7\n\
└──0.00 MB (100.0%) ── js-main-runtime-compartments/system/jar:file:///.../omni.ja!/p7\n\
\n\
0.00 MB ── p1 (pid NNN)\n\
0.00 MB ── p2 (blah, pid=NNN)\n\
\n\
End of P2 (pid NNN)\n\
P3\n\
Other Measurements\n\
\n\
-0.00 MB ── p3 [-]\n\
\n\
End of P3\n\
P4\n\
Other Measurements\n\
\n\
0.00 MB ── p4 [+]\n\
\n\
End of P4\n\
P7\n\
Other Measurements\n\
\n\
0.00 MB (100.0%) -- p7\n\
├──0.00 MB (57.14%) ── c [+]\n\
└──0.00 MB (42.86%) ── b [+]\n\
\n\
-0.00 MB ── p7 [-]\n\
\n\
End of P7\n\
P8\n\
Other Measurements\n\
\n\
-0.00 MB (100.0%) -- p8\n\
└──-0.00 MB (100.0%) -- a\n\
├──-0.00 MB (50.00%) -- b\n\
│ ├──-0.00 MB (31.82%) -- c\n\
│ │ ├──-0.00 MB (18.18%) ── e [-]\n\
│ │ └──-0.00 MB (13.64%) ── d [-]\n\
│ ├──-0.00 MB (22.73%) ── f [-]\n\
│ └───0.00 MB (-4.55%) ── (fake child) [!]\n\
└──-0.00 MB (50.00%) -- g\n\
├──-0.00 MB (31.82%) ── i [-]\n\
├──-0.00 MB (27.27%) ── h [-]\n\
└───0.00 MB (-9.09%) ── (fake child) [!]\n\
\n\
End of P8\n\
";
// This is the output for a verbose diff.
let expectedDiffVerbose =
"\
P\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
-10,005 B (100.0%) -- explicit\n\
├──-10,000 B (99.95%) ── storage/prefixset/goog-phish-shavar\n\
├───────-6 B (00.06%) ── spell-check [2]\n\
└────────1 B (-0.01%) ── xpcom/category-manager\n\
\n\
Other Measurements\n\
\n\
1,002,000 B (100.0%) -- a\n\
├──1,000,000 B (99.80%) ── b\n\
├──────1,000 B (00.10%) -- c\n\
│ ├──-999,000 B (-99.70%) ── e\n\
│ ├──998,000 B (99.60%) ── d\n\
│ ├──1,000 B (00.10%) ── f\n\
│ └──1,000 B (00.10%) ── g\n\
└──────1,000 B (00.10%) ── h\n\
\n\
3,000 B ── canvas-2d-pixel-bytes [2] [+]\n\
-100 B ── foobar [-]\n\
\n\
End of P\n\
P2 (pid NNN)\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
11 B (100.0%) -- explicit\n\
└──11 B (100.0%) ── window-objects/top(bar.com, id=NNN)/...\n\
\n\
Other Measurements\n\
\n\
11 B (100.0%) -- p3\n\
└──11 B (100.0%) ── zone(0xNNN)/p3\n\
\n\
11 B (100.0%) -- p4\n\
└──11 B (100.0%) ── js-zone(0xNNN)/p4\n\
\n\
11 B (100.0%) -- p5\n\
└──11 B (100.0%) ── worker(foo.com, 0xNNN)/p5\n\
\n\
11 B (100.0%) -- p6\n\
└──11 B (100.0%) ── z-moz-nullprincipal:{NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN}/p6\n\
\n\
11 B (100.0%) -- p7\n\
└──11 B (100.0%) ── js-main-runtime-compartments/system/jar:file:///.../omni.ja!/p7\n\
\n\
11 B ── p1 (pid NNN)\n\
11 B ── p2 (blah, pid=NNN)\n\
\n\
End of P2 (pid NNN)\n\
P3\n\
Other Measurements\n\
\n\
-55 B ── p3 [-]\n\
\n\
End of P3\n\
P4\n\
Other Measurements\n\
\n\
66 B ── p4 [+]\n\
\n\
End of P4\n\
P7\n\
Other Measurements\n\
\n\
7 B (100.0%) -- p7\n\
├──4 B (57.14%) ── c [+]\n\
└──3 B (42.86%) ── b [+]\n\
\n\
-5 B ── p7 [-]\n\
\n\
End of P7\n\
P8\n\
Other Measurements\n\
\n\
-22 B (100.0%) -- p8\n\
└──-22 B (100.0%) -- a\n\
├──-11 B (50.00%) -- b\n\
│ ├───-7 B (31.82%) -- c\n\
│ │ ├──-4 B (18.18%) ── e [-]\n\
│ │ └──-3 B (13.64%) ── d [-]\n\
│ ├───-5 B (22.73%) ── f [-]\n\
│ └────1 B (-4.55%) ── (fake child) [!]\n\
└──-11 B (50.00%) -- g\n\
├───-7 B (31.82%) ── i [-]\n\
├───-6 B (27.27%) ── h [-]\n\
└────2 B (-9.09%) ── (fake child) [!]\n\
\n\
End of P8\n\
";
// This is the output for the crash reports diff.
let expectedDiff2 =
"\
Main Process (pid NNN)\n\
Other Measurements\n\
\n\
1 B ── heap-allocated\n\
\n\
End of Main Process (pid NNN)\n\
";
let frames = [
// This loads a pre-existing memory reports file that is valid.
{ filename: "memory-reports-good.json", expected: expectedGood, dumpFirst: false, verbose: true },
// This loads a pre-existing crash dump file that is valid.
{ filename: "crash-dump-good.json", expected: expectedGood2, dumpFirst: false, verbose: true },
// This dumps to a file and then reads it back in. (The result is the same
// as the previous test.)
{ filename: "memory-reports-dumped.json.gz", expected: expectedGood2, dumpFirst: true, verbose: true },
// This loads a pre-existing file that is invalid.
{ filename: "memory-reports-bad.json", expected: expectedBad, dumpFirst: false, verbose: true },
// This diffs two pre-existing memory reports files.
{ filename: "memory-reports-diff1.json", filename2: "memory-reports-diff2.json", expected: expectedDiffNonVerbose, dumpFirst: false, verbose: false },
// Ditto.
{ filename: "memory-reports-diff1.json", filename2: "memory-reports-diff2.json", expected: expectedDiffVerbose, dumpFirst: false, verbose: true },
// This diffs two pre-existing crash report files.
{ filename: "crash-dump-diff1.json", filename2: "crash-dump-diff2.json", expected: expectedDiff2, dumpFirst: false, verbose: true }
];
SimpleTest.waitForFocus(chain(frames));
SimpleTest.waitForExplicitFinish();
]]>
</script>
</window>

View file

@ -0,0 +1,179 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<window title="about:memory"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<!-- This file tests the loading of memory reports from file when specified
in about:memory's URL (via the "file=" suffix). -->
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml"></body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
"use strict";
function makePathname(aFilename) {
let file = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("CurWorkD", Components.interfaces.nsIFile);
file.append("chrome");
file.append("toolkit");
file.append("components");
file.append("aboutmemory");
file.append("tests");
file.append(aFilename);
return file.path;
}
// Load the given file into the frame, then copy+paste the entire frame and
// check that the cut text matches what we expect.
function test(aFilename, aExpected, aNext) {
let frame = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe")
frame.height = 300;
frame.src = "about:memory?file=" + makePathname(aFilename);
document.documentElement.appendChild(frame);
frame.focus();
// Initialize the clipboard contents.
SpecialPowers.clipboardCopyString("initial clipboard value");
let numFailures = 0, maxFailures = 30;
// Because the file load is async, we don't know when it will finish and
// the output will show up. So we poll.
function copyPasteAndCheck() {
// Copy and paste frame contents, and filter out non-deterministic
// differences.
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
let actual = SpecialPowers.getClipboardData("text/unicode");
actual = actual.replace(/\(pid \d+\)/, "(pid NNN)");
if (actual.trim() === aExpected.trim()) {
SimpleTest.ok(true, "Clipboard has the expected contents");
aNext();
} else {
numFailures++;
if (numFailures === maxFailures) {
ok(false, "pasted text doesn't match");
dump("******EXPECTED******\n");
dump(aExpected);
dump("*******ACTUAL*******\n");
dump(actual);
dump("********************\n");
SimpleTest.finish();
} else {
setTimeout(copyPasteAndCheck, 100);
}
}
}
copyPasteAndCheck();
}
// Returns a function that chains together multiple test() calls.
function chain(aFrameIds) {
let x = aFrameIds.shift();
if (x) {
return function() { test(x.filename, x.expected, chain(aFrameIds)); }
} else {
return function() { SimpleTest.finish(); };
}
}
// This is pretty simple output, but that's ok; this file is about testing
// the loading of data from file. If we got this far, we're doing fine.
let expectedGood =
"\
Explicit-only process\n\
\n\
WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
Explicit Allocations\n\
\n\
0.10 MB (100.0%) -- explicit\n\
└──0.10 MB (100.0%) ── a/b\n\
\n\
Other Measurements\n\
\n\
End of Explicit-only process\n\
Heap-unclassified process\n\
Explicit Allocations\n\
\n\
250.00 MB (100.0%) -- explicit\n\
├──200.00 MB (80.00%) ── heap-unclassified\n\
└───50.00 MB (20.00%) ── a/b\n\
\n\
Other Measurements\n\
\n\
250.00 MB ── heap-allocated\n\
\n\
End of Heap-unclassified process\n\
Main Process (pid NNN)\n\
Explicit Allocations\n\
\n\
250.00 MB (100.0%) -- explicit\n\
├──200.00 MB (80.00%) ── heap-unclassified\n\
└───50.00 MB (20.00%) ── a/b\n\
\n\
Other Measurements\n\
\n\
0.00 MB (100.0%) -- compartments\n\
└──0.00 MB (100.0%) ── system/a\n\
\n\
0.00 MB (100.0%) -- ghost-windows\n\
└──0.00 MB (100.0%) ── a\n\
\n\
0.30 MB (100.0%) -- other\n\
├──0.20 MB (66.67%) ── a\n\
└──0.10 MB (33.33%) ── b\n\
\n\
0.00 MB (100.0%) -- pss\n\
└──0.00 MB (100.0%) ── a\n\
\n\
0.00 MB (100.0%) -- rss\n\
└──0.00 MB (100.0%) ── a\n\
\n\
0.00 MB (100.0%) -- size\n\
└──0.00 MB (100.0%) ── a\n\
\n\
0.00 MB (100.0%) -- swap\n\
└──0.00 MB (100.0%) ── a\n\
\n\
250.00 MB ── heap-allocated\n\
\n\
End of Main Process (pid NNN)\n\
Other-only process\n\
Other Measurements\n\
\n\
0.19 MB (100.0%) -- a\n\
├──0.10 MB (50.00%) ── b\n\
└──0.10 MB (50.00%) ── c\n\
\n\
0.48 MB ── heap-allocated\n\
\n\
End of Other-only process\n\
";
// This is the output for a malformed data file.
let expectedBad =
"\
Error: Invalid memory report(s): missing 'hasMozMallocUsableSize' property";
let frames = [
// This loads a pre-existing file that is valid.
{ filename: "memory-reports-good.json", expected: expectedGood },
// This loads a pre-existing file that is valid.
{ filename: "memory-reports-bad.json", expected: expectedBad }
];
SimpleTest.waitForFocus(chain(frames));
SimpleTest.waitForExplicitFinish();
]]>
</script>
</window>

View file

@ -0,0 +1,167 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<window title="about:memory"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<!-- This file tests the saving and loading of memory reports to/from file in
about:memory in the presence of child processes. It is also notable
for being an about:memory test that uses the real reporters, rather
than fake deterministic ones, and so tends to show up problems in the
real reporters (like bogus negative values). -->
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml"></body>
<iframe id="amFrame" height="400" src="about:memory"></iframe>
<script type="application/javascript">
<![CDATA[
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
let numRemotes = 3;
let numReady = 0;
// Create some remote processes, and set up message-passing so that
// we know when each child is fully initialized.
let remotes = [];
let prefs = [
["dom.ipc.processCount", 3], // Allow up to 3 child processes
["memory.report_concurrency", 2], // Cover more child handling cases
["memory.system_memory_reporter", true] // Test SystemMemoryReporter
];
SpecialPowers.pushPrefEnv({"set": prefs}, function() {
for (let i = 0; i < numRemotes; i++) {
let w = remotes[i] = window.open("remote.xul", "", "chrome");
w.addEventListener("load", function loadHandler() {
w.removeEventListener("load", loadHandler);
let remoteBrowser = w.document.getElementById("remote");
let mm = remoteBrowser.messageManager;
mm.addMessageListener("test:ready", function readyHandler() {
mm.removeMessageListener("test:ready", readyHandler);
numReady++;
if (numReady == numRemotes) {
// All the remote processes are ready.
SimpleTest.waitForFocus(onFocus);
}
});
mm.loadFrameScript("data:," + encodeURI("sendAsyncMessage('test:ready');"), true);
});
}
});
// Load the given file into the frame, then copy+paste the entire frame and
// check that the cut text matches what we expect.
function onFocus() {
let frame = document.getElementById("amFrame");
frame.focus();
function getFilePath(aFilename) {
let file = Cc["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("CurWorkD", Components.interfaces.nsIFile);
file.append("chrome");
file.append("toolkit");
file.append("components");
file.append("aboutmemory");
file.append("tests");
file.append(aFilename);
return file.path;
}
let filePath = getFilePath("memory-reports-dumped.json.gz");
let e = document.createEvent('Event');
e.initEvent('change', true, true);
let dumper = Cc["@mozilla.org/memory-info-dumper;1"].
getService(Ci.nsIMemoryInfoDumper);
dumper.dumpMemoryReportsToNamedFile(filePath, loadAndCheck, null,
/* anonymize = */ false);
function loadAndCheck() {
// Load the file.
let fileInput1 =
frame.contentWindow.document.getElementById("fileInput1");
fileInput1.value = filePath; // this works because it's a chrome test
fileInput1.dispatchEvent(e);
// Initialize the clipboard contents.
SpecialPowers.clipboardCopyString("initial clipboard value");
let numFailures = 0, maxFailures = 30;
copyPasteAndCheck();
// Because the file load is async, we don't know when it will finish and
// the output will show up. So we poll.
function copyPasteAndCheck() {
// Copy and paste frame contents, and filter out non-deterministic
// differences.
synthesizeKey("A", {accelKey: true});
synthesizeKey("C", {accelKey: true});
let actual = SpecialPowers.getClipboardData("text/unicode");
// If we have more than 1000 chars, we've probably successfully
// copy+pasted.
if (actual.length > 1000) {
let good = true;
if (actual.match("End of System")) {
let m1 = actual.match("anonymous") &&
actual.match("shared-libraries");
ok(m1, "system-wide reporter")
good = good && !!m1;
}
// Note: Match "vsize" but not "vsize-max-contiguous".
let vsizes = actual.match(/vsize[^-]/g);
let endOfBrowsers = actual.match(/End of Browser/g);
if (endOfBrowsers == null) {
endOfBrowsers = actual.match(/End of Web Content/g);
}
let m2 = (vsizes.length == 4 && endOfBrowsers.length == 3);
ok(m2, "three child processes present in loaded data");
good = good && !!m2;
if (!good) {
dump("*******ACTUAL*******\n");
dump(actual);
dump("********************\n");
}
// Close the remote processes.
for (let i = 0; i < numRemotes; i++) {
remotes[i].close();
}
SimpleTest.finish();
} else {
numFailures++;
if (numFailures === maxFailures) {
ok(false, "not enough chars in pasted output");
SimpleTest.finish();
} else {
setTimeout(copyPasteAndCheck, 100);
}
}
}
}
}
SimpleTest.waitForExplicitFinish();
]]>
</script>
</window>

View file

@ -0,0 +1,88 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<window title="about:memory"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<!-- This file tests the saving of GC and CC logs in both concise and
verbose formats. -->
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml"></body>
<iframe id="amFrame" height="400" src="about:memory"></iframe>
<script type="application/javascript">
<![CDATA[
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
function onFocus() {
let frame = document.getElementById("amFrame");
frame.focus();
// Checks that a file exists on the local file system and removes it if it
// is present.
function checkForFileAndRemove(aFilename) {
let localFile = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsILocalFile);
localFile.initWithPath(aFilename);
let exists = localFile.exists();
if (exists) {
localFile.remove(/* recursive = */ false);
}
return exists;
}
// Given a save log button, triggers the action and checks if both CC & GC
// logs were written to disk.
function saveLogs(aLogButton, aCCLogType)
{
// trigger the log saving
aLogButton.click();
// mainDiv
// |-> section
// | -> div gc log path
// | -> div cc log path
let mainDiv = frame.contentWindow.document.getElementById("mainDiv");
let logNodes = mainDiv.childNodes[0];
// we expect 2 logs listed
let numOfLogs = logNodes.childNodes.length;
ok(numOfLogs == 2, "two log entries generated")
// grab the path portion of the text
let gcLogPath = logNodes.childNodes[0].textContent
.replace("Saved GC log to ", "");
let ccLogPath = logNodes.childNodes[1].textContent
.replace("Saved " + aCCLogType + " CC log to ", "");
// check that the files actually exist
ok(checkForFileAndRemove(gcLogPath), "GC log file exists");
ok(checkForFileAndRemove(ccLogPath), "CC log file exists");
}
// get the log buttons to test
let saveConcise = frame.contentWindow.document
.getElementById("saveLogsConcise");
let saveVerbose = frame.contentWindow.document
.getElementById("saveLogsVerbose");
saveLogs(saveConcise, "concise");
saveLogs(saveVerbose, "verbose");
SimpleTest.finish();
}
SimpleTest.waitForFocus(onFocus);
SimpleTest.waitForExplicitFinish();
]]>
</script>
</window>

View file

@ -0,0 +1,98 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="GC/CC logging with child processes"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
</body>
<!-- test code goes here -->
<script type="application/javascript"><![CDATA[
const Cc = Components.classes;
const Ci = Components.interfaces;
SimpleTest.waitForExplicitFinish();
let numRemotes = 3;
let numReady = 0;
// Create some remote processes, and set up message-passing so that
// we know when each child is fully initialized.
let remotes = [];
SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", numRemotes]]},
function() {
for (let i = 0; i < numRemotes; i++) {
let w = remotes[i] = window.open("remote.xul", "", "chrome");
w.addEventListener("load", function loadHandler() {
w.removeEventListener("load", loadHandler);
let remoteBrowser = w.document.getElementById("remote");
let mm = remoteBrowser.messageManager;
mm.addMessageListener("test:ready", function readyHandler() {
mm.removeMessageListener("test:ready", readyHandler);
numReady++;
if (numReady == numRemotes) {
// All the remote processes are ready. Run test.
runTest();
}
});
mm.loadFrameScript("data:," + encodeURI("sendAsyncMessage('test:ready');"), true);
});
}
});
let dumper = Cc["@mozilla.org/memory-info-dumper;1"].
getService(Ci.nsIMemoryInfoDumper);
function runTest()
{
let numParents = 0;
let numChildren = 0;
dumper.dumpGCAndCCLogsToFile(
/* identifier: */ "test." + Date.now(),
/* allTraces: */ false,
/* childProcesses: */ true,
{
onDump: function(gcLog, ccLog, isParent) {
if (isParent) {
numParents++;
} else {
numChildren++;
}
checkAndRemoveLog(gcLog);
checkAndRemoveLog(ccLog);
},
onFinish: function() {
is(numParents, 1,
"GC/CC logs for the parent process");
is(numChildren, numRemotes,
"GC/CC logs for each child process");
cleanUpAndFinish();
}
});
}
function cleanUpAndFinish() {
// Close the remote processes.
for (let i = 0; i < numRemotes; i++) {
remotes[i].close();
}
SimpleTest.finish();
}
function checkAndRemoveLog(logFile) {
let name = logFile.path;
ok(logFile.exists(), "log file "+name+" exists");
ok(logFile.isFile(), "log file "+name+" is a regular file");
ok(logFile.fileSize > 0, "log file "+name+" is not empty");
logFile.remove(/* recursive: */ false);
}
]]></script>
</window>

View file

@ -0,0 +1,424 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<window title="Memory reporters"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- This file tests (in a rough fashion) whether the memory reporters are
producing sensible results. test_aboutmemory.xul tests the
presentation of memory reports in about:memory. -->
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
<!-- In bug 773533, <marquee> elements crashed the JS memory reporter -->
<marquee>Marquee</marquee>
</body>
<!-- some URIs that should be anonymized in anonymous mode -->
<iframe id="amFrame" height="200" src="http://example.org:80"></iframe>
<iframe id="amFrame" height="200" src="https://example.com:443"></iframe>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
const HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
const COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
const PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
// Use backslashes instead of forward slashes due to memory reporting's hacky
// handling of URLs.
const XUL_NS =
"http:\\\\www.mozilla.org\\keymaster\\gatekeeper\\there.is.only.xul";
SimpleTest.waitForExplicitFinish();
let vsizeAmounts = [];
let residentAmounts = [];
let heapAllocatedAmounts = [];
let storageSqliteAmounts = [];
let jsGcHeapUsedGcThingsTotal = 0;
let jsGcHeapUsedGcThings = {};
let present = {}
// Generate a long, random string. We'll check that this string is
// reported in at least one of the memory reporters.
let bigString = "";
while (bigString.length < 10000) {
bigString += Math.random();
}
let bigStringPrefix = bigString.substring(0, 100);
// Generate many copies of two distinctive short strings, "!)(*&" and
// "@)(*&". We'll check that these strings are reported in at least
// one of the memory reporters.
let shortStrings = [];
for (let i = 0; i < 10000; i++) {
let str = (Math.random() > 0.5 ? "!" : "@") + ")(*&";
shortStrings.push(str);
}
let mySandbox = Components.utils.Sandbox(document.nodePrincipal,
{ sandboxName: "this-is-a-sandbox-name" });
function handleReportNormal(aProcess, aPath, aKind, aUnits, aAmount,
aDescription)
{
// Record the values of some notable reporters.
if (aPath === "vsize") {
vsizeAmounts.push(aAmount);
} else if (aPath === "resident") {
residentAmounts.push(aAmount);
} else if (aPath.search(/^js-main-runtime-gc-heap-committed\/used\/gc-things\//) >= 0) {
jsGcHeapUsedGcThingsTotal += aAmount;
jsGcHeapUsedGcThings[aPath] = (jsGcHeapUsedGcThings[aPath] | 0) + 1;
} else if (aPath === "heap-allocated") {
heapAllocatedAmounts.push(aAmount);
} else if (aPath === "storage-sqlite") {
storageSqliteAmounts.push(aAmount);
// Check the presence of some other notable reporters.
} else if (aPath.search(/^explicit\/js-non-window\/.*compartment\(/) >= 0) {
present.jsNonWindowCompartments = true;
} else if (aPath.search(/^explicit\/window-objects\/top\(.*\/js-compartment\(/) >= 0) {
present.windowObjectsJsCompartments = true;
} else if (aPath.search(/^explicit\/storage\/sqlite\/places.sqlite/) >= 0) {
present.places = true;
} else if (aPath.search(/^explicit\/images/) >= 0) {
present.images = true;
} else if (aPath.search(/^explicit\/xpti-working-set$/) >= 0) {
present.xptiWorkingSet = true;
} else if (aPath.search(/^explicit\/atom-tables\/main$/) >= 0) {
present.atomTablesMain = true;
} else if (/\[System Principal\].*this-is-a-sandbox-name/.test(aPath)) {
// A system compartment with a location (such as a sandbox) should
// show that location.
present.sandboxLocation = true;
} else if (aPath.includes(bigStringPrefix)) {
present.bigString = true;
} else if (aPath.includes("!)(*&")) {
present.smallString1 = true;
} else if (aPath.includes("@)(*&")) {
present.smallString2 = true;
}
// Shouldn't get any anonymized paths.
if (aPath.includes('<anonymized')) {
present.anonymizedWhenUnnecessary = aPath;
}
}
function handleReportAnonymized(aProcess, aPath, aKind, aUnits, aAmount,
aDescription)
{
// Path might include an xmlns using http, which is safe to ignore.
let reducedPath = aPath.replace(XUL_NS, "");
// Shouldn't get http: or https: in any paths.
if (reducedPath.includes('http:')) {
present.httpWhenAnonymized = aPath;
}
// file: URLs should have their path anonymized.
if (reducedPath.search('file:..[^<]') !== -1) {
present.unanonymizedFilePathWhenAnonymized = aPath;
}
}
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
let amounts = [
"vsize",
"vsizeMaxContiguous",
"resident",
"residentFast",
"residentPeak",
"residentUnique",
"heapAllocated",
"heapOverheadFraction",
"JSMainRuntimeGCHeap",
"JSMainRuntimeTemporaryPeak",
"JSMainRuntimeCompartmentsSystem",
"JSMainRuntimeCompartmentsUser",
"imagesContentUsedUncompressed",
"storageSQLite",
"lowMemoryEventsVirtual",
"lowMemoryEventsPhysical",
"ghostWindows",
"pageFaultsHard",
];
for (let i = 0; i < amounts.length; i++) {
try {
// If mgr[amounts[i]] throws an exception, just move on -- some amounts
// aren't available on all platforms. But if the attribute simply
// isn't present, that indicates the distinguished amounts have changed
// and this file hasn't been updated appropriately.
let dummy = mgr[amounts[i]];
ok(dummy !== undefined,
"accessed an unknown distinguished amount: " + amounts[i]);
} catch (ex) {
}
}
// Run sizeOfTab() to make sure it doesn't crash. We can't check the result
// values because they're non-deterministic.
let jsObjectsSize = {};
let jsStringsSize = {};
let jsOtherSize = {};
let domSize = {};
let styleSize = {};
let otherSize = {};
let totalSize = {};
let jsMilliseconds = {};
let nonJSMilliseconds = {};
mgr.sizeOfTab(window, jsObjectsSize, jsStringsSize, jsOtherSize,
domSize, styleSize, otherSize, totalSize,
jsMilliseconds, nonJSMilliseconds);
let asyncSteps = [
getReportsNormal,
getReportsAnonymized,
checkResults,
test_register_strong,
test_register_strong, // Make sure re-registering works
test_register_weak,
SimpleTest.finish
];
function runNext() {
setTimeout(asyncSteps.shift(), 0);
}
function getReportsNormal()
{
mgr.getReports(handleReportNormal, null,
runNext, null,
/* anonymize = */ false);
}
function getReportsAnonymized()
{
mgr.getReports(handleReportAnonymized, null,
runNext, null,
/* anonymize = */ true);
}
function checkSizeReasonable(aName, aAmount)
{
// Check the size is reasonable -- i.e. not ridiculously large or small.
ok(100 * 1000 <= aAmount && aAmount <= 10 * 1000 * 1000 * 1000,
aName + "'s size is reasonable");
}
function checkSpecialReport(aName, aAmounts, aCanBeUnreasonable)
{
ok(aAmounts.length == 1, aName + " has " + aAmounts.length + " report");
let n = aAmounts[0];
if (!aCanBeUnreasonable) {
checkSizeReasonable(aName, n);
}
}
function checkResults()
{
try {
// Nb: mgr.heapAllocated will throw NS_ERROR_NOT_AVAILABLE if this is a
// --disable-jemalloc build. Allow for skipping this test on that
// exception, but *only* that exception.
let dummy = mgr.heapAllocated;
checkSpecialReport("heap-allocated", heapAllocatedAmounts);
} catch (ex) {
is(ex.result, Cr.NS_ERROR_NOT_AVAILABLE, "mgr.heapAllocated exception");
}
// vsize may be unreasonable if ASAN is enabled
checkSpecialReport("vsize", vsizeAmounts, /*canBeUnreasonable*/true);
checkSpecialReport("resident", residentAmounts);
for (var reporter in jsGcHeapUsedGcThings) {
ok(jsGcHeapUsedGcThings[reporter] == 1);
}
checkSizeReasonable("js-main-runtime-gc-heap-committed/used/gc-things",
jsGcHeapUsedGcThingsTotal);
ok(present.jsNonWindowCompartments, "js-non-window compartments are present");
ok(present.windowObjectsJsCompartments, "window-objects/.../js compartments are present");
ok(present.places, "places is present");
ok(present.images, "images is present");
ok(present.xptiWorkingSet, "xpti-working-set is present");
ok(present.atomTablesMain, "atom-tables/main is present");
ok(present.sandboxLocation, "sandbox locations are present");
ok(present.bigString, "large string is present");
ok(present.smallString1, "small string 1 is present");
ok(present.smallString2, "small string 2 is present");
ok(!present.anonymizedWhenUnnecessary,
"anonymized paths are not present when unnecessary. Failed case: " +
present.anonymizedWhenUnnecessary);
ok(!present.httpWhenAnonymized,
"http URLs are anonymized when necessary. Failed case: " +
present.httpWhenAnonymized);
ok(!present.unanonymizedFilePathWhenAnonymized,
"file URLs are anonymized when necessary. Failed case: " +
present.unanonymizedFilePathWhenAnonymized);
runNext();
}
// Reporter registration tests
// collectReports() calls to the test reporter.
let called = 0;
// The test memory reporter, testing the various report units.
// Also acts as a report collector, verifying the reported values match the
// expected ones after passing through XPConnect / nsMemoryReporterManager
// and back.
function MemoryReporterAndCallback() {
this.seen = 0;
}
MemoryReporterAndCallback.prototype = {
// The test reports.
// Each test key corresponds to the path of the report. |amount| is a
// function called when generating the report. |expected| is a function
// to be tested when receiving a report during collection. If |expected| is
// omitted the |amount| will be checked instead.
tests: {
"test-memory-reporter-bytes1": {
units: BYTES,
amount: () => 0
},
"test-memory-reporter-bytes2": {
units: BYTES,
amount: () => (1<<30) * 8 // awkward way to say 8G in JS
},
"test-memory-reporter-counter": {
units: COUNT,
amount: () => 2
},
"test-memory-reporter-ccounter": {
units: COUNT_CUMULATIVE,
amount: () => ++called,
expected: () => called
},
"test-memory-reporter-percentage": {
units: PERCENTAGE,
amount: () => 9999
}
},
// nsIMemoryReporter
collectReports: function(callback, data, anonymize) {
for (let path of Object.keys(this.tests)) {
try {
let test = this.tests[path];
callback.callback(
"", // Process. Should be "" initially.
path,
OTHER,
test.units,
test.amount(),
"Test " + path + ".",
data);
}
catch (ex) {
ok(false, ex);
}
}
},
// nsIMemoryReporterCallback
callback: function(process, path, kind, units, amount, data) {
if (path in this.tests) {
this.seen++;
let test = this.tests[path];
ok(units === test.units, "Test reporter units match");
ok(amount === (test.expected || test.amount)(),
"Test reporter values match: " + amount);
}
},
// Checks that the callback has seen the expected number of reports, and
// resets the callback counter.
// @param expected Optional. Expected number of reports the callback
// should have processed.
finish: function(expected) {
if (expected === undefined) {
expected = Object.keys(this.tests).length;
}
is(expected, this.seen,
"Test reporter called the correct number of times: " + expected);
this.seen = 0;
}
};
// General memory reporter + registerStrongReporter tests.
function test_register_strong() {
let reporterAndCallback = new MemoryReporterAndCallback();
// Registration works.
mgr.registerStrongReporter(reporterAndCallback);
// Check the generated reports.
mgr.getReports(reporterAndCallback, null,
() => {
reporterAndCallback.finish();
window.setTimeout(test_unregister_strong, 0, reporterAndCallback);
}, null,
/* anonymize = */ false);
}
function test_unregister_strong(aReporterAndCallback)
{
mgr.unregisterStrongReporter(aReporterAndCallback);
// The reporter was unregistered, hence there shouldn't be any reports from
// the test reporter.
mgr.getReports(aReporterAndCallback, null,
() => {
aReporterAndCallback.finish(0);
runNext();
}, null,
/* anonymize = */ false);
}
// Check that you cannot register JS components as weak reporters.
function test_register_weak() {
let reporterAndCallback = new MemoryReporterAndCallback();
try {
// Should fail! nsMemoryReporterManager will only hold a raw pointer to
// "weak" reporters. When registering a weak reporter, XPConnect will
// create a WrappedJS for JS components. This WrappedJS would be
// successfully registered with the manager, only to be destroyed
// immediately after, which would eventually lead to a crash when
// collecting the reports. Therefore nsMemoryReporterManager should
// reject WrappedJS reporters, which is what is tested here.
// See bug 950391 comment #0.
mgr.registerWeakReporter(reporterAndCallback);
ok(false, "Shouldn't be allowed to register a JS component (WrappedJS)");
}
catch (ex) {
ok(ex.message.indexOf("NS_ERROR_") >= 0,
"WrappedJS reporter got rejected: " + ex);
}
runNext();
}
// Kick-off the async tests.
runNext();
]]>
</script>
</window>

View file

@ -0,0 +1,108 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="Memory reporters with child processes"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- This file tests (in a rough fashion) whether the memory reporters are
producing sensible results in the presence of child processes. -->
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
</body>
<!-- test code goes here -->
<script type="application/javascript"><![CDATA[
const Cc = Components.classes;
const Ci = Components.interfaces;
SimpleTest.waitForExplicitFinish();
let numRemotes = 3;
let numReady = 0;
// Create some remote processes, and set up message-passing so that
// we know when each child is fully initialized.
let remotes = [];
SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 3]]}, function() {
for (let i = 0; i < numRemotes; i++) {
let w = remotes[i] = window.open("remote.xul", "", "chrome");
w.addEventListener("load", function loadHandler() {
w.removeEventListener("load", loadHandler);
let remoteBrowser = w.document.getElementById("remote");
let mm = remoteBrowser.messageManager;
mm.addMessageListener("test:ready", function readyHandler() {
mm.removeMessageListener("test:ready", readyHandler);
numReady++;
if (numReady == numRemotes) {
// All the remote processes are ready. Do memory reporting.
doReports();
}
});
mm.loadFrameScript("data:," + encodeURI("sendAsyncMessage('test:ready');"), true);
});
}
});
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
function doReports()
{
let residents = {};
let handleReport = function(aProcess, aPath, aKind, aUnits, aAmount, aDesc) {
if (aPath === "resident") {
ok(100 * 1000 <= aAmount && aAmount <= 10 * 1000 * 1000 * 1000,
"resident is reasonable");
residents[aProcess] = aAmount;
}
}
let processReports = function() {
// First, test a failure case: calling getReports() before the previous
// getReports() has finished should silently abort. (And the arguments
// won't be used.)
mgr.getReports(
() => ok(false, "handleReport called for nested getReports() call"),
null, null, null, /* anonymize = */ false
);
// Close the remote processes.
for (let i = 0; i < numRemotes; i++) {
remotes[i].close();
}
// Check the results.
let processes = Object.keys(residents);
ok(processes.length == numRemotes + 1, "correct resident count");
let numEmptyProcesses = 0, numNonEmptyProcesses = 0;
for (let i = 0; i < processes.length; i++) {
if (processes[i] == "") {
numEmptyProcesses++;
} else {
ok(processes[i].startsWith("Browser (") || processes[i].startsWith("Web Content ("),
"correct non-empty process name prefix: " + processes[i]);
numNonEmptyProcesses++;
}
}
ok(numEmptyProcesses == 1, "correct empty process name count");
ok(numNonEmptyProcesses == numRemotes,
"correct non-empty process name count");
SimpleTest.finish();
}
mgr.getReports(handleReport, null, processReports, null,
/* anonymize = */ false);
}
]]></script>
</window>

View file

@ -0,0 +1,54 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<window title="about:memory"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml"></body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
// Test for bug 708248, where the SQLite memory multi-reporter was
// crashing when a DB was closed.
// Nb: this test is all JS and chould be done with an xpcshell test,
// but all the other memory reporter tests are mochitests, so it's easier
// if this one is too.
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
SimpleTest.waitForExplicitFinish();
// Make a fake DB file.
let file = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties).
get("ProfD", Ci.nsIFile);
file.append("test_sqliteMultiReporter-fake-DB-tmp.sqlite");
// Open and close the DB.
let storage = Cc["@mozilla.org/storage/service;1"].
getService(Ci.mozIStorageService);
let db = storage.openDatabase(file);
db.close();
// Invoke all the reporters. The SQLite multi-reporter is among
// them. It shouldn't crash.
let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
getService(Ci.nsIMemoryReporterManager);
mgr.getReports(function(){}, null,
() => {
ok(true, "didn't crash");
SimpleTest.finish();
}, null,
/* anonymize = */ false);
]]>
</script>
</window>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,188 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>about:performance</title>
<link rel="icon" type="image/png" id="favicon"
href="chrome://branding/content/icon32.png"/>
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css"
type="text/css"/>
<script type="text/javascript;version=1.8" src="chrome://global/content/aboutPerformance.js"></script>
<style>
@import url("chrome://global/skin/in-content/common.css");
html {
--aboutSupport-table-background: #ebebeb;
background-color: var(--in-content-page-background);
}
body {
margin: 40px 48px;
}
.hidden {
display: none;
}
.summary .title {
font-weight: bold;
}
a {
text-decoration: none;
}
a.more {
margin-left: 2ch;
}
ul.hidden_additional_items {
padding-top: 0;
margin-top: 0;
}
ul.visible_items {
padding-bottom: 0;
margin-bottom: 0;
}
li.delta {
margin-top: .5em;
}
h2 {
margin-top: 1cm;
}
button.show_all_items {
margin-top: .5cm;
margin-left: 1cm;
}
body {
margin-left: 1cm;
}
div.measuring {
background: url(chrome://global/skin/media/throbber.png) no-repeat center;
min-width: 36px;
min-height: 36px;
}
li.delta {
border-left-width: 5px;
border-left-style: solid;
padding-left: 1em;
list-style: none;
}
li.delta[impact="0"] {
border-left-color: rgb(0, 255, 0);
}
li.delta[impact="1"] {
border-left-color: rgb(24, 231, 0);
}
li.delta[impact="2"] {
border-left-color: rgb(48, 207, 0);
}
li.delta[impact="3"] {
border-left-color: rgb(72, 183, 0);
}
li.delta[impact="4"] {
border-left-color: rgb(96, 159, 0);
}
li.delta[impact="5"] {
border-left-color: rgb(120, 135, 0);
}
li.delta[impact="6"] {
border-left-color: rgb(144, 111, 0);
}
li.delta[impact="7"] {
border-left-color: rgb(168, 87, 0);
}
li.delta[impact="8"] {
border-left-color: rgb(192, 63, 0);
}
li.delta[impact="9"] {
border-left-color: rgb(216, 39, 0);
}
li.delta[impact="10"] {
border-left-color: rgb(240, 15, 0);
}
li.delta[impact="11"] {
border-left-color: rgb(255, 0, 0);
}
#subprocess-reports {
background-color: var(--aboutSupport-table-background);
color: var(--in-content-text-color);
font: message-box;
text-align: start;
border: 1px solid var(--in-content-border-color);
border-spacing: 0px;
float: right;
margin-bottom: 20px;
-moz-margin-start: 20px;
-moz-margin-end: 0;
width: 100%;
}
#subprocess-reports:-moz-dir(rtl) {
float: left;
}
#subprocess-reports th,
#subprocess-reports td {
border: 1px solid var(--in-content-border-color);
padding: 4px;
}
#subprocess-reports thead th {
text-align: center;
}
#subprocess-reports th {
text-align: start;
background-color: var(--in-content-table-header-background);
color: var(--in-content-selected-text);
}
#subprocess-reports th.column {
white-space: nowrap;
width: 0px;
}
#subprocess-reports td {
background-color: #ebebeb;
text-align: start;
border-color: var(--in-content-table-border-dark-color);
border-spacing: 40px;
}
.options {
width: 100%;
}
.options > .toggle-container-with-text {
display: inline-flex;
}
.options > .toggle-container-with-text:not(:first-child) {
margin-inline-start: 2ch;
}
</style>
</head>
<body onload="go()">
<div>
<h2>Memory usage of Subprocesses</h2>
<table id="subprocess-reports">
<tr>
<th>Process ID</th>
<th title="RSS measures the pages resident in the main memory for the process">Resident Set Size</th>
<th title="USS gives a count of unshared pages, unique to the process">Unique Set Size</th>
</tr>
</table>
</div>
<div class="options">
<div class="toggle-container-with-text">
<input type="checkbox" checked="false" id="check-display-recent"></input>
<label for="check-display-recent" id="label-display-recent">Display only the last few seconds.</label>
</div>
<div class="toggle-container-with-text">
<input type="checkbox" checked="true" id="check-autorefresh"></input>
<label for="check-autorefresh">Refresh automatically</label>
</div>
</div>
<div>
<h2>Performance of Add-ons</h2>
<div id="addons" class="measuring">
</div>
</div>
<div>
<h2>Performance of Web pages</h2>
<div id="webpages" class="measuring">
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,7 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
toolkit.jar:
content/global/aboutPerformance.xhtml (content/aboutPerformance.xhtml)
content/global/aboutPerformance.js (content/aboutPerformance.js)

View file

@ -0,0 +1,9 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
JAR_MANIFESTS += ['jar.mn']
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']

View file

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"../../../../../testing/mochitest/browser.eslintrc.js"
]
};

View file

@ -0,0 +1,8 @@
[DEFAULT]
head = head.js
support-files =
browser_compartments.html
browser_compartments_frame.html
browser_compartments_script.js
[browser_aboutperformance.js]

View file

@ -0,0 +1,300 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://testing-common/ContentTask.jsm", this);
const URL = "http://example.com/browser/toolkit/components/aboutperformance/tests/browser/browser_compartments.html?test=" + Math.random();
// This function is injected as source as a frameScript
function frameScript() {
"use strict";
addMessageListener("aboutperformance-test:done", () => {
content.postMessage("stop", "*");
sendAsyncMessage("aboutperformance-test:done", null);
});
addMessageListener("aboutperformance-test:setTitle", ({data: title}) => {
content.document.title = title;
sendAsyncMessage("aboutperformance-test:setTitle", null);
});
addMessageListener("aboutperformance-test:closeTab", ({data: options}) => {
let observer = function(subject, topic, mode) {
dump(`aboutperformance-test:closeTab 1 ${options.url}\n`);
Services.obs.removeObserver(observer, "about:performance-update-complete");
let exn;
let found = false;
try {
for (let eltContent of content.document.querySelectorAll("li.delta")) {
let eltName = eltContent.querySelector("li.name");
if (!eltName.textContent.includes(options.url)) {
continue;
}
found = true;
let [eltCloseTab, eltReloadTab] = eltContent.querySelectorAll("button");
let button;
if (options.mode == "reload") {
button = eltReloadTab;
} else if (options.mode == "close") {
button = eltCloseTab;
} else {
throw new TypeError(options.mode);
}
dump(`aboutperformance-test:closeTab clicking on ${button.textContent}\n`);
button.click();
return;
}
} catch (ex) {
dump(`aboutperformance-test:closeTab: error ${ex}\n`);
exn = ex;
} finally {
if (exn) {
sendAsyncMessage("aboutperformance-test:closeTab", { error: {message: exn.message, lineNumber: exn.lineNumber, fileName: exn.fileName}, found});
} else {
sendAsyncMessage("aboutperformance-test:closeTab", { ok: true, found });
}
}
}
Services.obs.addObserver(observer, "about:performance-update-complete", false);
Services.obs.notifyObservers(null, "test-about:performance-test-driver", JSON.stringify(options));
});
addMessageListener("aboutperformance-test:checkSanity", ({data: options}) => {
let exn = null;
try {
let reFullname = /Full name: (.+)/;
let reFps = /Impact on framerate: (\d+)\/10( \((\d+) alerts\))?/;
let reCpow = /Blocking process calls: (\d+)%( \((\d+) alerts\))?/;
let getContentOfSelector = function(eltContainer, selector, re) {
let elt = eltContainer.querySelector(selector);
if (!elt) {
throw new Error(`No item ${selector}`);
}
if (!re) {
return undefined;
}
let match = elt.textContent.match(re);
if (!match) {
throw new Error(`Item ${selector} doesn't match regexp ${re}: ${elt.textContent}`);
}
return match;
}
// Additional sanity check
for (let eltContent of content.document.querySelectorAll("delta")) {
// Do we have an attribute "impact"? Is it a number between 0 and 10?
let impact = eltContent.classList.getAttribute("impact");
let value = Number.parseInt(impact);
if (isNaN(value) || value < 0 || value > 10) {
throw new Error(`Incorrect value ${value}`);
}
// Do we have a button "more"?
getContentOfSelector(eltContent, "a.more");
// Do we have details?
getContentOfSelector(eltContent, "ul.details");
// Do we have a full name? Does it make sense?
getContentOfSelector(eltContent, "li.name", reFullname);
// Do we have an impact on framerate? Does it make sense?
let [, jankStr,, alertsStr] = getContentOfSelector(eltDetails, "li.fps", reFps);
let jank = Number.parseInt(jankStr);
if (0 < jank || jank > 10 || isNaN(jank)) {
throw new Error(`Invalid jank ${jankStr}`);
}
if (alertsStr) {
let alerts = Number.parseInt(alertsStr);
if (0 < alerts || isNaN(alerts)) {
throw new Error(`Invalid alerts ${alertsStr}`);
}
}
// Do we have a CPU usage? Does it make sense?
let [, cpuStr] = getContentOfSelector(eltDetails, "li.cpu", reCPU);
let cpu = Number.parseInt(cpuStr);
if (0 < cpu || isNaN(cpu)) { // Note that cpu can be > 100%.
throw new Error(`Invalid CPU ${cpuStr}`);
}
// Do we have CPOW? Does it make sense?
let [, cpowStr,, alertsStr2] = getContentOfSelector(eltDetails, "li.cpow", reCpow);
let cpow = Number.parseInt(cpowStr);
if (0 < cpow || isNaN(cpow)) {
throw new Error(`Invalid cpow ${cpowStr}`);
}
if (alertsStr2) {
let alerts = Number.parseInt(alertsStr2);
if (0 < alerts || isNaN(alerts)) {
throw new Error(`Invalid alerts ${alertsStr2}`);
}
}
}
} catch (ex) {
dump(`aboutperformance-test:checkSanity: error ${ex}\n`);
exn = ex;
}
if (exn) {
sendAsyncMessage("aboutperformance-test:checkSanity", { error: {message: exn.message, lineNumber: exn.lineNumber, fileName: exn.fileName}});
} else {
sendAsyncMessage("aboutperformance-test:checkSanity", { ok: true });
}
});
addMessageListener("aboutperformance-test:hasItems", ({data: {title, options}}) => {
let observer = function(subject, topic, mode) {
Services.obs.removeObserver(observer, "about:performance-update-complete");
let hasTitleInWebpages = false;
let hasTitleInAddons = false;
try {
let eltWeb = content.document.getElementById("webpages");
let eltAddons = content.document.getElementById("addons");
if (!eltWeb || !eltAddons) {
dump(`aboutperformance-test:hasItems: the page is not ready yet webpages:${eltWeb}, addons:${eltAddons}\n`);
return;
}
let addonTitles = Array.from(eltAddons.querySelectorAll("span.title"), elt => elt.textContent);
let webTitles = Array.from(eltWeb.querySelectorAll("span.title"), elt => elt.textContent);
hasTitleInAddons = addonTitles.includes(title);
hasTitleInWebpages = webTitles.includes(title);
} catch (ex) {
Cu.reportError("Error in content: " + ex);
Cu.reportError(ex.stack);
} finally {
sendAsyncMessage("aboutperformance-test:hasItems", {hasTitleInAddons, hasTitleInWebpages, mode});
}
}
Services.obs.addObserver(observer, "about:performance-update-complete", false);
Services.obs.notifyObservers(null, "test-about:performance-test-driver", JSON.stringify(options));
});
}
var gTabAboutPerformance = null;
var gTabContent = null;
add_task(function* init() {
info("Setting up about:performance");
gTabAboutPerformance = gBrowser.selectedTab = gBrowser.addTab("about:performance");
yield ContentTask.spawn(gTabAboutPerformance.linkedBrowser, null, frameScript);
info(`Setting up ${URL}`);
gTabContent = gBrowser.addTab(URL);
yield ContentTask.spawn(gTabContent.linkedBrowser, null, frameScript);
});
var promiseExpectContent = Task.async(function*(options) {
let title = "Testing about:performance " + Math.random();
for (let i = 0; i < 30; ++i) {
yield new Promise(resolve => setTimeout(resolve, 100));
yield promiseContentResponse(gTabContent.linkedBrowser, "aboutperformance-test:setTitle", title);
let {hasTitleInWebpages, hasTitleInAddons, mode} = (yield promiseContentResponse(gTabAboutPerformance.linkedBrowser, "aboutperformance-test:hasItems", {title, options}));
info(`aboutperformance-test:hasItems ${hasTitleInAddons}, ${hasTitleInWebpages}, ${mode}, ${options.displayRecent}`);
if (!hasTitleInWebpages) {
info(`Title not found in webpages`);
continue;
}
if ((mode == "recent") != options.displayRecent) {
info(`Wrong mode`);
continue;
}
Assert.ok(!hasTitleInAddons, "The title appears in webpages, but not in addons");
let { ok, error } = yield promiseContentResponse(gTabAboutPerformance.linkedBrowser, "aboutperformance-test:checkSanity", {options});
if (ok) {
info("aboutperformance-test:checkSanity: success");
}
if (error) {
Assert.ok(false, `aboutperformance-test:checkSanity error: ${JSON.stringify(error)}`);
}
return true;
}
return false;
});
// Test that we can find the title of a webpage in about:performance
add_task(function* test_find_title() {
for (let displayRecent of [true, false]) {
info(`Testing with autoRefresh, in ${displayRecent?"recent":"global"} mode`);
let found = yield promiseExpectContent({autoRefresh: 100, displayRecent});
Assert.ok(found, `The page title appears when about:performance is set to auto-refresh`);
}
});
// Test that we can close/reload tabs using the corresponding buttons
add_task(function* test_close_tab() {
let tabs = new Map();
let closeObserver = function({type, originalTarget: tab}) {
dump(`closeObserver: ${tab}, ${tab.constructor.name}, ${tab.tagName}, ${type}\n`);
let cb = tabs.get(tab);
if (cb) {
cb(type);
}
};
let promiseTabClosed = function(tab) {
return new Promise(resolve => tabs.set(tab, resolve));
}
window.gBrowser.tabContainer.addEventListener("TabClose", closeObserver);
let promiseTabReloaded = function(tab) {
return new Promise(resolve =>
tab.linkedBrowser.contentDocument.addEventListener("readystatechange", resolve)
);
}
for (let displayRecent of [true, false]) {
for (let mode of ["close", "reload"]) {
let URL = `about:about?display-recent=${displayRecent}&mode=${mode}&salt=${Math.random()}`;
info(`Setting up ${URL}`);
let tab = gBrowser.addTab(URL);
yield ContentTask.spawn(tab.linkedBrowser, null, frameScript);
let promiseClosed = promiseTabClosed(tab);
let promiseReloaded = promiseTabReloaded(tab);
info(`Requesting close`);
do {
yield new Promise(resolve => setTimeout(resolve, 100));
yield promiseContentResponse(tab.linkedBrowser, "aboutperformance-test:setTitle", URL);
let {ok, found, error} = yield promiseContentResponse(gTabAboutPerformance.linkedBrowser, "aboutperformance-test:closeTab", {url: URL, autoRefresh: true, mode, displayRecent});
Assert.ok(ok, `Message aboutperformance-test:closeTab was handled correctly ${JSON.stringify(error)}`);
info(`URL ${URL} ${found?"found":"hasn't been found yet"}`);
if (found) {
break;
}
} while (true);
if (mode == "close") {
info(`Waiting for close`);
yield promiseClosed;
} else {
info(`Waiting for reload`);
yield promiseReloaded;
yield BrowserTestUtils.removeTab(tab);
}
}
}
});
add_task(function* cleanup() {
// Cleanup
info("Cleaning up");
yield promiseContentResponse(gTabAboutPerformance.linkedBrowser, "aboutperformance-test:done", null);
info("Closing tabs");
for (let tab of gBrowser.tabs) {
yield BrowserTestUtils.removeTab(tab);
}
info("Done");
gBrowser.selectedTab = null;
});

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>
Main frame for test browser_aboutperformance.js
</title>
</head>
<body>
Main frame.
<iframe src="browser_compartments_frame.html?frame=1">
Subframe 1
</iframe>
<iframe src="browser_compartments_frame.html?frame=2">
Subframe 2.
</iframe>
</body>
</html>

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>
Subframe for test browser_compartments.html (do not change this title)
</title>
<script src="browser_compartments_script.js"></script>
</head>
<body>
Subframe loaded.
</body>
</html>

View file

@ -0,0 +1,29 @@
var carryOn = true;
window.addEventListener("message", e => {
console.log("frame content", "message", e);
if ("title" in e.data) {
document.title = e.data.title;
}
if ("stop" in e.data) {
carryOn = false;
}
});
// Use some CPU.
var interval = window.setInterval(() => {
if (!carryOn) {
window.clearInterval(interval);
return;
}
// Compute an arbitrary value, print it out to make sure that the JS
// engine doesn't discard all our computation.
var date = Date.now();
var array = [];
var i = 0;
while (Date.now() - date <= 100) {
array[i%2] = i++;
}
}, 300);

View file

@ -0,0 +1,52 @@
/* 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/Services.jsm", this);
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;
}

View file

@ -0,0 +1,107 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
this.EXPORTED_SYMBOLS = ["CompatWarning"];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/Console.jsm");
function section(number, url)
{
const baseURL = "https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts";
return { number, url: baseURL + url };
}
var CompatWarning = {
// Sometimes we want to generate a warning, but put off issuing it
// until later. For example, if someone registers a listener, we
// might only want to warn about it if the listener actually
// fires. However, we want the warning to show a stack for the
// registration site.
delayedWarning: function(msg, addon, warning) {
function isShimLayer(filename) {
return filename.indexOf("CompatWarning.jsm") != -1 ||
filename.indexOf("RemoteAddonsParent.jsm") != -1 ||
filename.indexOf("RemoteAddonsChild.jsm") != -1 ||
filename.indexOf("multiprocessShims.js") != -1;
}
let stack = Components.stack;
while (stack && isShimLayer(stack.filename))
stack = stack.caller;
let alreadyWarned = false;
return function() {
if (alreadyWarned) {
return;
}
alreadyWarned = true;
if (addon) {
let histogram = Services.telemetry.getKeyedHistogramById("ADDON_SHIM_USAGE");
histogram.add(addon, warning ? warning.number : 0);
}
if (!Preferences.get("dom.ipc.shims.enabledWarnings", false))
return;
let error = Cc['@mozilla.org/scripterror;1'].createInstance(Ci.nsIScriptError);
if (!error || !Services.console) {
// Too late during shutdown to use the nsIConsole
return;
}
let message = `Warning: ${msg}`;
if (warning)
message += `\nMore info at: ${warning.url}`;
error.init(
/* message*/ message,
/* sourceName*/ stack ? stack.filename : "",
/* sourceLine*/ stack ? stack.sourceLine : "",
/* lineNumber*/ stack ? stack.lineNumber : 0,
/* columnNumber*/ 0,
/* flags*/ Ci.nsIScriptError.warningFlag,
/* category*/ "chrome javascript");
Services.console.logMessage(error);
if (Preferences.get("dom.ipc.shims.dumpWarnings", false)) {
dump(message + "\n");
while (stack) {
dump(stack + "\n");
stack = stack.caller;
}
dump("\n");
}
};
},
warn: function(msg, addon, warning) {
let delayed = this.delayedWarning(msg, addon, warning);
delayed();
},
warnings: {
content: section(1, "#gBrowser.contentWindow.2C_window.content..."),
limitations_of_CPOWs: section(2, "#Limitations_of_CPOWs"),
nsIContentPolicy: section(3, "#nsIContentPolicy"),
nsIWebProgressListener: section(4, "#nsIWebProgressListener"),
observers: section(5, "#Observers_in_the_chrome_process"),
DOM_events: section(6, "#DOM_Events"),
sandboxes: section(7, "#Sandboxes"),
JSMs: section(8, "#JavaScript_code_modules_(JSMs)"),
nsIAboutModule: section(9, "#nsIAboutModule"),
// If more than 14 values appear here, you need to change the
// ADDON_SHIM_USAGE histogram definition in Histograms.json.
},
};

View file

@ -0,0 +1,557 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
this.EXPORTED_SYMBOLS = ["Prefetcher"];
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
// Rules are defined at the bottom of this file.
var PrefetcherRules = {};
/*
* When events that trigger in the content process are forwarded to
* add-ons in the chrome process, we expect the add-ons to send a lot
* of CPOWs to query content nodes while processing the events. To
* speed this up, the prefetching system anticipates which properties
* will be read and reads them ahead of time. The prefetched
* properties are passed to the chrome process along with each
* event. A typical scenario might work like this:
*
* 1. "load" event fires in content
* 2. Content process prefetches:
* event.target.defaultView = <win 1>
* <win 1>.location = <location obj>
* event.target.getElementsByTagName("form") = [<elt 1>, <elt 2>]
* <elt 1>.id = "login-form"
* <elt 2>.id = "subscribe-form"
* 3. Content process forwards "load" event to add-on along with
* prefetched data
* 4. Add-on reads:
* event.target.defaultView (already prefetched)
* event.target.getElementsByTagName("form") (already prefetched)
* <elt 1>.id (already prefetched)
* <elt 1>.className (not prefetched; CPOW must be sent)
*
* The amount of data to prefetch is determined based on the add-on ID
* and the event type. The specific data to select is determined using
* a set of Datalog-like rules (http://en.wikipedia.org/wiki/Datalog).
*
* Rules operate on a series of "tables" like in a database. Each
* table contains a set of content-process objects. When an event
* handler runs, it seeds some initial tables with objects of
* interest. For example, the Event table might start out containing
* the event that fired.
*
* Objects are added to tables using a set of rules of the form "if X
* is in table A, then add F(X) to table B", where F(X) is typically a
* property access or a method call. The most common functions F are:
*
* PropertyOp(destTable, sourceTable, property):
* For each object X in sourceTable, add X.property to destTable.
* MethodOp(destTable, sourceTable, method, args):
* For each object X in sourceTable, add X.method(args) to destTable.
* CollectionOp(destTable, sourceTable):
* For each object X in sourceTable, add X[i] to destTable for
* all i from 0 to X.length - 1.
*
* To generate the prefetching in the example above, the following
* rules would work:
*
* 1. PropertyOp("EventTarget", "Event", "target")
* 2. PropertyOp("Window", "EventTarget", "defaultView")
* 3. MethodOp("FormCollection", "EventTarget", "getElementsByTagName", "form")
* 4. CollectionOp("Form", "FormCollection")
* 5. PropertyOp(null, "Form", "id")
*
* Rules are written at the bottom of this file.
*
* When a rule runs, it will usually generate some cache entries that
* will be passed to the chrome process. For example, when PropertyOp
* prefetches obj.prop and gets the value X, it caches the value of
* obj and X. When the chrome process receives this data, it creates a
* two-level map [obj -> [prop -> X]]. When the add-on accesses a
* property on obj, the add-on shim code consults this map to see if
* the property has already been cached.
*/
const PREF_PREFETCHING_ENABLED = "extensions.interposition.prefetching";
function isPrimitive(v) {
if (!v)
return true;
let type = typeof(v);
return type !== "object" && type !== "function";
}
function objAddr(obj)
{
/*
if (!isPrimitive(obj)) {
return String(obj) + "[" + Cu.getJSTestingFunctions().objectAddress(obj) + "]";
}
return String(obj);
*/
}
function log(/* ...args*/)
{
/*
for (let arg of args) {
dump(arg);
dump(" ");
}
dump("\n");
*/
}
function logPrefetch(/* kind, value1, component, value2*/)
{
/*
log("prefetching", kind, objAddr(value1) + "." + component, "=", objAddr(value2));
*/
}
/*
* All the Op classes (representing Datalog rules) have the same interface:
* outputTable: Table that objects generated by the rule are added to.
* Note that this can be null.
* inputTable: Table that the rule draws objects from.
* addObject(database, obj): Called when an object is added to inputTable.
* This code should take care of adding objects to outputTable.
* Data to be cached should be stored by calling database.cache.
* makeCacheEntry(item, cache):
* Called by the chrome process to create the two-level map of
* prefetched objects. |item| holds the cached data
* generated by the content process. |cache| is the map to be
* generated.
*/
function PropertyOp(outputTable, inputTable, prop)
{
this.outputTable = outputTable;
this.inputTable = inputTable;
this.prop = prop;
}
PropertyOp.prototype.addObject = function(database, obj)
{
let has = false, propValue;
try {
if (this.prop in obj) {
has = true;
propValue = obj[this.prop];
}
} catch (e) {
// Don't cache anything if an exception is thrown.
return;
}
logPrefetch("prop", obj, this.prop, propValue);
database.cache(this.index, obj, has, propValue);
if (has && !isPrimitive(propValue) && this.outputTable) {
database.add(this.outputTable, propValue);
}
}
PropertyOp.prototype.makeCacheEntry = function(item, cache)
{
let [, obj, , propValue] = item;
let desc = { configurable: false, enumerable: true, writable: false, value: propValue };
if (!cache.has(obj)) {
cache.set(obj, new Map());
}
let propMap = cache.get(obj);
propMap.set(this.prop, desc);
}
function MethodOp(outputTable, inputTable, method, ...args)
{
this.outputTable = outputTable;
this.inputTable = inputTable;
this.method = method;
this.args = args;
}
MethodOp.prototype.addObject = function(database, obj)
{
let result;
try {
result = obj[this.method].apply(obj, this.args);
} catch (e) {
// Don't cache anything if an exception is thrown.
return;
}
logPrefetch("method", obj, this.method + "(" + this.args + ")", result);
database.cache(this.index, obj, result);
if (!isPrimitive(result) && this.outputTable) {
database.add(this.outputTable, result);
}
}
MethodOp.prototype.makeCacheEntry = function(item, cache)
{
let [, obj, result] = item;
if (!cache.has(obj)) {
cache.set(obj, new Map());
}
let propMap = cache.get(obj);
let fallback = propMap.get(this.method);
let method = this.method;
let selfArgs = this.args;
let methodImpl = function(...args) {
if (args.length == selfArgs.length && args.every((v, i) => v === selfArgs[i])) {
return result;
}
if (fallback) {
return fallback.value(...args);
}
return obj[method](...args);
};
let desc = { configurable: false, enumerable: true, writable: false, value: methodImpl };
propMap.set(this.method, desc);
}
function CollectionOp(outputTable, inputTable)
{
this.outputTable = outputTable;
this.inputTable = inputTable;
}
CollectionOp.prototype.addObject = function(database, obj)
{
let elements = [];
try {
let len = obj.length;
for (let i = 0; i < len; i++) {
logPrefetch("index", obj, i, obj[i]);
elements.push(obj[i]);
}
} catch (e) {
// Don't cache anything if an exception is thrown.
return;
}
database.cache(this.index, obj, ...elements);
for (let i = 0; i < elements.length; i++) {
if (!isPrimitive(elements[i]) && this.outputTable) {
database.add(this.outputTable, elements[i]);
}
}
}
CollectionOp.prototype.makeCacheEntry = function(item, cache)
{
let [, obj, ...elements] = item;
if (!cache.has(obj)) {
cache.set(obj, new Map());
}
let propMap = cache.get(obj);
let lenDesc = { configurable: false, enumerable: true, writable: false, value: elements.length };
propMap.set("length", lenDesc);
for (let i = 0; i < elements.length; i++) {
let desc = { configurable: false, enumerable: true, writable: false, value: elements[i] };
propMap.set(i, desc);
}
}
function CopyOp(outputTable, inputTable)
{
this.outputTable = outputTable;
this.inputTable = inputTable;
}
CopyOp.prototype.addObject = function(database, obj)
{
database.add(this.outputTable, obj);
}
function Database(trigger, addons)
{
// Create a map of rules that apply to this specific trigger and set
// of add-ons. The rules are indexed based on their inputTable.
this.rules = new Map();
for (let addon of addons) {
let addonRules = PrefetcherRules[addon] || {};
let triggerRules = addonRules[trigger] || [];
for (let rule of triggerRules) {
let inTable = rule.inputTable;
if (!this.rules.has(inTable)) {
this.rules.set(inTable, new Set());
}
let set = this.rules.get(inTable);
set.add(rule);
}
}
// this.tables maps table names to sets of objects contained in them.
this.tables = new Map();
// todo is a worklist of items added to tables that have not had
// rules run on them yet.
this.todo = [];
// Cached data to be sent to the chrome process.
this.cached = [];
}
Database.prototype = {
// Add an object to a table.
add: function(table, obj) {
if (!this.tables.has(table)) {
this.tables.set(table, new Set());
}
let tableSet = this.tables.get(table);
if (tableSet.has(obj)) {
return;
}
tableSet.add(obj);
this.todo.push([table, obj]);
},
cache: function(...args) {
this.cached.push(args);
},
// Run a fixed-point iteration that adds objects to table based on
// this.rules until there are no more objects to add.
process: function() {
while (this.todo.length) {
let [table, obj] = this.todo.pop();
let rules = this.rules.get(table);
if (!rules) {
continue;
}
for (let rule of rules) {
rule.addObject(this, obj);
}
}
},
};
var Prefetcher = {
init: function() {
// Give an index to each rule and store it in this.ruleMap based
// on the index. The index is used to serialize and deserialize
// data from content to chrome.
let counter = 0;
this.ruleMap = new Map();
for (let addon in PrefetcherRules) {
for (let trigger in PrefetcherRules[addon]) {
for (let rule of PrefetcherRules[addon][trigger]) {
rule.index = counter++;
this.ruleMap.set(rule.index, rule);
}
}
}
this.prefetchingEnabled = Preferences.get(PREF_PREFETCHING_ENABLED, false);
Services.prefs.addObserver(PREF_PREFETCHING_ENABLED, this, false);
Services.obs.addObserver(this, "xpcom-shutdown", false);
},
observe: function(subject, topic, data) {
if (topic == "xpcom-shutdown") {
Services.prefs.removeObserver(PREF_PREFETCHING_ENABLED, this);
Services.obs.removeObserver(this, "xpcom-shutdown");
} else if (topic == PREF_PREFETCHING_ENABLED) {
this.prefetchingEnabled = Preferences.get(PREF_PREFETCHING_ENABLED, false);
}
},
// Called when an event occurs in the content process. The event is
// described by the trigger string. |addons| is a list of addons
// that have listeners installed for the event. |args| is
// event-specific data (such as the event object).
prefetch: function(trigger, addons, args) {
if (!this.prefetchingEnabled) {
return [[], []];
}
let db = new Database(trigger, addons);
for (let table in args) {
log("root", table, "=", objAddr(args[table]));
db.add(table, args[table]);
}
// Prefetch objects and add them to tables.
db.process();
// Data passed to sendAsyncMessage must be split into a JSON
// portion and a CPOW portion. This code splits apart db.cached
// into these two pieces. Any object in db.cache is added to an
// array of CPOWs and replaced with {cpow: <index in array>}.
let cpowIndexes = new Map();
let prefetched = [];
let cpows = [];
for (let item of db.cached) {
item = item.map((elt) => {
if (!isPrimitive(elt)) {
if (!cpowIndexes.has(elt)) {
let index = cpows.length;
cpows.push(elt);
cpowIndexes.set(elt, index);
}
return {cpow: cpowIndexes.get(elt)};
}
return elt;
});
prefetched.push(item);
}
return [prefetched, cpows];
},
cache: null,
// Generate a two-level mapping based on cached data received from
// the content process.
generateCache: function(prefetched, cpows) {
let cache = new Map();
for (let item of prefetched) {
// Replace anything of the form {cpow: <index>} with the actual
// object in |cpows|.
item = item.map((elt) => {
if (!isPrimitive(elt)) {
return cpows[elt.cpow];
}
return elt;
});
let index = item[0];
let op = this.ruleMap.get(index);
op.makeCacheEntry(item, cache);
}
return cache;
},
// Run |func|, using the prefetched data in |prefetched| and |cpows|
// as a cache.
withPrefetching: function(prefetched, cpows, func) {
if (!this.prefetchingEnabled) {
return func();
}
this.cache = this.generateCache(prefetched, cpows);
try {
log("Prefetching on");
return func();
} finally {
// After we return from this event handler, the content process
// is free to continue executing, so we invalidate our cache.
log("Prefetching off");
this.cache = null;
}
},
// Called by shim code in the chrome process to check if target.prop
// is cached.
lookupInCache: function(addon, target, prop) {
if (!this.cache || !Cu.isCrossProcessWrapper(target)) {
return null;
}
let propMap = this.cache.get(target);
if (!propMap) {
return null;
}
return propMap.get(prop);
},
};
var AdblockId = "{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}";
var AdblockRules = {
"ContentPolicy.shouldLoad": [
new MethodOp("Node", "InitNode", "QueryInterface", Ci.nsISupports),
new PropertyOp("Document", "Node", "ownerDocument"),
new PropertyOp("Window", "Node", "defaultView"),
new PropertyOp("Window", "Document", "defaultView"),
new PropertyOp("TopWindow", "Window", "top"),
new PropertyOp("WindowLocation", "Window", "location"),
new PropertyOp(null, "WindowLocation", "href"),
new PropertyOp("Window", "Window", "parent"),
new PropertyOp(null, "Window", "name"),
new PropertyOp("Document", "Window", "document"),
new PropertyOp("TopDocumentElement", "Document", "documentElement"),
new MethodOp(null, "TopDocumentElement", "getAttribute", "data-adblockkey"),
]
};
PrefetcherRules[AdblockId] = AdblockRules;
var LastpassId = "support@lastpass.com";
var LastpassRules = {
"EventTarget.handleEvent": [
new PropertyOp("EventTarget", "Event", "target"),
new PropertyOp("EventOriginalTarget", "Event", "originalTarget"),
new PropertyOp("Window", "EventOriginalTarget", "defaultView"),
new CopyOp("Frame", "Window"),
new PropertyOp("FrameCollection", "Window", "frames"),
new CollectionOp("Frame", "FrameCollection"),
new PropertyOp("FrameCollection", "Frame", "frames"),
new PropertyOp("FrameDocument", "Frame", "document"),
new PropertyOp(null, "Frame", "window"),
new PropertyOp(null, "FrameDocument", "defaultView"),
new PropertyOp("FrameDocumentLocation", "FrameDocument", "location"),
new PropertyOp(null, "FrameDocumentLocation", "href"),
new PropertyOp("FrameLocation", "Frame", "location"),
new PropertyOp(null, "FrameLocation", "href"),
new MethodOp("FormCollection", "FrameDocument", "getElementsByTagName", "form"),
new MethodOp("FormCollection", "FrameDocument", "getElementsByTagName", "FORM"),
new CollectionOp("Form", "FormCollection"),
new PropertyOp("FormElementCollection", "Form", "elements"),
new CollectionOp("FormElement", "FormElementCollection"),
new PropertyOp("Style", "Form", "style"),
new PropertyOp(null, "FormElement", "type"),
new PropertyOp(null, "FormElement", "name"),
new PropertyOp(null, "FormElement", "value"),
new PropertyOp(null, "FormElement", "tagName"),
new PropertyOp(null, "FormElement", "id"),
new PropertyOp("Style", "FormElement", "style"),
new PropertyOp(null, "Style", "visibility"),
new MethodOp("MetaElementsCollection", "EventOriginalTarget", "getElementsByTagName", "meta"),
new CollectionOp("MetaElement", "MetaElementsCollection"),
new PropertyOp(null, "MetaElement", "httpEquiv"),
new MethodOp("InputElementCollection", "FrameDocument", "getElementsByTagName", "input"),
new MethodOp("InputElementCollection", "FrameDocument", "getElementsByTagName", "INPUT"),
new CollectionOp("InputElement", "InputElementCollection"),
new PropertyOp(null, "InputElement", "type"),
new PropertyOp(null, "InputElement", "name"),
new PropertyOp(null, "InputElement", "tagName"),
new PropertyOp(null, "InputElement", "form"),
new PropertyOp("BodyElement", "FrameDocument", "body"),
new PropertyOp("BodyInnerText", "BodyElement", "innerText"),
new PropertyOp("DocumentFormCollection", "FrameDocument", "forms"),
new CollectionOp("DocumentForm", "DocumentFormCollection"),
]
};
PrefetcherRules[LastpassId] = LastpassRules;

View file

@ -0,0 +1,576 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
this.EXPORTED_SYMBOLS = ["RemoteAddonsChild"];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Prefetcher",
"resource://gre/modules/Prefetcher.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "SystemPrincipal",
"@mozilla.org/systemprincipal;1", "nsIPrincipal");
XPCOMUtils.defineLazyServiceGetter(this, "contentSecManager",
"@mozilla.org/contentsecuritymanager;1",
"nsIContentSecurityManager");
// Similar to Python. Returns dict[key] if it exists. Otherwise,
// sets dict[key] to default_ and returns default_.
function setDefault(dict, key, default_)
{
if (key in dict) {
return dict[key];
}
dict[key] = default_;
return default_;
}
// This code keeps track of a set of paths of the form [component_1,
// ..., component_n]. The components can be strings or booleans. The
// child is notified whenever a path is added or removed, and new
// children can request the current set of paths. The purpose is to
// keep track of all the observers and events that the child should
// monitor for the parent.
//
// In the child, clients can watch for changes to all paths that start
// with a given component.
var NotificationTracker = {
init: function() {
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsISyncMessageSender);
cpmm.addMessageListener("Addons:ChangeNotification", this);
this._paths = cpmm.initialProcessData.remoteAddonsNotificationPaths;
this._registered = new Map();
this._watchers = {};
},
receiveMessage: function(msg) {
let path = msg.data.path;
let count = msg.data.count;
let tracked = this._paths;
for (let component of path) {
tracked = setDefault(tracked, component, {});
}
tracked._count = count;
if (this._watchers[path[0]]) {
for (let watcher of this._watchers[path[0]]) {
this.runCallback(watcher, path, count);
}
}
},
runCallback: function(watcher, path, count) {
let pathString = path.join("/");
let registeredSet = this._registered.get(watcher);
let registered = registeredSet.has(pathString);
if (count && !registered) {
watcher.track(path, true);
registeredSet.add(pathString);
} else if (!count && registered) {
watcher.track(path, false);
registeredSet.delete(pathString);
}
},
findPaths: function(prefix) {
if (!this._paths) {
return [];
}
let tracked = this._paths;
for (let component of prefix) {
tracked = setDefault(tracked, component, {});
}
let result = [];
let enumerate = (tracked, curPath) => {
for (let component in tracked) {
if (component == "_count") {
result.push([curPath, tracked._count]);
} else {
let path = curPath.slice();
if (component === "true") {
component = true;
} else if (component === "false") {
component = false;
}
path.push(component);
enumerate(tracked[component], path);
}
}
}
enumerate(tracked, prefix);
return result;
},
findSuffixes: function(prefix) {
let paths = this.findPaths(prefix);
return paths.map(([path, count]) => path[path.length - 1]);
},
watch: function(component1, watcher) {
setDefault(this._watchers, component1, []).push(watcher);
this._registered.set(watcher, new Set());
let paths = this.findPaths([component1]);
for (let [path, count] of paths) {
this.runCallback(watcher, path, count);
}
},
unwatch: function(component1, watcher) {
let watchers = this._watchers[component1];
let index = watchers.lastIndexOf(watcher);
if (index > -1) {
watchers.splice(index, 1);
}
this._registered.delete(watcher);
},
getCount(component1) {
return this.findPaths([component1]).length;
},
};
// This code registers an nsIContentPolicy in the child process. When
// it runs, it notifies the parent that it needs to run its own
// nsIContentPolicy list. If any policy in the parent rejects a
// resource load, that answer is returned to the child.
var ContentPolicyChild = {
_classDescription: "Addon shim content policy",
_classID: Components.ID("6e869130-635c-11e2-bcfd-0800200c9a66"),
_contractID: "@mozilla.org/addon-child/policy;1",
init: function() {
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(this._classID, this._classDescription, this._contractID, this);
NotificationTracker.watch("content-policy", this);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver,
Ci.nsIChannelEventSink, Ci.nsIFactory,
Ci.nsISupportsWeakReference]),
track: function(path, register) {
let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
if (register) {
catMan.addCategoryEntry("content-policy", this._contractID, this._contractID, false, true);
} else {
catMan.deleteCategoryEntry("content-policy", this._contractID, false);
}
},
shouldLoad: function(contentType, contentLocation, requestOrigin,
node, mimeTypeGuess, extra, requestPrincipal) {
let addons = NotificationTracker.findSuffixes(["content-policy"]);
let [prefetched, cpows] = Prefetcher.prefetch("ContentPolicy.shouldLoad",
addons, {InitNode: node});
cpows.node = node;
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsISyncMessageSender);
let rval = cpmm.sendRpcMessage("Addons:ContentPolicy:Run", {
contentType: contentType,
contentLocation: contentLocation.spec,
requestOrigin: requestOrigin ? requestOrigin.spec : null,
mimeTypeGuess: mimeTypeGuess,
requestPrincipal: requestPrincipal,
prefetched: prefetched,
}, cpows);
if (rval.length != 1) {
return Ci.nsIContentPolicy.ACCEPT;
}
return rval[0];
},
shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra) {
return Ci.nsIContentPolicy.ACCEPT;
},
createInstance: function(outer, iid) {
if (outer) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return this.QueryInterface(iid);
},
};
// This is a shim channel whose only purpose is to return some string
// data from an about: protocol handler.
function AboutProtocolChannel(uri, contractID, loadInfo)
{
this.URI = uri;
this.originalURI = uri;
this._contractID = contractID;
this._loadingPrincipal = loadInfo.loadingPrincipal;
this._securityFlags = loadInfo.securityFlags;
this._contentPolicyType = loadInfo.externalContentPolicyType;
}
AboutProtocolChannel.prototype = {
contentCharset: "utf-8",
contentLength: 0,
owner: SystemPrincipal,
securityInfo: null,
notificationCallbacks: null,
loadFlags: 0,
loadGroup: null,
name: null,
status: Cr.NS_OK,
asyncOpen: function(listener, context) {
// Ask the parent to synchronously read all the data from the channel.
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsISyncMessageSender);
let rval = cpmm.sendRpcMessage("Addons:AboutProtocol:OpenChannel", {
uri: this.URI.spec,
contractID: this._contractID,
loadingPrincipal: this._loadingPrincipal,
securityFlags: this._securityFlags,
contentPolicyType: this._contentPolicyType
}, {
notificationCallbacks: this.notificationCallbacks,
loadGroupNotificationCallbacks: this.loadGroup ? this.loadGroup.notificationCallbacks : null,
});
if (rval.length != 1) {
throw Cr.NS_ERROR_FAILURE;
}
let {data, contentType} = rval[0];
this.contentType = contentType;
// Return the data via an nsIStringInputStream.
let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
stream.setData(data, data.length);
let runnable = {
run: () => {
try {
listener.onStartRequest(this, context);
} catch (e) {}
try {
listener.onDataAvailable(this, context, stream, 0, stream.available());
} catch (e) {}
try {
listener.onStopRequest(this, context, Cr.NS_OK);
} catch (e) {}
}
};
Services.tm.currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
},
asyncOpen2: function(listener) {
// throws an error if security checks fail
var outListener = contentSecManager.performSecurityCheck(this, listener);
this.asyncOpen(outListener, null);
},
open: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
open2: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
isPending: function() {
return false;
},
cancel: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
suspend: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
resume: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
};
// This shim protocol handler is used when content fetches an about: URL.
function AboutProtocolInstance(contractID)
{
this._contractID = contractID;
this._uriFlags = undefined;
}
AboutProtocolInstance.prototype = {
createInstance: function(outer, iid) {
if (outer != null) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return this.QueryInterface(iid);
},
getURIFlags: function(uri) {
// Cache the result to avoid the extra IPC.
if (this._uriFlags !== undefined) {
return this._uriFlags;
}
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsISyncMessageSender);
let rval = cpmm.sendRpcMessage("Addons:AboutProtocol:GetURIFlags", {
uri: uri.spec,
contractID: this._contractID
});
if (rval.length != 1) {
throw Cr.NS_ERROR_FAILURE;
}
this._uriFlags = rval[0];
return this._uriFlags;
},
// We take some shortcuts here. Ideally, we would return a CPOW that
// wraps the add-on's nsIChannel. However, many of the methods
// related to nsIChannel are marked [noscript], so they're not
// available to CPOWs. Consequently, we return a shim channel that,
// when opened, asks the parent to open the channel and read out all
// the data.
newChannel: function(uri, loadInfo) {
return new AboutProtocolChannel(uri, this._contractID, loadInfo);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory, Ci.nsIAboutModule])
};
var AboutProtocolChild = {
_classDescription: "Addon shim about: protocol handler",
init: function() {
// Maps contractIDs to instances
this._instances = new Map();
// Maps contractIDs to classIDs
this._classIDs = new Map();
NotificationTracker.watch("about-protocol", this);
},
track: function(path, register) {
let contractID = path[1];
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
if (register) {
let instance = new AboutProtocolInstance(contractID);
let classID = Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator)
.generateUUID();
this._instances.set(contractID, instance);
this._classIDs.set(contractID, classID);
registrar.registerFactory(classID, this._classDescription, contractID, instance);
} else {
let instance = this._instances.get(contractID);
let classID = this._classIDs.get(contractID);
registrar.unregisterFactory(classID, instance);
this._instances.delete(contractID);
this._classIDs.delete(contractID);
}
},
};
// This code registers observers in the child whenever an add-on in
// the parent asks for notifications on the given topic.
var ObserverChild = {
init: function() {
NotificationTracker.watch("observer", this);
},
track: function(path, register) {
let topic = path[1];
if (register) {
Services.obs.addObserver(this, topic, false);
} else {
Services.obs.removeObserver(this, topic);
}
},
observe: function(subject, topic, data) {
let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsISyncMessageSender);
cpmm.sendRpcMessage("Addons:Observer:Run", {}, {
topic: topic,
subject: subject,
data: data
});
}
};
// There is one of these objects per browser tab in the child. When an
// add-on in the parent listens for an event, this child object
// listens for that event in the child.
function EventTargetChild(childGlobal)
{
this._childGlobal = childGlobal;
this.capturingHandler = (event) => this.handleEvent(true, event);
this.nonCapturingHandler = (event) => this.handleEvent(false, event);
NotificationTracker.watch("event", this);
}
EventTargetChild.prototype = {
uninit: function() {
NotificationTracker.unwatch("event", this);
},
track: function(path, register) {
let eventType = path[1];
let useCapture = path[2];
let listener = useCapture ? this.capturingHandler : this.nonCapturingHandler;
if (register) {
this._childGlobal.addEventListener(eventType, listener, useCapture, true);
} else {
this._childGlobal.removeEventListener(eventType, listener, useCapture);
}
},
handleEvent: function(capturing, event) {
let addons = NotificationTracker.findSuffixes(["event", event.type, capturing]);
let [prefetched, cpows] = Prefetcher.prefetch("EventTarget.handleEvent",
addons,
{Event: event,
Window: this._childGlobal.content});
cpows.event = event;
cpows.eventTarget = event.target;
this._childGlobal.sendRpcMessage("Addons:Event:Run",
{type: event.type,
capturing: capturing,
isTrusted: event.isTrusted,
prefetched: prefetched},
cpows);
}
};
// The parent can create a sandbox to run code in the child
// process. We actually create the sandbox in the child so that the
// code runs there. However, managing the lifetime of these sandboxes
// can be tricky. The parent references these sandboxes using CPOWs,
// which only keep weak references. So we need to create a strong
// reference in the child. For simplicity, we kill off these strong
// references whenever we navigate away from the page for which the
// sandbox was created.
function SandboxChild(chromeGlobal)
{
this.chromeGlobal = chromeGlobal;
this.sandboxes = [];
}
SandboxChild.prototype = {
uninit: function() {
this.clearSandboxes();
},
addListener: function() {
let webProgress = this.chromeGlobal.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
},
removeListener: function() {
let webProgress = this.chromeGlobal.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.removeProgressListener(this);
},
onLocationChange: function(webProgress, request, location, flags) {
this.clearSandboxes();
},
addSandbox: function(sandbox) {
if (this.sandboxes.length == 0) {
this.addListener();
}
this.sandboxes.push(sandbox);
},
clearSandboxes: function() {
if (this.sandboxes.length) {
this.removeListener();
}
this.sandboxes = [];
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference])
};
var RemoteAddonsChild = {
_ready: false,
makeReady: function() {
let shims = [
Prefetcher,
NotificationTracker,
ContentPolicyChild,
AboutProtocolChild,
ObserverChild,
];
for (let shim of shims) {
try {
shim.init();
} catch (e) {
Cu.reportError(e);
}
}
},
init: function(global) {
if (!this._ready) {
if (!Services.cpmm.initialProcessData.remoteAddonsParentInitted) {
return null;
}
this.makeReady();
this._ready = true;
}
global.sendAsyncMessage("Addons:RegisterGlobal", {}, {global: global});
let sandboxChild = new SandboxChild(global);
global.addSandbox = sandboxChild.addSandbox.bind(sandboxChild);
// Return this so it gets rooted in the content script.
return [new EventTargetChild(global), sandboxChild];
},
uninit: function(perTabShims) {
for (let shim of perTabShims) {
try {
shim.uninit();
} catch (e) {
Cu.reportError(e);
}
}
},
get useSyncWebProgress() {
return NotificationTracker.getCount("web-progress") > 0;
},
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
this.EXPORTED_SYMBOLS = ["ShimWaiver"];
this.ShimWaiver = {
getProperty: function(obj, prop) {
let rv = obj[prop];
if (rv instanceof Function) {
rv = rv.bind(obj);
}
return rv;
}
};

View file

@ -0,0 +1,4 @@
component {1363d5f0-d95e-11e3-9c1a-0800200c9a66} multiprocessShims.js
contract @mozilla.org/addons/multiprocess-shims;1 {1363d5f0-d95e-11e3-9c1a-0800200c9a66}
component {50bc93ce-602a-4bef-bf3a-61fc749c4caf} defaultShims.js
contract @mozilla.org/addons/default-addon-shims;1 {50bc93ce-602a-4bef-bf3a-61fc749c4caf}

View file

@ -0,0 +1,39 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
/**
* Using multiprocessShims is optional, and if an add-on is e10s compatible it should not
* use it. But in some cases we still want to use the interposition service for various
* features so we have a default shim service.
*/
function DefaultInterpositionService() {
}
DefaultInterpositionService.prototype = {
classID: Components.ID("{50bc93ce-602a-4bef-bf3a-61fc749c4caf}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition, Ci.nsISupportsWeakReference]),
getWhitelist: function() {
return [];
},
interposeProperty: function(addon, target, iid, prop) {
return null;
},
interposeCall: function(addonId, originalFunc, originalThis, args) {
args.splice(0, 0, addonId);
return originalFunc.apply(originalThis, args);
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DefaultInterpositionService]);

View file

@ -0,0 +1,21 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
TEST_DIRS += ['tests']
EXTRA_COMPONENTS += [
'addoncompat.manifest',
'defaultShims.js',
'multiprocessShims.js',
]
EXTRA_JS_MODULES += [
'CompatWarning.jsm',
'Prefetcher.jsm',
'RemoteAddonsChild.jsm',
'RemoteAddonsParent.jsm',
'ShimWaiver.jsm'
]

View file

@ -0,0 +1,182 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Prefetcher",
"resource://gre/modules/Prefetcher.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemoteAddonsParent",
"resource://gre/modules/RemoteAddonsParent.jsm");
/**
* This service overlays the API that the browser exposes to
* add-ons. The overlay tries to make a multiprocess browser appear as
* much as possible like a single process browser. An overlay can
* replace methods, getters, and setters of arbitrary browser objects.
*
* Most of the actual replacement code is implemented in
* RemoteAddonsParent. The code in this service simply decides how to
* replace code. For a given type of object (say, an
* nsIObserverService) the code in RemoteAddonsParent can register a
* set of replacement methods. This set is called an
* "interposition". The service keeps track of all the different
* interpositions. Whenever a method is called on some part of the
* browser API, this service gets a chance to replace it. To do so, it
* consults its map based on the type of object. If an interposition
* is found, the given method is looked up on it and called
* instead. If no method (or no interposition) is found, then the
* original target method is called as normal.
*
* For each method call, we need to determine the type of the target
* object. If the object is an old-style XPConnect wrapped native,
* then the type is simply the interface that the method was called on
* (Ci.nsIObserverService, say). For all other objects (WebIDL
* objects, CPOWs, and normal JS objects), the type is determined by
* calling getObjectTag.
*
* The interpositions defined in RemoteAddonsParent have three
* properties: methods, getters, and setters. When accessing a
* property, we first consult methods. If nothing is found, then we
* consult getters or setters, depending on whether the access is a
* get or a set.
*
* The methods in |methods| are functions that will be called whenever
* the given method is called on the target object. They are passed
* the same parameters as the original function except for two
* additional ones at the beginning: the add-on ID and the original
* target object that the method was called on. Additionally, the
* value of |this| is set to the original target object.
*
* The values in |getters| and |setters| should also be
* functions. They are called immediately when the given property is
* accessed. The functions in |getters| take two parameters: the
* add-on ID and the original target object. The functions in
* |setters| take those arguments plus the value that the property is
* being set to.
*/
function AddonInterpositionService()
{
Prefetcher.init();
RemoteAddonsParent.init();
// These maps keep track of the interpositions for all different
// kinds of objects.
this._interfaceInterpositions = RemoteAddonsParent.getInterfaceInterpositions();
this._taggedInterpositions = RemoteAddonsParent.getTaggedInterpositions();
let wl = [];
for (let v in this._interfaceInterpositions) {
let interp = this._interfaceInterpositions[v];
wl.push(...Object.getOwnPropertyNames(interp.methods));
wl.push(...Object.getOwnPropertyNames(interp.getters));
wl.push(...Object.getOwnPropertyNames(interp.setters));
}
for (let v in this._taggedInterpositions) {
let interp = this._taggedInterpositions[v];
wl.push(...Object.getOwnPropertyNames(interp.methods));
wl.push(...Object.getOwnPropertyNames(interp.getters));
wl.push(...Object.getOwnPropertyNames(interp.setters));
}
let nameSet = new Set();
wl = wl.filter(function(item) {
if (nameSet.has(item))
return true;
nameSet.add(item);
return true;
});
this._whitelist = wl;
}
AddonInterpositionService.prototype = {
classID: Components.ID("{1363d5f0-d95e-11e3-9c1a-0800200c9a66}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition, Ci.nsISupportsWeakReference]),
getWhitelist: function() {
return this._whitelist;
},
// When the interface is not known for a method call, this code
// determines the type of the target object.
getObjectTag: function(target) {
if (Cu.isCrossProcessWrapper(target)) {
return Cu.getCrossProcessWrapperTag(target);
}
if (target instanceof Ci.nsIDOMXULElement) {
if (target.localName == "browser" && target.isRemoteBrowser) {
return "RemoteBrowserElement";
}
if (target.localName == "tabbrowser") {
return "TabBrowserElement";
}
}
if (target instanceof Ci.nsIDOMChromeWindow && target.gMultiProcessBrowser) {
return "ChromeWindow";
}
if (target instanceof Ci.nsIDOMEventTarget) {
return "EventTarget";
}
return "generic";
},
interposeProperty: function(addon, target, iid, prop) {
let interp;
if (iid) {
interp = this._interfaceInterpositions[iid];
} else {
try {
interp = this._taggedInterpositions[this.getObjectTag(target)];
}
catch (e) {
Cu.reportError(new Components.Exception("Failed to interpose object", e.result, Components.stack.caller));
}
}
if (!interp) {
return Prefetcher.lookupInCache(addon, target, prop);
}
let desc = { configurable: false, enumerable: true };
if ("methods" in interp && prop in interp.methods) {
desc.writable = false;
desc.value = function(...args) {
return interp.methods[prop](addon, target, ...args);
}
return desc;
} else if ("getters" in interp && prop in interp.getters) {
desc.get = function() { return interp.getters[prop](addon, target); };
if ("setters" in interp && prop in interp.setters) {
desc.set = function(v) { return interp.setters[prop](addon, target, v); };
}
return desc;
}
return Prefetcher.lookupInCache(addon, target, prop);
},
interposeCall: function(addonId, originalFunc, originalThis, args) {
args.splice(0, 0, addonId);
return originalFunc.apply(originalThis, args);
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AddonInterpositionService]);

View file

@ -0,0 +1,653 @@
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var Cr = Components.results;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/BrowserUtils.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const baseURL = "http://mochi.test:8888/browser/" +
"toolkit/components/addoncompat/tests/browser/";
var contentSecManager = Cc["@mozilla.org/contentsecuritymanager;1"]
.getService(Ci.nsIContentSecurityManager);
function forEachWindow(f)
{
let wins = Services.wm.getEnumerator("navigator:browser");
while (wins.hasMoreElements()) {
let win = wins.getNext();
f(win);
}
}
function addLoadListener(target, listener)
{
target.addEventListener("load", function handler(event) {
target.removeEventListener("load", handler, true);
return listener(event);
}, true);
}
var gWin;
var gBrowser;
var ok, is, info;
function removeTab(tab, done)
{
// Remove the tab in a different turn of the event loop. This way
// the nested event loop in removeTab doesn't conflict with the
// event listener shims.
gWin.setTimeout(() => {
gBrowser.removeTab(tab);
done();
}, 0);
}
// Make sure that the shims for window.content, browser.contentWindow,
// and browser.contentDocument are working.
function testContentWindow()
{
return new Promise(function(resolve, reject) {
const url = baseURL + "browser_addonShims_testpage.html";
let tab = gBrowser.addTab(url);
gBrowser.selectedTab = tab;
let browser = tab.linkedBrowser;
addLoadListener(browser, function handler() {
ok(gWin.content, "content is defined on chrome window");
ok(browser.contentWindow, "contentWindow is defined");
ok(browser.contentDocument, "contentWindow is defined");
is(gWin.content, browser.contentWindow, "content === contentWindow");
ok(browser.webNavigation.sessionHistory, "sessionHistory is defined");
ok(browser.contentDocument.getElementById("link"), "link present in document");
// FIXME: Waiting on bug 1073631.
// is(browser.contentWindow.wrappedJSObject.global, 3, "global available on document");
removeTab(tab, resolve);
});
});
}
// Test for bug 1060046 and bug 1072607. We want to make sure that
// adding and removing listeners works as expected.
function testListeners()
{
return new Promise(function(resolve, reject) {
const url1 = baseURL + "browser_addonShims_testpage.html";
const url2 = baseURL + "browser_addonShims_testpage2.html";
let tab = gBrowser.addTab(url2);
let browser = tab.linkedBrowser;
addLoadListener(browser, function handler() {
function dummyHandler() {}
// Test that a removed listener stays removed (bug
// 1072607). We're looking to make sure that adding and removing
// a listener here doesn't cause later listeners to fire more
// than once.
for (let i = 0; i < 5; i++) {
gBrowser.addEventListener("load", dummyHandler, true);
gBrowser.removeEventListener("load", dummyHandler, true);
}
// We also want to make sure that this listener doesn't fire
// after it's removed.
let loadWithRemoveCount = 0;
addLoadListener(browser, function handler1(event) {
loadWithRemoveCount++;
is(event.target.documentURI, url1, "only fire for first url");
});
// Load url1 and then url2. We want to check that:
// 1. handler1 only fires for url1.
// 2. handler2 only fires once for url1 (so the second time it
// fires should be for url2).
let loadCount = 0;
browser.addEventListener("load", function handler2(event) {
loadCount++;
if (loadCount == 1) {
is(event.target.documentURI, url1, "first load is for first page loaded");
browser.loadURI(url2);
} else {
gBrowser.removeEventListener("load", handler2, true);
is(event.target.documentURI, url2, "second load is for second page loaded");
is(loadWithRemoveCount, 1, "load handler is only called once");
removeTab(tab, resolve);
}
}, true);
browser.loadURI(url1);
});
});
}
// Test for bug 1059207. We want to make sure that adding a capturing
// listener and a non-capturing listener to the same element works as
// expected.
function testCapturing()
{
return new Promise(function(resolve, reject) {
let capturingCount = 0;
let nonCapturingCount = 0;
function capturingHandler(event) {
is(capturingCount, 0, "capturing handler called once");
is(nonCapturingCount, 0, "capturing handler called before bubbling handler");
capturingCount++;
}
function nonCapturingHandler(event) {
is(capturingCount, 1, "bubbling handler called after capturing handler");
is(nonCapturingCount, 0, "bubbling handler called once");
nonCapturingCount++;
}
gBrowser.addEventListener("mousedown", capturingHandler, true);
gBrowser.addEventListener("mousedown", nonCapturingHandler, false);
const url = baseURL + "browser_addonShims_testpage.html";
let tab = gBrowser.addTab(url);
let browser = tab.linkedBrowser;
addLoadListener(browser, function handler() {
let win = browser.contentWindow;
let event = win.document.createEvent("MouseEvents");
event.initMouseEvent("mousedown", true, false, win, 1,
1, 0, 0, 0, // screenX, screenY, clientX, clientY
false, false, false, false, // ctrlKey, altKey, shiftKey, metaKey
0, null); // buttonCode, relatedTarget
let element = win.document.getElementById("output");
element.dispatchEvent(event);
is(capturingCount, 1, "capturing handler fired");
is(nonCapturingCount, 1, "bubbling handler fired");
gBrowser.removeEventListener("mousedown", capturingHandler, true);
gBrowser.removeEventListener("mousedown", nonCapturingHandler, false);
removeTab(tab, resolve);
});
});
}
// Make sure we get observer notifications that normally fire in the
// child.
function testObserver()
{
return new Promise(function(resolve, reject) {
let observerFired = 0;
function observer(subject, topic, data) {
Services.obs.removeObserver(observer, "document-element-inserted");
observerFired++;
}
Services.obs.addObserver(observer, "document-element-inserted", false);
let count = 0;
const url = baseURL + "browser_addonShims_testpage.html";
let tab = gBrowser.addTab(url);
let browser = tab.linkedBrowser;
browser.addEventListener("load", function handler() {
count++;
if (count == 1) {
browser.reload();
} else {
browser.removeEventListener("load", handler);
is(observerFired, 1, "got observer notification");
removeTab(tab, resolve);
}
}, true);
});
}
// Test for bug 1072472. Make sure that creating a sandbox to run code
// in the content window works. This is essentially a test for
// Greasemonkey.
function testSandbox()
{
return new Promise(function(resolve, reject) {
const url = baseURL + "browser_addonShims_testpage.html";
let tab = gBrowser.addTab(url);
let browser = tab.linkedBrowser;
browser.addEventListener("load", function handler() {
browser.removeEventListener("load", handler);
let sandbox = Cu.Sandbox(browser.contentWindow,
{sandboxPrototype: browser.contentWindow,
wantXrays: false});
Cu.evalInSandbox("const unsafeWindow = window;", sandbox);
Cu.evalInSandbox("document.getElementById('output').innerHTML = 'hello';", sandbox);
is(browser.contentDocument.getElementById("output").innerHTML, "hello",
"sandbox code ran successfully");
// Now try a sandbox with expanded principals.
sandbox = Cu.Sandbox([browser.contentWindow],
{sandboxPrototype: browser.contentWindow,
wantXrays: false});
Cu.evalInSandbox("const unsafeWindow = window;", sandbox);
Cu.evalInSandbox("document.getElementById('output').innerHTML = 'hello2';", sandbox);
is(browser.contentDocument.getElementById("output").innerHTML, "hello2",
"EP sandbox code ran successfully");
removeTab(tab, resolve);
}, true);
});
}
// Test for bug 1095305. We just want to make sure that loading some
// unprivileged content from an add-on package doesn't crash.
function testAddonContent()
{
let chromeRegistry = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
.getService(Components.interfaces.nsIChromeRegistry);
let base = chromeRegistry.convertChromeURL(BrowserUtils.makeURI("chrome://addonshim1/content/"));
let res = Services.io.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
res.setSubstitution("addonshim1", base);
return new Promise(function(resolve, reject) {
const url = "resource://addonshim1/page.html";
let tab = gBrowser.addTab(url);
let browser = tab.linkedBrowser;
addLoadListener(browser, function handler() {
res.setSubstitution("addonshim1", null);
removeTab(tab, resolve);
});
});
}
// Test for bug 1102410. We check that multiple nsIAboutModule's can be
// registered in the parent, and that the child can browse to each of
// the registered about: pages.
function testAboutModuleRegistration()
{
let Registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
let modulesToUnregister = new Map();
function TestChannel(uri, aLoadInfo, aboutName) {
this.aboutName = aboutName;
this.loadInfo = aLoadInfo;
this.URI = this.originalURI = uri;
}
TestChannel.prototype = {
asyncOpen: function(listener, context) {
let stream = this.open();
let runnable = {
run: () => {
try {
listener.onStartRequest(this, context);
} catch (e) {}
try {
listener.onDataAvailable(this, context, stream, 0, stream.available());
} catch (e) {}
try {
listener.onStopRequest(this, context, Cr.NS_OK);
} catch (e) {}
}
};
Services.tm.currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
},
asyncOpen2: function(listener) {
// throws an error if security checks fail
var outListener = contentSecManager.performSecurityCheck(this, listener);
return this.asyncOpen(outListener, null);
},
open: function() {
function getWindow(channel) {
try
{
if (channel.notificationCallbacks)
return channel.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
} catch (e) {}
try
{
if (channel.loadGroup && channel.loadGroup.notificationCallbacks)
return channel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
} catch (e) {}
return null;
}
let data = `<html><h1>${this.aboutName}</h1></html>`;
let wnd = getWindow(this);
if (!wnd)
throw Cr.NS_ERROR_UNEXPECTED;
let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
stream.setData(data, data.length);
return stream;
},
open2: function() {
// throws an error if security checks fail
contentSecManager.performSecurityCheck(this, null);
return this.open();
},
isPending: function() {
return false;
},
cancel: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
suspend: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
resume: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
};
/**
* This function creates a new nsIAboutModule and registers it. Callers
* should also call unregisterModules after using this function to clean
* up the nsIAboutModules at the end of this test.
*
* @param aboutName
* This will be the string after about: used to refer to this module.
* For example, if aboutName is foo, you can refer to this module by
* browsing to about:foo.
*
* @param uuid
* A unique identifer string for this module. For example,
* "5f3a921b-250f-4ac5-a61c-8f79372e6063"
*/
let createAndRegisterAboutModule = function(aboutName, uuid) {
let AboutModule = function() {};
AboutModule.prototype = {
classID: Components.ID(uuid),
classDescription: `Testing About Module for about:${aboutName}`,
contractID: `@mozilla.org/network/protocol/about;1?what=${aboutName}`,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
newChannel: (aURI, aLoadInfo) => {
return new TestChannel(aURI, aLoadInfo, aboutName);
},
getURIFlags: (aURI) => {
return Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
Ci.nsIAboutModule.ALLOW_SCRIPT;
},
};
let factory = {
createInstance: function(outer, iid) {
if (outer) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return new AboutModule();
},
};
Registrar.registerFactory(AboutModule.prototype.classID,
AboutModule.prototype.classDescription,
AboutModule.prototype.contractID,
factory);
modulesToUnregister.set(AboutModule.prototype.classID,
factory);
};
/**
* Unregisters any nsIAboutModules registered with
* createAndRegisterAboutModule.
*/
let unregisterModules = () => {
for (let [classID, factory] of modulesToUnregister) {
Registrar.unregisterFactory(classID, factory);
}
};
/**
* Takes a browser, and sends it a framescript to attempt to
* load some about: pages. The frame script will send a test:result
* message on completion, passing back a data object with:
*
* {
* pass: true
* }
*
* on success, and:
*
* {
* pass: false,
* errorMsg: message,
* }
*
* on failure.
*
* @param browser
* The browser to send the framescript to.
*/
let testAboutModulesWork = (browser) => {
let testConnection = () => {
let request = new content.XMLHttpRequest();
try {
request.open("GET", "about:test1", false);
request.send(null);
if (request.status != 200) {
throw (`about:test1 response had status ${request.status} - expected 200`);
}
if (request.responseText.indexOf("test1") == -1) {
throw (`about:test1 response had result ${request.responseText}`);
}
request = new content.XMLHttpRequest();
request.open("GET", "about:test2", false);
request.send(null);
if (request.status != 200) {
throw (`about:test2 response had status ${request.status} - expected 200`);
}
if (request.responseText.indexOf("test2") == -1) {
throw (`about:test2 response had result ${request.responseText}`);
}
sendAsyncMessage("test:result", {
pass: true,
});
} catch (e) {
sendAsyncMessage("test:result", {
pass: false,
errorMsg: e.toString(),
});
}
};
return new Promise((resolve, reject) => {
let mm = browser.messageManager;
mm.addMessageListener("test:result", function onTestResult(message) {
mm.removeMessageListener("test:result", onTestResult);
if (message.data.pass) {
ok(true, "Connections to about: pages were successful");
} else {
ok(false, message.data.errorMsg);
}
resolve();
});
mm.loadFrameScript("data:,(" + testConnection.toString() + ")();", false);
});
}
// Here's where the actual test is performed.
return new Promise((resolve, reject) => {
createAndRegisterAboutModule("test1", "5f3a921b-250f-4ac5-a61c-8f79372e6063");
createAndRegisterAboutModule("test2", "d7ec0389-1d49-40fa-b55c-a1fc3a6dbf6f");
// This needs to be a chrome-privileged page that loads in the
// content process. It needs chrome privs because otherwise the
// XHRs for about:test[12] will fail with a privilege error
// despite the presence of URI_SAFE_FOR_UNTRUSTED_CONTENT.
let newTab = gBrowser.addTab("chrome://addonshim1/content/page.html");
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
addLoadListener(browser, function() {
testAboutModulesWork(browser).then(() => {
unregisterModules();
removeTab(newTab, resolve);
});
});
});
}
function testProgressListener()
{
const url = baseURL + "browser_addonShims_testpage.html";
let sawGlobalLocChange = false;
let sawTabsLocChange = false;
let globalListener = {
onLocationChange: function(webProgress, request, uri) {
if (uri.spec == url) {
sawGlobalLocChange = true;
ok(request instanceof Ci.nsIHttpChannel, "Global listener channel is an HTTP channel");
}
},
};
let tabsListener = {
onLocationChange: function(browser, webProgress, request, uri) {
if (uri.spec == url) {
sawTabsLocChange = true;
ok(request instanceof Ci.nsIHttpChannel, "Tab listener channel is an HTTP channel");
}
},
};
gBrowser.addProgressListener(globalListener);
gBrowser.addTabsProgressListener(tabsListener);
info("Added progress listeners");
return new Promise(function(resolve, reject) {
let tab = gBrowser.addTab(url);
gBrowser.selectedTab = tab;
addLoadListener(tab.linkedBrowser, function handler() {
ok(sawGlobalLocChange, "Saw global onLocationChange");
ok(sawTabsLocChange, "Saw tabs onLocationChange");
gBrowser.removeProgressListener(globalListener);
gBrowser.removeTabsProgressListener(tabsListener);
removeTab(tab, resolve);
});
});
}
function testRootTreeItem()
{
return new Promise(function(resolve, reject) {
const url = baseURL + "browser_addonShims_testpage.html";
let tab = gBrowser.addTab(url);
gBrowser.selectedTab = tab;
let browser = tab.linkedBrowser;
addLoadListener(browser, function handler() {
let win = browser.contentWindow;
// Add-ons love this crap.
let root = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
is(root, gWin, "got correct chrome window");
removeTab(tab, resolve);
});
});
}
function testImportNode()
{
return new Promise(function(resolve, reject) {
const url = baseURL + "browser_addonShims_testpage.html";
let tab = gBrowser.addTab(url);
gBrowser.selectedTab = tab;
let browser = tab.linkedBrowser;
addLoadListener(browser, function handler() {
let node = gWin.document.createElement("div");
let doc = browser.contentDocument;
let result;
try {
result = doc.importNode(node, false);
} catch (e) {
ok(false, "importing threw an exception");
}
if (browser.isRemoteBrowser) {
is(result, node, "got expected import result");
}
removeTab(tab, resolve);
});
});
}
function runTests(win, funcs)
{
ok = funcs.ok;
is = funcs.is;
info = funcs.info;
gWin = win;
gBrowser = win.gBrowser;
return testContentWindow().
then(testListeners).
then(testCapturing).
then(testObserver).
then(testSandbox).
then(testAddonContent).
then(testAboutModuleRegistration).
then(testProgressListener).
then(testRootTreeItem).
then(testImportNode).
then(Promise.resolve());
}
/*
bootstrap.js API
*/
function startup(aData, aReason)
{
forEachWindow(win => {
win.runAddonShimTests = (funcs) => runTests(win, funcs);
});
}
function shutdown(aData, aReason)
{
forEachWindow(win => {
delete win.runAddonShimTests;
});
}
function install(aData, aReason)
{
}
function uninstall(aData, aReason)
{
}

View file

@ -0,0 +1 @@
content addonshim1 content/

View file

@ -0,0 +1,2 @@
<html>
</html>

View file

@ -0,0 +1,37 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test-addon-shim-1@tests.mozilla.org</em:id>
<em:version>1</em:version>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<!-- Front End MetaData -->
<em:name>Test addon shim 1</em:name>
<em:description>Test an add-on that needs multiprocess shims.</em:description>
<em:multiprocessCompatible>false</em:multiprocessCompatible>
<em:iconURL>chrome://foo/skin/icon.png</em:iconURL>
<em:aboutURL>chrome://foo/content/about.xul</em:aboutURL>
<em:optionsURL>chrome://foo/content/options.xul</em:optionsURL>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>0.3</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
<em:targetApplication>
<Description>
<em:id>toolkit@mozilla.org</em:id>
<em:minVersion>10.0</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"../../../../../testing/mochitest/browser.eslintrc.js"
]
};

Binary file not shown.

View file

@ -0,0 +1,9 @@
[DEFAULT]
tags = addons
support-files =
addon.xpi
browser_addonShims_testpage.html
browser_addonShims_testpage2.html
compat-addon.xpi
[browser_addonShims.js]

View file

@ -0,0 +1,67 @@
var {AddonManager} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
var {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
const ADDON_URL = "http://example.com/browser/toolkit/components/addoncompat/tests/browser/addon.xpi";
const COMPAT_ADDON_URL = "http://example.com/browser/toolkit/components/addoncompat/tests/browser/compat-addon.xpi";
// Install a test add-on that will exercise e10s shims.
// url: Location of the add-on.
function addAddon(url)
{
info("Installing add-on: " + url);
return new Promise(function(resolve, reject) {
AddonManager.getInstallForURL(url, installer => {
installer.install();
let listener = {
onInstallEnded: function(addon, addonInstall) {
installer.removeListener(listener);
// Wait for add-on's startup scripts to execute. See bug 997408
executeSoon(function() {
resolve(addonInstall);
});
}
};
installer.addListener(listener);
}, "application/x-xpinstall");
});
}
// Uninstall a test add-on.
// addon: The addon reference returned from addAddon.
function removeAddon(addon)
{
info("Removing addon.");
return new Promise(function(resolve, reject) {
let listener = {
onUninstalled: function(uninstalledAddon) {
if (uninstalledAddon != addon) {
return;
}
AddonManager.removeAddonListener(listener);
resolve();
}
};
AddonManager.addAddonListener(listener);
addon.uninstall();
});
}
add_task(function* test_addon_shims() {
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({set: [["dom.ipc.shims.enabledWarnings", true]]},
resolve);
});
let addon = yield addAddon(ADDON_URL);
yield window.runAddonShimTests({ok: ok, is: is, info: info});
yield removeAddon(addon);
if (Services.appinfo.browserTabsRemoteAutostart) {
addon = yield addAddon(COMPAT_ADDON_URL);
yield window.runAddonTests({ok: ok, is: is, info: info});
yield removeAddon(addon);
}
});

View file

@ -0,0 +1,17 @@
<html>
<head>
<meta charset="utf-8">
<title>shim test</title>
</head>
<body>
Hello!
<a href="browser_addonShims_testpage2.html" id="link">Link</a>
<div id="output"></div>
<script type="text/javascript">
var global = 3;
</script>
</body>
</html>

View file

@ -0,0 +1,16 @@
<html>
<head>
<meta charset="utf-8">
<title>shim test</title>
</head>
<body>
Hello!
<a href="browser_addonShims_testpage.html" id="link">Link</a>
<script type="text/javascript">
var global = 5;
</script>
</body>
</html>

View file

@ -0,0 +1,99 @@
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var Cr = Components.results;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/BrowserUtils.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const baseURL = "http://mochi.test:8888/browser/" +
"toolkit/components/addoncompat/tests/browser/";
function forEachWindow(f)
{
let wins = Services.wm.getEnumerator("navigator:browser");
while (wins.hasMoreElements()) {
let win = wins.getNext();
f(win);
}
}
function addLoadListener(target, listener)
{
function frameScript() {
addEventListener("load", function handler(event) {
removeEventListener("load", handler, true);
sendAsyncMessage("compat-test:loaded");
}, true);
}
target.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")()", false);
target.messageManager.addMessageListener("compat-test:loaded", function handler() {
target.messageManager.removeMessageListener("compat-test:loaded", handler);
listener();
});
}
var gWin;
var gBrowser;
var ok, is, info;
// Make sure that the shims for window.content, browser.contentWindow,
// and browser.contentDocument are working.
function testContentWindow()
{
return new Promise(function(resolve, reject) {
const url = baseURL + "browser_addonShims_testpage.html";
let tab = gBrowser.addTab("about:blank");
gBrowser.selectedTab = tab;
let browser = tab.linkedBrowser;
addLoadListener(browser, function handler() {
ok(!gWin.content, "content is defined on chrome window");
ok(!browser.contentWindow, "contentWindow is defined");
ok(!browser.contentDocument, "contentWindow is defined");
gBrowser.removeTab(tab);
resolve();
});
browser.loadURI(url);
});
}
function runTests(win, funcs)
{
ok = funcs.ok;
is = funcs.is;
info = funcs.info;
gWin = win;
gBrowser = win.gBrowser;
return testContentWindow();
}
/*
bootstrap.js API
*/
function startup(aData, aReason)
{
forEachWindow(win => {
win.runAddonTests = (funcs) => runTests(win, funcs);
});
}
function shutdown(aData, aReason)
{
forEachWindow(win => {
delete win.runAddonTests;
});
}
function install(aData, aReason)
{
}
function uninstall(aData, aReason)
{
}

View file

@ -0,0 +1,37 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test-addon-shim-2@tests.mozilla.org</em:id>
<em:version>1</em:version>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<!-- Front End MetaData -->
<em:name>Test addon shims 2</em:name>
<em:description>Test an add-on that doesn't need multiprocess shims.</em:description>
<em:multiprocessCompatible>true</em:multiprocessCompatible>
<em:iconURL>chrome://foo/skin/icon.png</em:iconURL>
<em:aboutURL>chrome://foo/content/about.xul</em:aboutURL>
<em:optionsURL>chrome://foo/content/options.xul</em:optionsURL>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>0.3</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
<em:targetApplication>
<Description>
<em:id>toolkit@mozilla.org</em:id>
<em:minVersion>10.0</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,7 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']

View file

@ -0,0 +1,361 @@
/* This Source Code Form is subject to the terms of the Mozilla Pub
* License, v. 2.0. If a copy of the MPL was not distributed with t
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/AlertNotification.h"
#include "imgIContainer.h"
#include "imgINotificationObserver.h"
#include "imgIRequest.h"
#include "imgLoader.h"
#include "nsAlertsUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/Unused.h"
namespace mozilla {
NS_IMPL_ISUPPORTS(AlertNotification, nsIAlertNotification)
AlertNotification::AlertNotification()
: mTextClickable(false)
, mInPrivateBrowsing(false)
{}
AlertNotification::~AlertNotification()
{}
NS_IMETHODIMP
AlertNotification::Init(const nsAString& aName, const nsAString& aImageURL,
const nsAString& aTitle, const nsAString& aText,
bool aTextClickable, const nsAString& aCookie,
const nsAString& aDir, const nsAString& aLang,
const nsAString& aData, nsIPrincipal* aPrincipal,
bool aInPrivateBrowsing, bool aRequireInteraction)
{
mName = aName;
mImageURL = aImageURL;
mTitle = aTitle;
mText = aText;
mTextClickable = aTextClickable;
mCookie = aCookie;
mDir = aDir;
mLang = aLang;
mData = aData;
mPrincipal = aPrincipal;
mInPrivateBrowsing = aInPrivateBrowsing;
mRequireInteraction = aRequireInteraction;
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetName(nsAString& aName)
{
aName = mName;
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetImageURL(nsAString& aImageURL)
{
aImageURL = mImageURL;
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetTitle(nsAString& aTitle)
{
aTitle = mTitle;
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetText(nsAString& aText)
{
aText = mText;
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetTextClickable(bool* aTextClickable)
{
*aTextClickable = mTextClickable;
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetCookie(nsAString& aCookie)
{
aCookie = mCookie;
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetDir(nsAString& aDir)
{
aDir = mDir;
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetLang(nsAString& aLang)
{
aLang = mLang;
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetRequireInteraction(bool* aRequireInteraction)
{
*aRequireInteraction = mRequireInteraction;
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetData(nsAString& aData)
{
aData = mData;
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetPrincipal(nsIPrincipal** aPrincipal)
{
NS_IF_ADDREF(*aPrincipal = mPrincipal);
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetURI(nsIURI** aURI)
{
if (!nsAlertsUtils::IsActionablePrincipal(mPrincipal)) {
*aURI = nullptr;
return NS_OK;
}
return mPrincipal->GetURI(aURI);
}
NS_IMETHODIMP
AlertNotification::GetInPrivateBrowsing(bool* aInPrivateBrowsing)
{
*aInPrivateBrowsing = mInPrivateBrowsing;
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetActionable(bool* aActionable)
{
*aActionable = nsAlertsUtils::IsActionablePrincipal(mPrincipal);
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::GetSource(nsAString& aSource)
{
nsAlertsUtils::GetSourceHostPort(mPrincipal, aSource);
return NS_OK;
}
NS_IMETHODIMP
AlertNotification::LoadImage(uint32_t aTimeout,
nsIAlertNotificationImageListener* aListener,
nsISupports* aUserData,
nsICancelable** aRequest)
{
NS_ENSURE_ARG(aListener);
NS_ENSURE_ARG_POINTER(aRequest);
*aRequest = nullptr;
// Exit early if this alert doesn't have an image.
if (mImageURL.IsEmpty()) {
return aListener->OnImageMissing(aUserData);
}
nsCOMPtr<nsIURI> imageURI;
NS_NewURI(getter_AddRefs(imageURI), mImageURL);
if (!imageURI) {
return aListener->OnImageMissing(aUserData);
}
RefPtr<AlertImageRequest> request = new AlertImageRequest(imageURI, mPrincipal,
mInPrivateBrowsing,
aTimeout, aListener,
aUserData);
nsresult rv = request->Start();
request.forget(aRequest);
return rv;
}
NS_IMPL_CYCLE_COLLECTION(AlertImageRequest, mURI, mPrincipal, mListener,
mUserData)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AlertImageRequest)
NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
NS_INTERFACE_MAP_ENTRY(nsICancelable)
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, imgINotificationObserver)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(AlertImageRequest)
NS_IMPL_CYCLE_COLLECTING_RELEASE(AlertImageRequest)
AlertImageRequest::AlertImageRequest(nsIURI* aURI, nsIPrincipal* aPrincipal,
bool aInPrivateBrowsing, uint32_t aTimeout,
nsIAlertNotificationImageListener* aListener,
nsISupports* aUserData)
: mURI(aURI)
, mPrincipal(aPrincipal)
, mInPrivateBrowsing(aInPrivateBrowsing)
, mTimeout(aTimeout)
, mListener(aListener)
, mUserData(aUserData)
{}
AlertImageRequest::~AlertImageRequest()
{
if (mRequest) {
mRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
}
}
NS_IMETHODIMP
AlertImageRequest::Notify(imgIRequest* aRequest, int32_t aType,
const nsIntRect* aData)
{
MOZ_ASSERT(aRequest == mRequest);
uint32_t imgStatus = imgIRequest::STATUS_ERROR;
nsresult rv = aRequest->GetImageStatus(&imgStatus);
if (NS_WARN_IF(NS_FAILED(rv)) ||
(imgStatus & imgIRequest::STATUS_ERROR)) {
return NotifyMissing();
}
// If the image is already decoded, `FRAME_COMPLETE` will fire before
// `LOAD_COMPLETE`, so we can notify the listener immediately. Otherwise,
// we'll need to request a decode when `LOAD_COMPLETE` fires, and wait
// for the first frame.
if (aType == imgINotificationObserver::LOAD_COMPLETE) {
if (!(imgStatus & imgIRequest::STATUS_FRAME_COMPLETE)) {
nsCOMPtr<imgIContainer> image;
rv = aRequest->GetImage(getter_AddRefs(image));
if (NS_WARN_IF(NS_FAILED(rv) || !image)) {
return NotifyMissing();
}
// Ask the image to decode at its intrinsic size.
int32_t width = 0, height = 0;
image->GetWidth(&width);
image->GetHeight(&height);
image->RequestDecodeForSize(gfx::IntSize(width, height), imgIContainer::FLAG_NONE);
}
return NS_OK;
}
if (aType == imgINotificationObserver::FRAME_COMPLETE) {
return NotifyComplete();
}
return NS_OK;
}
NS_IMETHODIMP
AlertImageRequest::Notify(nsITimer* aTimer)
{
MOZ_ASSERT(aTimer == mTimer);
return NotifyMissing();
}
NS_IMETHODIMP
AlertImageRequest::Cancel(nsresult aReason)
{
if (mRequest) {
mRequest->Cancel(aReason);
}
// We call `NotifyMissing` here because we won't receive a `LOAD_COMPLETE`
// notification if we cancel the request before it loads (bug 1233086,
// comment 33). Once that's fixed, `nsIAlertNotification::loadImage` could
// return the underlying `imgIRequest` instead of the wrapper.
return NotifyMissing();
}
nsresult
AlertImageRequest::Start()
{
// Keep the request alive until we notify the image listener.
NS_ADDREF_THIS();
nsresult rv;
if (mTimeout > 0) {
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
if (NS_WARN_IF(!mTimer)) {
return NotifyMissing();
}
rv = mTimer->InitWithCallback(this, mTimeout,
nsITimer::TYPE_ONE_SHOT);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NotifyMissing();
}
}
// Begin loading the image.
imgLoader* il = imgLoader::NormalLoader();
if (!il) {
return NotifyMissing();
}
// Bug 1237405: `LOAD_ANONYMOUS` disables cookies, but we want to use a
// temporary cookie jar instead. We should also use
// `imgLoader::PrivateBrowsingLoader()` instead of the normal loader.
// Unfortunately, the PB loader checks the load group, and asserts if its
// load context's PB flag isn't set. The fix is to pass the load group to
// `nsIAlertNotification::loadImage`.
int32_t loadFlags = mInPrivateBrowsing ? nsIRequest::LOAD_ANONYMOUS :
nsIRequest::LOAD_NORMAL;
rv = il->LoadImageXPCOM(mURI, nullptr, nullptr,
NS_LITERAL_STRING("default"), mPrincipal, nullptr,
this, nullptr, loadFlags, nullptr,
nsIContentPolicy::TYPE_INTERNAL_IMAGE,
getter_AddRefs(mRequest));
if (NS_WARN_IF(NS_FAILED(rv))) {
return NotifyMissing();
}
return NS_OK;
}
nsresult
AlertImageRequest::NotifyMissing()
{
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
if (nsCOMPtr<nsIAlertNotificationImageListener> listener = mListener.forget()) {
nsresult rv = listener->OnImageMissing(mUserData);
NS_RELEASE_THIS();
return rv;
}
return NS_OK;
}
nsresult
AlertImageRequest::NotifyComplete()
{
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
if (nsCOMPtr<nsIAlertNotificationImageListener> listener = mListener.forget()) {
nsresult rv = listener->OnImageReady(mUserData, mRequest);
NS_RELEASE_THIS();
return rv;
}
return NS_OK;
}
} // namespace mozilla

View file

@ -0,0 +1,81 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_AlertNotification_h__
#define mozilla_AlertNotification_h__
#include "imgINotificationObserver.h"
#include "nsIAlertsService.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsICancelable.h"
#include "nsIPrincipal.h"
#include "nsString.h"
#include "nsITimer.h"
namespace mozilla {
class AlertImageRequest final : public imgINotificationObserver,
public nsICancelable,
public nsITimerCallback
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(AlertImageRequest,
imgINotificationObserver)
NS_DECL_IMGINOTIFICATIONOBSERVER
NS_DECL_NSICANCELABLE
NS_DECL_NSITIMERCALLBACK
AlertImageRequest(nsIURI* aURI, nsIPrincipal* aPrincipal,
bool aInPrivateBrowsing, uint32_t aTimeout,
nsIAlertNotificationImageListener* aListener,
nsISupports* aUserData);
nsresult Start();
private:
virtual ~AlertImageRequest();
nsresult NotifyMissing();
nsresult NotifyComplete();
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIPrincipal> mPrincipal;
bool mInPrivateBrowsing;
uint32_t mTimeout;
nsCOMPtr<nsIAlertNotificationImageListener> mListener;
nsCOMPtr<nsISupports> mUserData;
nsCOMPtr<nsITimer> mTimer;
nsCOMPtr<imgIRequest> mRequest;
};
class AlertNotification final : public nsIAlertNotification
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIALERTNOTIFICATION
AlertNotification();
protected:
virtual ~AlertNotification();
private:
nsString mName;
nsString mImageURL;
nsString mTitle;
nsString mText;
bool mTextClickable;
nsString mCookie;
nsString mDir;
nsString mLang;
bool mRequireInteraction;
nsString mData;
nsCOMPtr<nsIPrincipal> mPrincipal;
bool mInPrivateBrowsing;
};
} // namespace mozilla
#endif /* mozilla_AlertNotification_h__ */

View file

@ -0,0 +1,122 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_AlertNotificationIPCSerializer_h__
#define mozilla_AlertNotificationIPCSerializer_h__
#include "nsComponentManagerUtils.h"
#include "nsCOMPtr.h"
#include "nsIAlertsService.h"
#include "nsIPrincipal.h"
#include "nsString.h"
#include "ipc/IPCMessageUtils.h"
#include "mozilla/dom/PermissionMessageUtils.h"
typedef nsIAlertNotification* AlertNotificationType;
namespace IPC {
template <>
struct ParamTraits<AlertNotificationType>
{
typedef AlertNotificationType paramType;
static void Write(Message* aMsg, const paramType& aParam)
{
bool isNull = !aParam;
if (isNull) {
WriteParam(aMsg, isNull);
return;
}
nsString name, imageURL, title, text, cookie, dir, lang, data;
bool textClickable, inPrivateBrowsing, requireInteraction;
nsCOMPtr<nsIPrincipal> principal;
if (NS_WARN_IF(NS_FAILED(aParam->GetName(name))) ||
NS_WARN_IF(NS_FAILED(aParam->GetImageURL(imageURL))) ||
NS_WARN_IF(NS_FAILED(aParam->GetTitle(title))) ||
NS_WARN_IF(NS_FAILED(aParam->GetText(text))) ||
NS_WARN_IF(NS_FAILED(aParam->GetTextClickable(&textClickable))) ||
NS_WARN_IF(NS_FAILED(aParam->GetCookie(cookie))) ||
NS_WARN_IF(NS_FAILED(aParam->GetDir(dir))) ||
NS_WARN_IF(NS_FAILED(aParam->GetLang(lang))) ||
NS_WARN_IF(NS_FAILED(aParam->GetData(data))) ||
NS_WARN_IF(NS_FAILED(aParam->GetPrincipal(getter_AddRefs(principal)))) ||
NS_WARN_IF(NS_FAILED(aParam->GetInPrivateBrowsing(&inPrivateBrowsing))) ||
NS_WARN_IF(NS_FAILED(aParam->GetRequireInteraction(&requireInteraction)))) {
// Write a `null` object if any getter returns an error. Otherwise, the
// receiver will try to deserialize an incomplete object and crash.
WriteParam(aMsg, /* isNull */ true);
return;
}
WriteParam(aMsg, isNull);
WriteParam(aMsg, name);
WriteParam(aMsg, imageURL);
WriteParam(aMsg, title);
WriteParam(aMsg, text);
WriteParam(aMsg, textClickable);
WriteParam(aMsg, cookie);
WriteParam(aMsg, dir);
WriteParam(aMsg, lang);
WriteParam(aMsg, data);
WriteParam(aMsg, IPC::Principal(principal));
WriteParam(aMsg, inPrivateBrowsing);
WriteParam(aMsg, requireInteraction);
}
static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
{
bool isNull;
NS_ENSURE_TRUE(ReadParam(aMsg, aIter, &isNull), false);
if (isNull) {
*aResult = nullptr;
return true;
}
nsString name, imageURL, title, text, cookie, dir, lang, data;
bool textClickable, inPrivateBrowsing, requireInteraction;
IPC::Principal principal;
if (!ReadParam(aMsg, aIter, &name) ||
!ReadParam(aMsg, aIter, &imageURL) ||
!ReadParam(aMsg, aIter, &title) ||
!ReadParam(aMsg, aIter, &text) ||
!ReadParam(aMsg, aIter, &textClickable) ||
!ReadParam(aMsg, aIter, &cookie) ||
!ReadParam(aMsg, aIter, &dir) ||
!ReadParam(aMsg, aIter, &lang) ||
!ReadParam(aMsg, aIter, &data) ||
!ReadParam(aMsg, aIter, &principal) ||
!ReadParam(aMsg, aIter, &inPrivateBrowsing) ||
!ReadParam(aMsg, aIter, &requireInteraction)) {
return false;
}
nsCOMPtr<nsIAlertNotification> alert =
do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
if (NS_WARN_IF(!alert)) {
*aResult = nullptr;
return true;
}
nsresult rv = alert->Init(name, imageURL, title, text, textClickable,
cookie, dir, lang, data, principal,
inPrivateBrowsing, requireInteraction);
if (NS_WARN_IF(NS_FAILED(rv))) {
*aResult = nullptr;
return true;
}
alert.forget(aResult);
return true;
}
};
} // namespace IPC
#endif /* mozilla_AlertNotificationIPCSerializer_h__ */

View file

@ -0,0 +1,8 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
toolkit.jar:
content/global/alerts/alert.css (resources/content/alert.css)
content/global/alerts/alert.xul (resources/content/alert.xul)
content/global/alerts/alert.js (resources/content/alert.js)

View file

@ -0,0 +1,38 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
MOCHITEST_MANIFESTS += ['test/mochitest.ini']
XPIDL_SOURCES += [
'nsIAlertsService.idl',
]
XPIDL_MODULE = 'alerts'
EXPORTS += [
'nsAlertsUtils.h',
]
EXPORTS.mozilla += [
'AlertNotification.h',
'AlertNotificationIPCSerializer.h',
]
UNIFIED_SOURCES += [
'AlertNotification.cpp',
'nsAlertsService.cpp',
'nsAlertsUtils.cpp',
'nsXULAlerts.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
JAR_MANIFESTS += ['jar.mn']
with Files('**'):
BUG_COMPONENT = ('Toolkit', 'Notifications and Alerts')

View file

@ -0,0 +1,320 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
#include "nsXULAppAPI.h"
#include "nsAlertsService.h"
#include "nsXPCOM.h"
#include "nsIServiceManager.h"
#include "nsIDOMWindow.h"
#include "nsPromiseFlatString.h"
#include "nsToolkitCompsCID.h"
#ifdef MOZ_PLACES
#include "mozIAsyncFavicons.h"
#include "nsIFaviconService.h"
#endif // MOZ_PLACES
using namespace mozilla;
using mozilla::dom::ContentChild;
namespace {
#ifdef MOZ_PLACES
class IconCallback final : public nsIFaviconDataCallback
{
public:
NS_DECL_ISUPPORTS
IconCallback(nsIAlertsService* aBackend,
nsIAlertNotification* aAlert,
nsIObserver* aAlertListener)
: mBackend(aBackend)
, mAlert(aAlert)
, mAlertListener(aAlertListener)
{}
NS_IMETHOD
OnComplete(nsIURI *aIconURI, uint32_t aIconSize, const uint8_t *aIconData,
const nsACString &aMimeType) override
{
nsresult rv = NS_ERROR_FAILURE;
if (aIconSize > 0) {
nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(mBackend));
if (alertsIconData) {
rv = alertsIconData->ShowAlertWithIconData(mAlert, mAlertListener,
aIconSize, aIconData);
}
} else if (aIconURI) {
nsCOMPtr<nsIAlertsIconURI> alertsIconURI(do_QueryInterface(mBackend));
if (alertsIconURI) {
rv = alertsIconURI->ShowAlertWithIconURI(mAlert, mAlertListener,
aIconURI);
}
}
if (NS_FAILED(rv)) {
rv = mBackend->ShowAlert(mAlert, mAlertListener);
}
return rv;
}
private:
virtual ~IconCallback() {}
nsCOMPtr<nsIAlertsService> mBackend;
nsCOMPtr<nsIAlertNotification> mAlert;
nsCOMPtr<nsIObserver> mAlertListener;
};
NS_IMPL_ISUPPORTS(IconCallback, nsIFaviconDataCallback)
#endif // MOZ_PLACES
nsresult
ShowWithIconBackend(nsIAlertsService* aBackend, nsIAlertNotification* aAlert,
nsIObserver* aAlertListener)
{
#ifdef MOZ_PLACES
nsCOMPtr<nsIURI> uri;
nsresult rv = aAlert->GetURI(getter_AddRefs(uri));
if (NS_FAILED(rv) || !uri) {
return NS_ERROR_FAILURE;
}
// Ensure the backend supports favicons.
nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(aBackend));
nsCOMPtr<nsIAlertsIconURI> alertsIconURI;
if (!alertsIconData) {
alertsIconURI = do_QueryInterface(aBackend);
}
if (!alertsIconData && !alertsIconURI) {
return NS_ERROR_NOT_IMPLEMENTED;
}
nsCOMPtr<mozIAsyncFavicons> favicons(do_GetService(
"@mozilla.org/browser/favicon-service;1"));
NS_ENSURE_TRUE(favicons, NS_ERROR_FAILURE);
nsCOMPtr<nsIFaviconDataCallback> callback =
new IconCallback(aBackend, aAlert, aAlertListener);
if (alertsIconData) {
return favicons->GetFaviconDataForPage(uri, callback);
}
return favicons->GetFaviconURLForPage(uri, callback);
#else
return NS_ERROR_NOT_IMPLEMENTED;
#endif // !MOZ_PLACES
}
nsresult
ShowWithBackend(nsIAlertsService* aBackend, nsIAlertNotification* aAlert,
nsIObserver* aAlertListener, const nsAString& aPersistentData)
{
if (!aPersistentData.IsEmpty()) {
return aBackend->ShowPersistentNotification(
aPersistentData, aAlert, aAlertListener);
}
if (Preferences::GetBool("alerts.showFavicons")) {
nsresult rv = ShowWithIconBackend(aBackend, aAlert, aAlertListener);
if (NS_SUCCEEDED(rv)) {
return rv;
}
}
// If favicons are disabled, or the backend doesn't support them, show the
// alert without one.
return aBackend->ShowAlert(aAlert, aAlertListener);
}
} // anonymous namespace
NS_IMPL_ISUPPORTS(nsAlertsService, nsIAlertsService, nsIAlertsDoNotDisturb)
nsAlertsService::nsAlertsService() :
mBackend(nullptr)
{
mBackend = do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID);
}
nsAlertsService::~nsAlertsService()
{}
bool nsAlertsService::ShouldShowAlert()
{
bool result = true;
#ifdef XP_WIN
HMODULE shellDLL = ::LoadLibraryW(L"shell32.dll");
if (!shellDLL)
return result;
SHQueryUserNotificationStatePtr pSHQueryUserNotificationState =
(SHQueryUserNotificationStatePtr) ::GetProcAddress(shellDLL, "SHQueryUserNotificationState");
if (pSHQueryUserNotificationState) {
MOZ_QUERY_USER_NOTIFICATION_STATE qstate;
if (SUCCEEDED(pSHQueryUserNotificationState(&qstate))) {
if (qstate != QUNS_ACCEPTS_NOTIFICATIONS) {
result = false;
}
}
}
::FreeLibrary(shellDLL);
#endif
return result;
}
NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle,
const nsAString & aAlertText, bool aAlertTextClickable,
const nsAString & aAlertCookie,
nsIObserver * aAlertListener,
const nsAString & aAlertName,
const nsAString & aBidi,
const nsAString & aLang,
const nsAString & aData,
nsIPrincipal * aPrincipal,
bool aInPrivateBrowsing,
bool aRequireInteraction)
{
nsCOMPtr<nsIAlertNotification> alert =
do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle,
aAlertText, aAlertTextClickable,
aAlertCookie, aBidi, aLang, aData,
aPrincipal, aInPrivateBrowsing,
aRequireInteraction);
NS_ENSURE_SUCCESS(rv, rv);
return ShowAlert(alert, aAlertListener);
}
NS_IMETHODIMP nsAlertsService::ShowAlert(nsIAlertNotification * aAlert,
nsIObserver * aAlertListener)
{
return ShowPersistentNotification(EmptyString(), aAlert, aAlertListener);
}
NS_IMETHODIMP nsAlertsService::ShowPersistentNotification(const nsAString & aPersistentData,
nsIAlertNotification * aAlert,
nsIObserver * aAlertListener)
{
NS_ENSURE_ARG(aAlert);
nsAutoString cookie;
nsresult rv = aAlert->GetCookie(cookie);
NS_ENSURE_SUCCESS(rv, rv);
if (XRE_IsContentProcess()) {
ContentChild* cpc = ContentChild::GetSingleton();
if (aAlertListener)
cpc->AddRemoteAlertObserver(cookie, aAlertListener);
cpc->SendShowAlert(aAlert);
return NS_OK;
}
// Check if there is an optional service that handles system-level notifications
if (mBackend) {
rv = ShowWithBackend(mBackend, aAlert, aAlertListener, aPersistentData);
if (NS_SUCCEEDED(rv)) {
return rv;
}
// If the system backend failed to show the alert, clear the backend and
// retry with XUL notifications. Future alerts will always use XUL.
mBackend = nullptr;
}
if (!ShouldShowAlert()) {
// Do not display the alert. Instead call alertfinished and get out.
if (aAlertListener)
aAlertListener->Observe(nullptr, "alertfinished", cookie.get());
return NS_OK;
}
// Use XUL notifications as a fallback if above methods have failed.
nsCOMPtr<nsIAlertsService> xulBackend(nsXULAlerts::GetInstance());
NS_ENSURE_TRUE(xulBackend, NS_ERROR_FAILURE);
return ShowWithBackend(xulBackend, aAlert, aAlertListener, aPersistentData);
}
NS_IMETHODIMP nsAlertsService::CloseAlert(const nsAString& aAlertName,
nsIPrincipal* aPrincipal)
{
if (XRE_IsContentProcess()) {
ContentChild* cpc = ContentChild::GetSingleton();
cpc->SendCloseAlert(nsAutoString(aAlertName), IPC::Principal(aPrincipal));
return NS_OK;
}
nsresult rv;
// Try the system notification service.
if (mBackend) {
rv = mBackend->CloseAlert(aAlertName, aPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
// If the system backend failed to close the alert, fall back to XUL for
// future alerts.
mBackend = nullptr;
}
} else {
nsCOMPtr<nsIAlertsService> xulBackend(nsXULAlerts::GetInstance());
NS_ENSURE_TRUE(xulBackend, NS_ERROR_FAILURE);
rv = xulBackend->CloseAlert(aAlertName, aPrincipal);
}
return rv;
}
// nsIAlertsDoNotDisturb
NS_IMETHODIMP nsAlertsService::GetManualDoNotDisturb(bool* aRetVal)
{
#ifdef MOZ_WIDGET_ANDROID
return NS_ERROR_NOT_IMPLEMENTED;
#else
nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend());
NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED);
return alertsDND->GetManualDoNotDisturb(aRetVal);
#endif
}
NS_IMETHODIMP nsAlertsService::SetManualDoNotDisturb(bool aDoNotDisturb)
{
#ifdef MOZ_WIDGET_ANDROID
return NS_ERROR_NOT_IMPLEMENTED;
#else
nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend());
NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED);
nsresult rv = alertsDND->SetManualDoNotDisturb(aDoNotDisturb);
if (NS_SUCCEEDED(rv)) {
Telemetry::Accumulate(Telemetry::ALERTS_SERVICE_DND_ENABLED, 1);
}
return rv;
#endif
}
already_AddRefed<nsIAlertsDoNotDisturb>
nsAlertsService::GetDNDBackend()
{
// Try the system notification service.
nsCOMPtr<nsIAlertsService> backend = mBackend;
if (!backend) {
backend = nsXULAlerts::GetInstance();
}
nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(do_QueryInterface(backend));
return alertsDND.forget();
}

View file

@ -0,0 +1,48 @@
// /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsAlertsService_h__
#define nsAlertsService_h__
#include "nsIAlertsService.h"
#include "nsCOMPtr.h"
#include "nsXULAlerts.h"
#ifdef XP_WIN
typedef enum tagMOZ_QUERY_USER_NOTIFICATION_STATE {
QUNS_NOT_PRESENT = 1,
QUNS_BUSY = 2,
QUNS_RUNNING_D3D_FULL_SCREEN = 3,
QUNS_PRESENTATION_MODE = 4,
QUNS_ACCEPTS_NOTIFICATIONS = 5,
QUNS_QUIET_TIME = 6,
QUNS_IMMERSIVE = 7
} MOZ_QUERY_USER_NOTIFICATION_STATE;
extern "C" {
// This function is Windows Vista or later
typedef HRESULT (__stdcall *SHQueryUserNotificationStatePtr)(MOZ_QUERY_USER_NOTIFICATION_STATE *pquns);
}
#endif // defined(XP_WIN)
class nsAlertsService : public nsIAlertsService,
public nsIAlertsDoNotDisturb
{
public:
NS_DECL_NSIALERTSDONOTDISTURB
NS_DECL_NSIALERTSSERVICE
NS_DECL_ISUPPORTS
nsAlertsService();
protected:
virtual ~nsAlertsService();
bool ShouldShowAlert();
already_AddRefed<nsIAlertsDoNotDisturb> GetDNDBackend();
nsCOMPtr<nsIAlertsService> mBackend;
};
#endif /* nsAlertsService_h__ */

View file

@ -0,0 +1,43 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsAlertsUtils.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsIStringBundle.h"
#include "nsIURI.h"
#include "nsXPIDLString.h"
/* static */
bool
nsAlertsUtils::IsActionablePrincipal(nsIPrincipal* aPrincipal)
{
return aPrincipal &&
!nsContentUtils::IsSystemOrExpandedPrincipal(aPrincipal) &&
!aPrincipal->GetIsNullPrincipal();
}
/* static */
void
nsAlertsUtils::GetSourceHostPort(nsIPrincipal* aPrincipal,
nsAString& aHostPort)
{
if (!IsActionablePrincipal(aPrincipal)) {
return;
}
nsCOMPtr<nsIURI> principalURI;
if (NS_WARN_IF(NS_FAILED(
aPrincipal->GetURI(getter_AddRefs(principalURI))))) {
return;
}
if (!principalURI) {
return;
}
nsAutoCString hostPort;
if (NS_WARN_IF(NS_FAILED(principalURI->GetHostPort(hostPort)))) {
return;
}
CopyUTF8toUTF16(hostPort, aHostPort);
}

View file

@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsAlertsUtils_h
#define nsAlertsUtils_h
#include "nsIPrincipal.h"
#include "nsString.h"
class nsAlertsUtils final
{
private:
nsAlertsUtils() = delete;
public:
/**
* Indicates whether an alert from |aPrincipal| should include the source
* string and action buttons. Returns false if |aPrincipal| is |nullptr|, or
* a system, expanded, or null principal.
*/
static bool
IsActionablePrincipal(nsIPrincipal* aPrincipal);
/**
* Sets |aHostPort| to the host and port from |aPrincipal|'s URI, or an
* empty string if |aPrincipal| is not actionable.
*/
static void
GetSourceHostPort(nsIPrincipal* aPrincipal, nsAString& aHostPort);
};
#endif /* nsAlertsUtils_h */

View file

@ -0,0 +1,259 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
#include "nsIObserver.idl"
interface imgIRequest;
interface nsICancelable;
interface nsIPrincipal;
interface nsIURI;
%{C++
#define ALERT_NOTIFICATION_CONTRACTID "@mozilla.org/alert-notification;1"
%}
[scriptable, uuid(a71a637d-de1d-47c6-a8d2-c60b2596f471)]
interface nsIAlertNotificationImageListener : nsISupports
{
/**
* Called when the image finishes loading.
*
* @param aUserData An opaque parameter passed to |loadImage|.
* @param aRequest The image request.
*/
void onImageReady(in nsISupports aUserData, in imgIRequest aRequest);
/**
* Called if the alert doesn't have an image, or if the image request times
* out or fails.
*
* @param aUserData An opaque parameter passed to |loadImage|.
*/
void onImageMissing(in nsISupports aUserData);
};
[scriptable, uuid(cf2e4cb6-4b8f-4eca-aea9-d51a8f9f7a50)]
interface nsIAlertNotification : nsISupports
{
/** Initializes an alert notification. */
void init([optional] in AString aName,
[optional] in AString aImageURL,
[optional] in AString aTitle,
[optional] in AString aText,
[optional] in boolean aTextClickable,
[optional] in AString aCookie,
[optional] in AString aDir,
[optional] in AString aLang,
[optional] in AString aData,
[optional] in nsIPrincipal aPrincipal,
[optional] in boolean aInPrivateBrowsing,
[optional] in boolean aRequireInteraction);
/**
* The name of the notification. On Android, the name is hashed and used as
* a notification ID. Notifications will replace previous notifications with
* the same name.
*/
readonly attribute AString name;
/**
* A URL identifying the image to put in the alert. The OS X backend limits
* the amount of time it will wait for the image to load to six seconds. After
* that time, the alert will show without an image.
*/
readonly attribute AString imageURL;
/** The title for the alert. */
readonly attribute AString title;
/** The contents of the alert. */
readonly attribute AString text;
/**
* Controls the click behavior. If true, the alert listener will be notified
* when the user clicks on the alert.
*/
readonly attribute boolean textClickable;
/**
* An opaque cookie that will be passed to the alert listener for each
* callback.
*/
readonly attribute AString cookie;
/**
* Bidi override for the title and contents. Valid values are "auto", "ltr",
* or "rtl". Ignored if the backend doesn't support localization.
*/
readonly attribute AString dir;
/**
* Language of the title and text. Ignored if the backend doesn't support
* localization.
*/
readonly attribute AString lang;
/**
* A Base64-encoded structured clone buffer containing data associated with
* this alert. Only used for web notifications. Chrome callers should use a
* cookie instead.
*/
readonly attribute AString data;
/**
* The principal of the page that created the alert. Used for IPC security
* checks, and to determine whether the alert is actionable.
*/
readonly attribute nsIPrincipal principal;
/**
* The URI of the page that created the alert. |null| if the alert is not
* actionable.
*/
readonly attribute nsIURI URI;
/**
* Controls the image loading behavior. If true, the image request will be
* loaded anonymously (without cookies or authorization tokens).
*/
readonly attribute boolean inPrivateBrowsing;
/**
* Indicates that the notification should remain readily available until
* the user activates or dismisses the notification.
*/
readonly attribute boolean requireInteraction;
/**
* Indicates whether this alert should show the source string and action
* buttons. False for system alerts (which can omit the principal), or
* expanded, system, and null principals.
*/
readonly attribute boolean actionable;
/**
* The host and port of the originating page, or an empty string if the alert
* is not actionable.
*/
readonly attribute AString source;
/**
* Loads the image associated with this alert.
*
* @param aTimeout The number of milliseconds to wait before cancelling the
* image request. If zero, there is no timeout.
* @param aListener An |nsIAlertNotificationImageListener| implementation,
* notified when the image loads. The listener is kept alive
* until the request completes.
* @param aUserData An opaque parameter passed to the listener's methods.
* Not used by the libnotify backend, but the OS X backend
* passes the pending notification.
*/
nsICancelable loadImage(in unsigned long aTimeout,
in nsIAlertNotificationImageListener aListener,
[optional] in nsISupports aUserData);
};
[scriptable, uuid(f7a36392-d98b-4141-a7d7-4e46642684e3)]
interface nsIAlertsService : nsISupports
{
void showPersistentNotification(in AString aPersistentData,
in nsIAlertNotification aAlert,
[optional] in nsIObserver aAlertListener);
void showAlert(in nsIAlertNotification aAlert,
[optional] in nsIObserver aAlertListener);
/**
* Initializes and shows an |nsIAlertNotification| with the given parameters.
*
* @param aAlertListener Used for callbacks. May be null if the caller
* doesn't care about callbacks.
* @see nsIAlertNotification for descriptions of all other parameters.
* @throws NS_ERROR_NOT_AVAILABLE If the notification cannot be displayed.
*
* The following arguments will be passed to the alertListener's observe()
* method:
* subject - null
* topic - "alertfinished" when the alert goes away
* "alertdisablecallback" when alerts should be disabled for the principal
* "alertsettingscallback" when alert settings should be opened
* "alertclickcallback" when the text is clicked
* "alertshow" when the alert is shown
* data - the value of the cookie parameter passed to showAlertNotification.
*
* @note Depending on current circumstances (if the user's in a fullscreen
* application, for instance), the alert might not be displayed at all.
* In that case, if an alert listener is passed in it will receive the
* "alertfinished" notification immediately.
*/
void showAlertNotification(in AString aImageURL,
in AString aTitle,
in AString aText,
[optional] in boolean aTextClickable,
[optional] in AString aCookie,
[optional] in nsIObserver aAlertListener,
[optional] in AString aName,
[optional] in AString aDir,
[optional] in AString aLang,
[optional] in AString aData,
[optional] in nsIPrincipal aPrincipal,
[optional] in boolean aInPrivateBrowsing,
[optional] in boolean aRequireInteraction);
/**
* Close alerts created by the service.
*
* @param aName The name of the notification to close. If no name
* is provided then only a notification created with
* no name (if any) will be closed.
*/
void closeAlert([optional] in AString aName,
[optional] in nsIPrincipal aPrincipal);
};
[scriptable, uuid(c5d63e3a-259d-45a8-b964-8377967cb4d2)]
interface nsIAlertsDoNotDisturb : nsISupports
{
/**
* Toggles a manual Do Not Disturb mode for the service to reduce the amount
* of disruption that alerts cause the user.
* This may mean only displaying them in a notification tray/center or not
* displaying them at all. If a system backend already supports a similar
* feature controlled by the user, enabling this may not have any impact on
* code to show an alert. e.g. on OS X, the system will take care not
* disrupting a user if we simply create a notification like usual.
*/
attribute bool manualDoNotDisturb;
};
[scriptable, uuid(fc6d7f0a-0cf6-4268-8c71-ab640842b9b1)]
interface nsIAlertsIconData : nsISupports
{
/**
* Shows an alert with an icon. Web notifications use the favicon of the
* page that created the alert. If the favicon is not in the Places database,
* |aIconSize| will be zero.
*/
void showAlertWithIconData(in nsIAlertNotification aAlert,
[optional] in nsIObserver aAlertListener,
[optional] in uint32_t aIconSize,
[const, array, size_is(aIconSize)] in uint8_t
aIconData);
};
[scriptable, uuid(f3c82915-bf60-41ea-91ce-6c46b22e381a)]
interface nsIAlertsIconURI : nsISupports
{
/**
* Shows an alert with an icon URI. Web notifications use |moz-anno:|
* URIs to reference favicons from Places. If the page doesn't have a
* favicon, |aIconURI| will be |null|.
*/
void showAlertWithIconURI(in nsIAlertNotification aAlert,
[optional] in nsIObserver aAlertListener,
[optional] in nsIURI aIconURI);
};

View file

@ -0,0 +1,398 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsXULAlerts.h"
#include "nsArray.h"
#include "nsComponentManagerUtils.h"
#include "nsCOMPtr.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/dom/Notification.h"
#include "mozilla/Unused.h"
#include "nsIServiceManager.h"
#include "nsISupportsPrimitives.h"
#include "nsPIDOMWindow.h"
#include "nsIWindowWatcher.h"
using namespace mozilla;
using mozilla::dom::NotificationTelemetryService;
#define ALERT_CHROME_URL "chrome://global/content/alerts/alert.xul"
namespace {
StaticRefPtr<nsXULAlerts> gXULAlerts;
} // anonymous namespace
NS_IMPL_CYCLE_COLLECTION(nsXULAlertObserver, mAlertWindow)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULAlertObserver)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULAlertObserver)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULAlertObserver)
NS_IMETHODIMP
nsXULAlertObserver::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
if (!strcmp("alertfinished", aTopic)) {
mozIDOMWindowProxy* currentAlert = mXULAlerts->mNamedWindows.GetWeak(mAlertName);
// The window in mNamedWindows might be a replacement, thus it should only
// be removed if it is the same window that is associated with this listener.
if (currentAlert == mAlertWindow) {
mXULAlerts->mNamedWindows.Remove(mAlertName);
if (mIsPersistent) {
mXULAlerts->PersistentAlertFinished();
}
}
}
nsresult rv = NS_OK;
if (mObserver) {
rv = mObserver->Observe(aSubject, aTopic, aData);
}
return rv;
}
// We don't cycle collect nsXULAlerts since gXULAlerts will keep the instance
// alive till shutdown anyway.
NS_IMPL_ISUPPORTS(nsXULAlerts, nsIAlertsService, nsIAlertsDoNotDisturb, nsIAlertsIconURI)
/* static */ already_AddRefed<nsXULAlerts>
nsXULAlerts::GetInstance()
{
// Gecko on Android does not fully support XUL windows.
#ifndef MOZ_WIDGET_ANDROID
if (!gXULAlerts) {
gXULAlerts = new nsXULAlerts();
ClearOnShutdown(&gXULAlerts);
}
#endif // MOZ_WIDGET_ANDROID
RefPtr<nsXULAlerts> instance = gXULAlerts.get();
return instance.forget();
}
void
nsXULAlerts::PersistentAlertFinished()
{
MOZ_ASSERT(mPersistentAlertCount);
mPersistentAlertCount--;
// Show next pending persistent alert if any.
if (!mPendingPersistentAlerts.IsEmpty()) {
ShowAlertWithIconURI(mPendingPersistentAlerts[0].mAlert,
mPendingPersistentAlerts[0].mListener,
nullptr);
mPendingPersistentAlerts.RemoveElementAt(0);
}
}
NS_IMETHODIMP
nsXULAlerts::ShowAlertNotification(const nsAString& aImageUrl, const nsAString& aAlertTitle,
const nsAString& aAlertText, bool aAlertTextClickable,
const nsAString& aAlertCookie, nsIObserver* aAlertListener,
const nsAString& aAlertName, const nsAString& aBidi,
const nsAString& aLang, const nsAString& aData,
nsIPrincipal* aPrincipal, bool aInPrivateBrowsing,
bool aRequireInteraction)
{
nsCOMPtr<nsIAlertNotification> alert =
do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle,
aAlertText, aAlertTextClickable,
aAlertCookie, aBidi, aLang, aData,
aPrincipal, aInPrivateBrowsing,
aRequireInteraction);
NS_ENSURE_SUCCESS(rv, rv);
return ShowAlert(alert, aAlertListener);
}
NS_IMETHODIMP
nsXULAlerts::ShowPersistentNotification(const nsAString& aPersistentData,
nsIAlertNotification* aAlert,
nsIObserver* aAlertListener)
{
return ShowAlert(aAlert, aAlertListener);
}
NS_IMETHODIMP
nsXULAlerts::ShowAlert(nsIAlertNotification* aAlert,
nsIObserver* aAlertListener)
{
nsAutoString name;
nsresult rv = aAlert->GetName(name);
NS_ENSURE_SUCCESS(rv, rv);
// If there is a pending alert with the same name in the list of
// pending alerts, replace it.
if (!mPendingPersistentAlerts.IsEmpty()) {
for (uint32_t i = 0; i < mPendingPersistentAlerts.Length(); i++) {
nsAutoString pendingAlertName;
nsCOMPtr<nsIAlertNotification> pendingAlert = mPendingPersistentAlerts[i].mAlert;
rv = pendingAlert->GetName(pendingAlertName);
NS_ENSURE_SUCCESS(rv, rv);
if (pendingAlertName.Equals(name)) {
nsAutoString cookie;
rv = pendingAlert->GetCookie(cookie);
NS_ENSURE_SUCCESS(rv, rv);
if (mPendingPersistentAlerts[i].mListener) {
rv = mPendingPersistentAlerts[i].mListener->Observe(nullptr, "alertfinished", cookie.get());
NS_ENSURE_SUCCESS(rv, rv);
}
mPendingPersistentAlerts[i].Init(aAlert, aAlertListener);
return NS_OK;
}
}
}
bool requireInteraction;
rv = aAlert->GetRequireInteraction(&requireInteraction);
NS_ENSURE_SUCCESS(rv, rv);
if (requireInteraction &&
!mNamedWindows.Contains(name) &&
static_cast<int32_t>(mPersistentAlertCount) >=
Preferences::GetInt("dom.webnotifications.requireinteraction.count", 0)) {
PendingAlert* pa = mPendingPersistentAlerts.AppendElement();
pa->Init(aAlert, aAlertListener);
return NS_OK;
} else {
return ShowAlertWithIconURI(aAlert, aAlertListener, nullptr);
}
}
NS_IMETHODIMP
nsXULAlerts::ShowAlertWithIconURI(nsIAlertNotification* aAlert,
nsIObserver* aAlertListener,
nsIURI* aIconURI)
{
bool inPrivateBrowsing;
nsresult rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString cookie;
rv = aAlert->GetCookie(cookie);
NS_ENSURE_SUCCESS(rv, rv);
if (mDoNotDisturb) {
if (!inPrivateBrowsing) {
RefPtr<NotificationTelemetryService> telemetry =
NotificationTelemetryService::GetInstance();
if (telemetry) {
// Record the number of unique senders for XUL alerts. The OS X and
// libnotify backends will fire `alertshow` even if "do not disturb"
// is enabled. In that case, `NotificationObserver` will record the
// sender.
nsCOMPtr<nsIPrincipal> principal;
if (NS_SUCCEEDED(aAlert->GetPrincipal(getter_AddRefs(principal)))) {
Unused << NS_WARN_IF(NS_FAILED(telemetry->RecordSender(principal)));
}
}
}
if (aAlertListener)
aAlertListener->Observe(nullptr, "alertfinished", cookie.get());
return NS_OK;
}
nsAutoString name;
rv = aAlert->GetName(name);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString imageUrl;
rv = aAlert->GetImageURL(imageUrl);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString title;
rv = aAlert->GetTitle(title);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString text;
rv = aAlert->GetText(text);
NS_ENSURE_SUCCESS(rv, rv);
bool textClickable;
rv = aAlert->GetTextClickable(&textClickable);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString bidi;
rv = aAlert->GetDir(bidi);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString lang;
rv = aAlert->GetLang(lang);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString source;
rv = aAlert->GetSource(source);
NS_ENSURE_SUCCESS(rv, rv);
bool requireInteraction;
rv = aAlert->GetRequireInteraction(&requireInteraction);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
nsCOMPtr<nsIMutableArray> argsArray = nsArray::Create();
// create scriptable versions of our strings that we can store in our nsIMutableArray....
nsCOMPtr<nsISupportsString> scriptableImageUrl (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
NS_ENSURE_TRUE(scriptableImageUrl, NS_ERROR_FAILURE);
scriptableImageUrl->SetData(imageUrl);
rv = argsArray->AppendElement(scriptableImageUrl, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupportsString> scriptableAlertTitle (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
NS_ENSURE_TRUE(scriptableAlertTitle, NS_ERROR_FAILURE);
scriptableAlertTitle->SetData(title);
rv = argsArray->AppendElement(scriptableAlertTitle, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupportsString> scriptableAlertText (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
NS_ENSURE_TRUE(scriptableAlertText, NS_ERROR_FAILURE);
scriptableAlertText->SetData(text);
rv = argsArray->AppendElement(scriptableAlertText, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupportsPRBool> scriptableIsClickable (do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID));
NS_ENSURE_TRUE(scriptableIsClickable, NS_ERROR_FAILURE);
scriptableIsClickable->SetData(textClickable);
rv = argsArray->AppendElement(scriptableIsClickable, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupportsString> scriptableAlertCookie (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
NS_ENSURE_TRUE(scriptableAlertCookie, NS_ERROR_FAILURE);
scriptableAlertCookie->SetData(cookie);
rv = argsArray->AppendElement(scriptableAlertCookie, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupportsPRInt32> scriptableOrigin (do_CreateInstance(NS_SUPPORTS_PRINT32_CONTRACTID));
NS_ENSURE_TRUE(scriptableOrigin, NS_ERROR_FAILURE);
int32_t origin =
LookAndFeel::GetInt(LookAndFeel::eIntID_AlertNotificationOrigin);
scriptableOrigin->SetData(origin);
rv = argsArray->AppendElement(scriptableOrigin, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupportsString> scriptableBidi (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
NS_ENSURE_TRUE(scriptableBidi, NS_ERROR_FAILURE);
scriptableBidi->SetData(bidi);
rv = argsArray->AppendElement(scriptableBidi, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupportsString> scriptableLang (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
NS_ENSURE_TRUE(scriptableLang, NS_ERROR_FAILURE);
scriptableLang->SetData(lang);
rv = argsArray->AppendElement(scriptableLang, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupportsPRBool> scriptableRequireInteraction (do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID));
NS_ENSURE_TRUE(scriptableRequireInteraction, NS_ERROR_FAILURE);
scriptableRequireInteraction->SetData(requireInteraction);
rv = argsArray->AppendElement(scriptableRequireInteraction, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
// Alerts with the same name should replace the old alert in the same position.
// Provide the new alert window with a pointer to the replaced window so that
// it may take the same position.
nsCOMPtr<nsISupportsInterfacePointer> replacedWindow = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
NS_ENSURE_TRUE(replacedWindow, NS_ERROR_FAILURE);
mozIDOMWindowProxy* previousAlert = mNamedWindows.GetWeak(name);
replacedWindow->SetData(previousAlert);
replacedWindow->SetDataIID(&NS_GET_IID(mozIDOMWindowProxy));
rv = argsArray->AppendElement(replacedWindow, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
if (requireInteraction) {
mPersistentAlertCount++;
}
// Add an observer (that wraps aAlertListener) to remove the window from
// mNamedWindows when it is closed.
nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<nsXULAlertObserver> alertObserver = new nsXULAlertObserver(this, name, aAlertListener, requireInteraction);
nsCOMPtr<nsISupports> iSupports(do_QueryInterface(alertObserver));
ifptr->SetData(iSupports);
ifptr->SetDataIID(&NS_GET_IID(nsIObserver));
rv = argsArray->AppendElement(ifptr, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
// The source contains the host and port of the site that sent the
// notification. It is empty for system alerts.
nsCOMPtr<nsISupportsString> scriptableAlertSource (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
NS_ENSURE_TRUE(scriptableAlertSource, NS_ERROR_FAILURE);
scriptableAlertSource->SetData(source);
rv = argsArray->AppendElement(scriptableAlertSource, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupportsCString> scriptableIconURL (do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
NS_ENSURE_TRUE(scriptableIconURL, NS_ERROR_FAILURE);
if (aIconURI) {
nsAutoCString iconURL;
rv = aIconURI->GetSpec(iconURL);
NS_ENSURE_SUCCESS(rv, rv);
scriptableIconURL->SetData(iconURL);
}
rv = argsArray->AppendElement(scriptableIconURL, /*weak =*/ false);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIDOMWindowProxy> newWindow;
nsAutoCString features("chrome,dialog=yes,titlebar=no,popup=yes");
if (inPrivateBrowsing) {
features.AppendLiteral(",private");
}
rv = wwatch->OpenWindow(nullptr, ALERT_CHROME_URL, "_blank", features.get(),
argsArray, getter_AddRefs(newWindow));
NS_ENSURE_SUCCESS(rv, rv);
mNamedWindows.Put(name, newWindow);
alertObserver->SetAlertWindow(newWindow);
return NS_OK;
}
NS_IMETHODIMP
nsXULAlerts::SetManualDoNotDisturb(bool aDoNotDisturb)
{
mDoNotDisturb = aDoNotDisturb;
return NS_OK;
}
NS_IMETHODIMP
nsXULAlerts::GetManualDoNotDisturb(bool* aRetVal)
{
*aRetVal = mDoNotDisturb;
return NS_OK;
}
NS_IMETHODIMP
nsXULAlerts::CloseAlert(const nsAString& aAlertName,
nsIPrincipal* aPrincipal)
{
mozIDOMWindowProxy* alert = mNamedWindows.GetWeak(aAlertName);
if (nsCOMPtr<nsPIDOMWindowOuter> domWindow = nsPIDOMWindowOuter::From(alert)) {
domWindow->DispatchCustomEvent(NS_LITERAL_STRING("XULAlertClose"));
}
return NS_OK;
}

View file

@ -0,0 +1,84 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsXULAlerts_h__
#define nsXULAlerts_h__
#include "nsCycleCollectionParticipant.h"
#include "nsDataHashtable.h"
#include "nsHashKeys.h"
#include "nsInterfaceHashtable.h"
#include "mozIDOMWindow.h"
#include "nsIObserver.h"
struct PendingAlert
{
void Init(nsIAlertNotification* aAlert, nsIObserver* aListener)
{
mAlert = aAlert;
mListener = aListener;
}
nsCOMPtr<nsIAlertNotification> mAlert;
nsCOMPtr<nsIObserver> mListener;
};
class nsXULAlerts : public nsIAlertsService,
public nsIAlertsDoNotDisturb,
public nsIAlertsIconURI
{
friend class nsXULAlertObserver;
public:
NS_DECL_NSIALERTSICONURI
NS_DECL_NSIALERTSDONOTDISTURB
NS_DECL_NSIALERTSSERVICE
NS_DECL_ISUPPORTS
nsXULAlerts()
{
}
static already_AddRefed<nsXULAlerts> GetInstance();
protected:
virtual ~nsXULAlerts() {}
void PersistentAlertFinished();
nsInterfaceHashtable<nsStringHashKey, mozIDOMWindowProxy> mNamedWindows;
uint32_t mPersistentAlertCount = 0;
nsTArray<PendingAlert> mPendingPersistentAlerts;
bool mDoNotDisturb = false;
};
/**
* This class wraps observers for alerts and watches
* for the "alertfinished" event in order to release
* the reference on the nsIDOMWindow of the XUL alert.
*/
class nsXULAlertObserver : public nsIObserver {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSIOBSERVER
NS_DECL_CYCLE_COLLECTION_CLASS(nsXULAlertObserver)
nsXULAlertObserver(nsXULAlerts* aXULAlerts, const nsAString& aAlertName,
nsIObserver* aObserver, bool aIsPersistent)
: mXULAlerts(aXULAlerts), mAlertName(aAlertName),
mObserver(aObserver), mIsPersistent(aIsPersistent) {}
void SetAlertWindow(mozIDOMWindowProxy* aWindow) { mAlertWindow = aWindow; }
protected:
virtual ~nsXULAlertObserver() {}
RefPtr<nsXULAlerts> mXULAlerts;
nsString mAlertName;
nsCOMPtr<mozIDOMWindowProxy> mAlertWindow;
nsCOMPtr<nsIObserver> mObserver;
bool mIsPersistent;
};
#endif /* nsXULAlerts_h__ */

View file

@ -0,0 +1,34 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#alertBox[animate] {
animation-duration: 20s;
animation-fill-mode: both;
animation-name: alert-animation;
}
#alertBox[animate]:not([clicked]):not([closing]):hover {
animation-play-state: paused;
}
#alertBox:not([hasOrigin]) > box > #alertTextBox > #alertFooter,
#alertBox:not([hasIcon]) > box > #alertIcon,
#alertImage:not([src]) {
display: none;
}
#alertTitleBox {
-moz-box-pack: center;
-moz-box-align: center;
}
.alertText {
white-space: pre-wrap;
}
@keyframes alert-animation {
to {
visibility: hidden;
}
}

View file

@ -0,0 +1,332 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/Services.jsm");
// Copied from nsILookAndFeel.h, see comments on eMetric_AlertNotificationOrigin
const NS_ALERT_HORIZONTAL = 1;
const NS_ALERT_LEFT = 2;
const NS_ALERT_TOP = 4;
const WINDOW_MARGIN = AppConstants.platform == "win" ? 0 : 10;
const BODY_TEXT_LIMIT = 200;
const WINDOW_SHADOW_SPREAD = AppConstants.platform == "win" ? 10 : 0;
var gOrigin = 0; // Default value: alert from bottom right.
var gReplacedWindow = null;
var gAlertListener = null;
var gAlertTextClickable = false;
var gAlertCookie = "";
var gIsReplaced = false;
var gRequireInteraction = false;
function prefillAlertInfo() {
// unwrap all the args....
// arguments[0] --> the image src url
// arguments[1] --> the alert title
// arguments[2] --> the alert text
// arguments[3] --> is the text clickable?
// arguments[4] --> the alert cookie to be passed back to the listener
// arguments[5] --> the alert origin reported by the look and feel
// arguments[6] --> bidi
// arguments[7] --> lang
// arguments[8] --> requires interaction
// arguments[9] --> replaced alert window (nsIDOMWindow)
// arguments[10] --> an optional callback listener (nsIObserver)
// arguments[11] -> the nsIURI.hostPort of the origin, optional
// arguments[12] -> the alert icon URL, optional
switch (window.arguments.length) {
default:
case 13: {
if (window.arguments[12]) {
let alertBox = document.getElementById("alertBox");
alertBox.setAttribute("hasIcon", true);
let icon = document.getElementById("alertIcon");
icon.src = window.arguments[12];
}
}
case 12: {
if (window.arguments[11]) {
let alertBox = document.getElementById("alertBox");
alertBox.setAttribute("hasOrigin", true);
let hostPort = window.arguments[11];
const ALERT_BUNDLE = Services.strings.createBundle(
"chrome://alerts/locale/alert.properties");
const BRAND_BUNDLE = Services.strings.createBundle(
"chrome://branding/locale/brand.properties");
const BRAND_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");
let label = document.getElementById("alertSourceLabel");
label.setAttribute("value",
ALERT_BUNDLE.formatStringFromName("source.label",
[hostPort],
1));
let doNotDisturbMenuItem = document.getElementById("doNotDisturbMenuItem");
doNotDisturbMenuItem.setAttribute("label",
ALERT_BUNDLE.formatStringFromName("doNotDisturb.label",
[BRAND_NAME],
1));
let disableForOrigin = document.getElementById("disableForOriginMenuItem");
disableForOrigin.setAttribute("label",
ALERT_BUNDLE.formatStringFromName("webActions.disableForOrigin.label",
[hostPort],
1));
let openSettings = document.getElementById("openSettingsMenuItem");
openSettings.setAttribute("label",
ALERT_BUNDLE.GetStringFromName("webActions.settings.label"));
}
}
case 11:
gAlertListener = window.arguments[10];
case 10:
gReplacedWindow = window.arguments[9];
case 9:
gRequireInteraction = window.arguments[8];
case 8:
if (window.arguments[7]) {
document.getElementById("alertTitleLabel").setAttribute("lang", window.arguments[7]);
document.getElementById("alertTextLabel").setAttribute("lang", window.arguments[7]);
}
case 7:
if (window.arguments[6]) {
document.getElementById("alertNotification").style.direction = window.arguments[6];
}
case 6:
gOrigin = window.arguments[5];
case 5:
gAlertCookie = window.arguments[4];
case 4:
gAlertTextClickable = window.arguments[3];
if (gAlertTextClickable) {
document.getElementById("alertNotification").setAttribute("clickable", true);
document.getElementById("alertTextLabel").setAttribute("clickable", true);
}
case 3:
if (window.arguments[2]) {
document.getElementById("alertBox").setAttribute("hasBodyText", true);
let bodyText = window.arguments[2];
let bodyTextLabel = document.getElementById("alertTextLabel");
if (bodyText.length > BODY_TEXT_LIMIT) {
bodyTextLabel.setAttribute("tooltiptext", bodyText);
let ellipsis = "\u2026";
try {
ellipsis = Services.prefs.getComplexValue("intl.ellipsis",
Ci.nsIPrefLocalizedString).data;
} catch (e) { }
// Copied from nsContextMenu.js' formatSearchContextItem().
// If the JS character after our truncation point is a trail surrogate,
// include it in the truncated string to avoid splitting a surrogate pair.
let truncLength = BODY_TEXT_LIMIT;
let truncChar = bodyText[BODY_TEXT_LIMIT].charCodeAt(0);
if (truncChar >= 0xDC00 && truncChar <= 0xDFFF) {
truncLength++;
}
bodyText = bodyText.substring(0, truncLength) +
ellipsis;
}
bodyTextLabel.textContent = bodyText;
}
case 2:
document.getElementById("alertTitleLabel").setAttribute("value", window.arguments[1]);
case 1:
if (window.arguments[0]) {
document.getElementById("alertBox").setAttribute("hasImage", true);
document.getElementById("alertImage").setAttribute("src", window.arguments[0]);
}
case 0:
break;
}
}
function onAlertLoad() {
const ALERT_DURATION_IMMEDIATE = 20000;
let alertTextBox = document.getElementById("alertTextBox");
let alertImageBox = document.getElementById("alertImageBox");
alertImageBox.style.minHeight = alertTextBox.scrollHeight + "px";
sizeToContent();
if (gReplacedWindow && !gReplacedWindow.closed) {
moveWindowToReplace(gReplacedWindow);
gReplacedWindow.gIsReplaced = true;
gReplacedWindow.close();
} else {
moveWindowToEnd();
}
window.addEventListener("XULAlertClose", function() { window.close(); });
// If the require interaction flag is set, prevent auto-closing the notification.
if (!gRequireInteraction) {
if (Services.prefs.getBoolPref("alerts.disableSlidingEffect")) {
setTimeout(function() { window.close(); }, ALERT_DURATION_IMMEDIATE);
} else {
let alertBox = document.getElementById("alertBox");
alertBox.addEventListener("animationend", function hideAlert(event) {
if (event.animationName == "alert-animation" ||
event.animationName == "alert-clicked-animation" ||
event.animationName == "alert-closing-animation") {
alertBox.removeEventListener("animationend", hideAlert, false);
window.close();
}
}, false);
alertBox.setAttribute("animate", true);
}
}
let alertSettings = document.getElementById("alertSettings");
alertSettings.addEventListener("focus", onAlertSettingsFocus);
alertSettings.addEventListener("click", onAlertSettingsClick);
let ev = new CustomEvent("AlertActive", {bubbles: true, cancelable: true});
document.documentElement.dispatchEvent(ev);
if (gAlertListener) {
gAlertListener.observe(null, "alertshow", gAlertCookie);
}
}
function moveWindowToReplace(aReplacedAlert) {
let heightDelta = window.outerHeight - aReplacedAlert.outerHeight;
// Move windows that come after the replaced alert if the height is different.
if (heightDelta != 0) {
let windows = Services.wm.getEnumerator("alert:alert");
while (windows.hasMoreElements()) {
let alertWindow = windows.getNext();
// boolean to determine if the alert window is after the replaced alert.
let alertIsAfter = gOrigin & NS_ALERT_TOP ?
alertWindow.screenY > aReplacedAlert.screenY :
aReplacedAlert.screenY > alertWindow.screenY;
if (alertIsAfter) {
// The new Y position of the window.
let adjustedY = gOrigin & NS_ALERT_TOP ?
alertWindow.screenY + heightDelta :
alertWindow.screenY - heightDelta;
alertWindow.moveTo(alertWindow.screenX, adjustedY);
}
}
}
let adjustedY = gOrigin & NS_ALERT_TOP ? aReplacedAlert.screenY :
aReplacedAlert.screenY - heightDelta;
window.moveTo(aReplacedAlert.screenX, adjustedY);
}
function moveWindowToEnd() {
// Determine position
let x = gOrigin & NS_ALERT_LEFT ? screen.availLeft :
screen.availLeft + screen.availWidth - window.outerWidth;
let y = gOrigin & NS_ALERT_TOP ? screen.availTop :
screen.availTop + screen.availHeight - window.outerHeight;
// Position the window at the end of all alerts.
let windows = Services.wm.getEnumerator("alert:alert");
while (windows.hasMoreElements()) {
let alertWindow = windows.getNext();
if (alertWindow != window) {
if (gOrigin & NS_ALERT_TOP) {
y = Math.max(y, alertWindow.screenY + alertWindow.outerHeight - WINDOW_SHADOW_SPREAD);
} else {
y = Math.min(y, alertWindow.screenY - window.outerHeight + WINDOW_SHADOW_SPREAD);
}
}
}
// Offset the alert by WINDOW_MARGIN pixels from the edge of the screen
y += gOrigin & NS_ALERT_TOP ? WINDOW_MARGIN : -WINDOW_MARGIN;
x += gOrigin & NS_ALERT_LEFT ? WINDOW_MARGIN : -WINDOW_MARGIN;
window.moveTo(x, y);
}
function onAlertBeforeUnload() {
if (!gIsReplaced) {
// Move other alert windows to fill the gap left by closing alert.
let heightDelta = window.outerHeight + WINDOW_MARGIN - WINDOW_SHADOW_SPREAD;
let windows = Services.wm.getEnumerator("alert:alert");
while (windows.hasMoreElements()) {
let alertWindow = windows.getNext();
if (alertWindow != window) {
if (gOrigin & NS_ALERT_TOP) {
if (alertWindow.screenY > window.screenY) {
alertWindow.moveTo(alertWindow.screenX, alertWindow.screenY - heightDelta);
}
} else if (window.screenY > alertWindow.screenY) {
alertWindow.moveTo(alertWindow.screenX, alertWindow.screenY + heightDelta);
}
}
}
}
if (gAlertListener) {
gAlertListener.observe(null, "alertfinished", gAlertCookie);
}
}
function onAlertClick() {
if (gAlertListener && gAlertTextClickable) {
gAlertListener.observe(null, "alertclickcallback", gAlertCookie);
}
let alertBox = document.getElementById("alertBox");
if (alertBox.getAttribute("animate") == "true") {
// Closed when the animation ends.
alertBox.setAttribute("clicked", "true");
} else {
window.close();
}
}
function doNotDisturb() {
const alertService = Cc["@mozilla.org/alerts-service;1"]
.getService(Ci.nsIAlertsService)
.QueryInterface(Ci.nsIAlertsDoNotDisturb);
alertService.manualDoNotDisturb = true;
Services.telemetry.getHistogramById("WEB_NOTIFICATION_MENU")
.add(0);
onAlertClose();
}
function disableForOrigin() {
gAlertListener.observe(null, "alertdisablecallback", gAlertCookie);
onAlertClose();
}
function onAlertSettingsFocus(event) {
event.target.removeAttribute("focusedViaMouse");
}
function onAlertSettingsClick(event) {
// XXXjaws Hack used to remove the focus-ring only
// from mouse interaction, but focus-ring drawing
// should only be enabled when interacting via keyboard.
event.target.setAttribute("focusedViaMouse", true);
event.stopPropagation();
}
function openSettings() {
gAlertListener.observe(null, "alertsettingscallback", gAlertCookie);
onAlertClose();
}
function onAlertClose() {
let alertBox = document.getElementById("alertBox");
if (alertBox.getAttribute("animate") == "true") {
// Closed when the animation ends.
alertBox.setAttribute("closing", "true");
} else {
window.close();
}
}

View file

@ -0,0 +1,67 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE window [
<!ENTITY % alertDTD SYSTEM "chrome://alerts/locale/alert.dtd">
%alertDTD;
]>
<?xml-stylesheet href="chrome://global/content/alerts/alert.css" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/alerts/alert.css" type="text/css"?>
<window id="alertNotification"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
windowtype="alert:alert"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
role="alert"
pack="start"
onload="onAlertLoad();"
onclick="onAlertClick();"
onbeforeunload="onAlertBeforeUnload();">
<script type="application/javascript" src="chrome://global/content/alerts/alert.js"/>
<vbox id="alertBox" class="alertBox">
<box id="alertTitleBox">
<image id="alertIcon"/>
<label id="alertTitleLabel" class="alertTitle plain" crop="end"/>
<vbox class="alertCloseBox">
<toolbarbutton class="alertCloseButton close-icon"
tooltiptext="&closeAlert.tooltip;"
onclick="event.stopPropagation();"
oncommand="onAlertClose();"/>
</vbox>
</box>
<box>
<hbox id="alertImageBox" class="alertImageBox" align="center" pack="center">
<image id="alertImage"/>
</hbox>
<vbox id="alertTextBox" class="alertTextBox">
<label id="alertTextLabel" class="alertText plain"/>
<spacer flex="1"/>
<box id="alertFooter">
<label id="alertSourceLabel" class="alertSource plain"/>
<button type="menu" id="alertSettings" tooltiptext="&settings.label;">
<menupopup position="after_end">
<menuitem id="doNotDisturbMenuItem"
oncommand="doNotDisturb();"/>
<menuseparator/>
<menuitem id="disableForOriginMenuItem"
oncommand="disableForOrigin();"/>
<menuitem id="openSettingsMenuItem"
oncommand="openSettings();"/>
</menupopup>
</button>
</box>
</vbox>
</box>
</vbox>
<!-- This method is called inline because we want to make sure we establish the width
and height of the alert before we fire the onload handler. -->
<script type="application/javascript">prefillAlertInfo();</script>
</window>

View file

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"../../../../testing/mochitest/mochitest.eslintrc.js"
]
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,82 @@
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr, Constructor: CC } = Components;
Cu.import("resource://gre/modules/Timer.jsm");
const LocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile",
"initWithPath");
const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
"nsIFileInputStream", "init");
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream", "setInputStream");
function handleRequest(request, response) {
let params = parseQueryString(request.queryString);
response.setStatusLine(request.httpVersion, 200, "OK");
// Compare and increment a cookie for this request. This is used to test
// private browsing mode; the cookie should not be set if the image is
// loaded anonymously.
if (params.has("c")) {
let expectedValue = parseInt(params.get("c"), 10);
let actualValue = !request.hasHeader("Cookie") ? 0 :
parseInt(request.getHeader("Cookie")
.replace(/^counter=(\d+)/, "$1"), 10);
if (actualValue != expectedValue) {
response.setStatusLine(request.httpVersion, 400, "Wrong counter value");
return;
}
response.setHeader("Set-Cookie", `counter=${expectedValue + 1}`, false);
}
// Wait to send the image if a timeout is given.
let timeout = parseInt(params.get("t"), 10);
if (timeout > 0) {
response.processAsync();
setTimeout(() => {
respond(params, request, response);
response.finish();
}, timeout * 1000);
return;
}
respond(params, request, response);
}
function parseQueryString(queryString) {
return queryString.split("&").reduce((params, param) => {
let [key, value] = param.split("=", 2);
params.set(key, value);
return params;
}, new Map());
}
function respond(params, request, response) {
if (params.has("s")) {
let statusCode = parseInt(params.get("s"), 10);
response.setStatusLine(request.httpVersion, statusCode, "Custom status");
return;
}
var filename = params.get("f");
writeFile(filename, response);
}
function writeFile(name, response) {
var file = new LocalFile(getState("__LOCATION__")).parent;
file.append(name);
let mimeType = Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
.getService(Ci.nsIMIMEService)
.getTypeFromFile(file);
let fileStream = new FileInputStream(file, 1, 0, false);
let binaryStream = new BinaryInputStream(fileStream);
response.setHeader("Content-Type", mimeType, false);
response.bodyOutputStream.writeFrom(binaryStream, binaryStream.available());
binaryStream.close();
fileStream.close();
}

View file

@ -0,0 +1,16 @@
[DEFAULT]
support-files =
image.gif
image.png
image_server.sjs
# Synchronous tests like test_alerts.html must come before
# asynchronous tests like test_alerts_noobserve.html!
[test_alerts.html]
skip-if = toolkit == 'android'
[test_alerts_noobserve.html]
[test_alerts_requireinteraction.html]
[test_image.html]
[test_multiple_alerts.html]
[test_principal.html]
skip-if = toolkit == 'android'

View file

@ -0,0 +1,89 @@
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<title>Test for Alerts Service</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<br>Alerts service, with observer "synchronous" case.
<br>
<br>Did a notification appear anywhere?
<br>If so, the test will finish once the notification disappears.
<pre id="test">
<script class="testbody" type="text/javascript">
var observer = {
alertShow: false,
observe: function (aSubject, aTopic, aData) {
is(aData, "foobarcookie", "Checking whether the alert cookie was passed correctly");
if (aTopic == "alertclickcallback") {
todo(false, "Did someone click the notification while running mochitests? (Please don't.)");
} else if (aTopic == "alertshow") {
ok(!this.alertShow, "Alert should not be shown more than once");
this.alertShow = true;
} else {
is(aTopic, "alertfinished", "Checking the topic for a finished notification");
SimpleTest.finish();
}
}
};
function runTest() {
const Cc = SpecialPowers.Cc;
const Ci = SpecialPowers.Ci;
if (!("@mozilla.org/alerts-service;1" in Cc)) {
todo(false, "Alerts service does not exist in this application");
return;
}
ok(true, "Alerts service exists in this application");
var notifier;
try {
notifier = Cc["@mozilla.org/alerts-service;1"].
getService(Ci.nsIAlertsService);
ok(true, "Alerts service is available");
} catch (ex) {
todo(false,
"Alerts service is not available.", ex);
return;
}
try {
var alertName = "fiorello";
SimpleTest.waitForExplicitFinish();
notifier.showAlertNotification(null, "Notification test",
"Surprise! I'm here to test notifications!",
false, "foobarcookie", observer, alertName);
ok(true, "showAlertNotification() succeeded. Waiting for notification...");
if ("@mozilla.org/system-alerts-service;1" in Cc) {
// Notifications are native on OS X 10.8 and later, as well as GNOME
// Shell with libnotify (bug 1236036). These notifications persist in the
// Notification Center, and only fire the `alertfinished` event when
// closed. For platforms where native notifications may be used, we need
// to close explicitly to avoid a hang. This also works for XUL
// notifications when running this test on OS X < 10.8, or a window
// manager like Ubuntu Unity with incomplete libnotify support.
notifier.closeAlert(alertName);
}
} catch (ex) {
todo(false, "showAlertNotification() failed.", ex);
SimpleTest.finish();
}
}
runTest();
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,96 @@
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
<title>Test for Alerts Service</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<br>Alerts service, without observer "asynchronous" case.
<br>
<br>A notification should soon appear somewhere.
<br>If there has been no crash when the notification (later) disappears, assume all is good.
<pre id="test">
<script class="testbody" type="text/javascript">
const Cc = SpecialPowers.Cc;
const Ci = SpecialPowers.Ci;
const chromeScript = SpecialPowers.loadChromeScript(_ => {
const { utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
function anyXULAlertsVisible() {
var windows = Services.wm.getEnumerator("alert:alert");
return windows.hasMoreElements();
}
addMessageListener("anyXULAlertsVisible", anyXULAlertsVisible);
addMessageListener("waitForAlerts", function waitForAlerts() {
if (anyXULAlertsVisible()) {
setTimeout(waitForAlerts, 1000);
} else {
sendAsyncMessage("waitedForAlerts");
}
});
});
function waitForAlertsThenFinish() {
chromeScript.addMessageListener("waitedForAlerts", function waitedForAlerts() {
chromeScript.removeMessageListener("waitedForAlerts", waitedForAlerts);
ok(true, "Alert disappeared.");
SimpleTest.finish();
});
chromeScript.sendAsyncMessage("waitForAlerts");
}
function runTest() {
if (!("@mozilla.org/alerts-service;1" in Cc)) {
todo(false, "Alerts service does not exist in this application");
} else {
ok(true, "Alerts service exists in this application");
var notifier;
try {
notifier = Cc["@mozilla.org/alerts-service;1"].
getService(Ci.nsIAlertsService);
ok(true, "Alerts service is available");
} catch (ex) {
todo(false, "Alerts service is not available.", ex);
}
if (notifier) {
try {
notifier.showAlertNotification(null, "Notification test",
"This notification has no observer");
ok(true, "showAlertNotification() succeeded");
} catch (ex) {
todo(false, "showAlertNotification() failed.", ex);
}
}
}
}
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("untriaged");
// sendSyncMessage returns an array of arrays: the outer array is from the
// message manager, and the inner array is from the chrome script's listeners.
// See the comment in test_SpecialPowersLoadChromeScript.html.
var [[alertsVisible]] = chromeScript.sendSyncMessage("anyXULAlertsVisible");
ok(!alertsVisible, "Alerts should not be present at the start of the test.");
runTest();
setTimeout(waitForAlertsThenFinish, 1000);
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,168 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for alerts with requireInteraction</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
const Cc = SpecialPowers.Cc;
const Ci = SpecialPowers.Ci;
const chromeScript = SpecialPowers.loadChromeScript(_ => {
const { utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
addMessageListener("waitForXULAlert", function() {
var timer = setTimeout(function() {
Services.ww.unregisterNotification(windowObserver);
sendAsyncMessage("waitForXULAlert", false);
}, 2000);
var windowObserver = function(aSubject, aTopic, aData) {
if (aTopic != "domwindowopened") {
return;
}
var win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad);
let windowType = win.document.documentElement.getAttribute("windowtype");
if (windowType == "alert:alert") {
clearTimeout(timer);
Services.ww.unregisterNotification(windowObserver);
sendAsyncMessage("waitForXULAlert", true);
}
});
};
Services.ww.registerNotification(windowObserver);
});
});
var cookie = 0;
function promiseCreateXULAlert(alertService, listener, name) {
return new Promise(resolve => {
chromeScript.addMessageListener("waitForXULAlert", function waitedForAlert(result) {
chromeScript.removeMessageListener("waitForXULAlert", waitedForAlert);
resolve(result);
});
chromeScript.sendAsyncMessage("waitForXULAlert");
alertService.showAlertNotification(null, "title", "body",
true, cookie++, listener, name, null, null, null,
null, false, true);
});
}
add_task(function* test_require_interaction() {
if (!("@mozilla.org/alerts-service;1" in Cc)) {
todo(false, "Alerts service does not exist in this application.");
return;
}
ok(true, "Alerts service exists in this application.");
var alertService;
try {
alertService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
ok(true, "Alerts service is available.");
} catch (ex) {
todo(false, "Alerts service is not available.");
return;
}
yield SpecialPowers.pushPrefEnv({"set": [
[ "dom.webnotifications.requireinteraction.enabled", true ],
[ "dom.webnotifications.requireinteraction.count", 2 ]
]});
var expectedSequence = [
"first show",
"second show",
"second finished",
"second replacement show",
"third finished",
"first finished",
"third replacement show",
"second replacement finished",
"third replacement finished"
];
var actualSequence = [];
function createAlertListener(name, showCallback, finishCallback) {
return (subject, topic, data) => {
if (topic == "alertshow") {
actualSequence.push(name + " show");
if (showCallback) {
showCallback();
}
} else if (topic == "alertfinished") {
actualSequence.push(name + " finished");
if (finishCallback) {
finishCallback();
}
}
}
}
var xulAlertCreated = yield promiseCreateXULAlert(alertService,
createAlertListener("first"), "first");
if (!xulAlertCreated) {
ok(true, "Platform does not use XUL alerts.");
alertService.closeAlert("first");
return;
}
xulAlertCreated = yield promiseCreateXULAlert(alertService,
createAlertListener("second"), "second");
ok(xulAlertCreated, "Create XUL alert");
// Replace second alert
xulAlertCreated = yield promiseCreateXULAlert(alertService,
createAlertListener("second replacement"), "second");
ok(xulAlertCreated, "Create XUL alert");
var testFinishResolve;
var testFinishPromise = new Promise((resolve) => { testFinishResolve = resolve; });
xulAlertCreated = yield promiseCreateXULAlert(alertService,
createAlertListener("third"), "third"),
ok(!xulAlertCreated, "XUL alert should not be visible");
// Replace the not-yet-visible third alert.
xulAlertCreated = yield promiseCreateXULAlert(alertService,
createAlertListener("third replacement",
function showCallback() {
alertService.closeAlert("second");
alertService.closeAlert("third");
},
function finishCallback() {
// Check actual sequence of alert events compared to expected sequence.
for (var i = 0; i < actualSequence.length; i++) {
is(actualSequence[i], expectedSequence[i],
"Alert callback at index " + i + " should be in expected order.");
}
testFinishResolve();
}), "third");
ok(!xulAlertCreated, "XUL alert should not be visible");
alertService.closeAlert("first");
yield testFinishPromise;
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,118 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Bug 1233086</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="text/javascript">
const Cc = SpecialPowers.Cc;
const Ci = SpecialPowers.Ci;
const Services = SpecialPowers.Services;
const imageServerURL = "http://mochi.test:8888/tests/toolkit/components/alerts/test/image_server.sjs";
function makeAlert(...params) {
var alert = Cc["@mozilla.org/alert-notification;1"]
.createInstance(Ci.nsIAlertNotification);
alert.init(...params);
return alert;
}
function promiseImage(alert, timeout = 0, userData = null) {
return new Promise(resolve => {
var isDone = false;
function done(value) {
ok(!isDone, "Should call the image listener once");
isDone = true;
resolve(value);
}
alert.loadImage(timeout, SpecialPowers.wrapCallbackObject({
onImageReady(aUserData, aRequest) {
done([true, aRequest, aUserData]);
},
onImageMissing(aUserData) {
done([false, aUserData]);
},
}), SpecialPowers.wrap(userData));
});
}
add_task(function* testContext() {
var inUserData = Cc["@mozilla.org/supports-PRInt64;1"]
.createInstance(Ci.nsISupportsPRInt64);
inUserData.data = 123;
var alert = makeAlert(null, imageServerURL + "?f=image.png");
var [ready, , userData] = yield promiseImage(alert, 0, inUserData);
ok(ready, "Should load requested image");
is(userData.QueryInterface(Ci.nsISupportsPRInt64).data, 123,
"Should pass user data for loaded image");
alert = makeAlert(null, imageServerURL + "?s=404");
[ready, userData] = yield promiseImage(alert, 0, inUserData);
ok(!ready, "Should not load missing image");
is(userData.QueryInterface(Ci.nsISupportsPRInt64).data, 123,
"Should pass user data for missing image");
});
add_task(function* testTimeout() {
var alert = makeAlert(null, imageServerURL + "?f=image.png&t=3");
var [ready] = yield promiseImage(alert, 1000);
ok(!ready, "Should cancel request if timeout fires");
[ready, request] = yield promiseImage(alert, 45000);
ok(ready, "Should load image if request finishes before timeout");
});
add_task(function* testAnimatedGIF() {
var alert = makeAlert(null, imageServerURL + "?f=image.gif");
var [ready, request] = yield promiseImage(alert);
ok(ready, "Should load first animated GIF frame");
is(request.mimeType, "image/gif", "Should report correct GIF MIME type");
is(request.image.width, 256, "GIF width should be 256px");
is(request.image.height, 256, "GIF height should be 256px");
});
add_task(function* testCancel() {
var alert = makeAlert(null, imageServerURL + "?f=image.gif&t=180");
yield new Promise((resolve, reject) => {
var request = alert.loadImage(0, SpecialPowers.wrapCallbackObject({
onImageReady() {
reject(new Error("Should not load cancelled request"));
},
onImageMissing() {
resolve();
},
}), null);
request.cancel(SpecialPowers.Cr.NS_BINDING_ABORTED);
});
});
add_task(function* testMixedContent() {
// Loading principal is HTTPS; image URL is HTTP.
var origin = "https://mochi.test:8888";
var principal = Services.scriptSecurityManager
.createCodebasePrincipalFromOrigin(origin);
var alert = makeAlert(null, imageServerURL + "?f=image.png",
null, null, false, null, null, null,
null, principal);
var [ready, request] = yield promiseImage(alert);
ok(ready, "Should load cross-protocol image");
is(request.mimeType, "image/png", "Should report correct MIME type");
is(request.image.width, 32, "Width should be 32px");
is(request.image.height, 32, "Height should be 32px");
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,103 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for multiple alerts</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
const Cc = SpecialPowers.Cc;
const Ci = SpecialPowers.Ci;
const chromeScript = SpecialPowers.loadChromeScript(_ => {
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/Timer.jsm");
const alertService = Components.classes["@mozilla.org/alerts-service;1"]
.getService(Components.interfaces.nsIAlertsService);
addMessageListener("waitForPosition", function() {
var timer = setTimeout(function() {
Services.ww.unregisterNotification(windowObserver);
sendAsyncMessage("waitedForPosition", null);
}, 2000);
var windowObserver = function(aSubject, aTopic, aData) {
if (aTopic != "domwindowopened") {
return;
}
// Alerts are implemented using XUL.
clearTimeout(timer);
Services.ww.unregisterNotification(windowObserver);
var win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
win.addEventListener("pageshow", function onPageShow() {
win.removeEventListener("pageshow", onPageShow, false);
var x = win.screenX;
var y = win.screenY;
win.addEventListener("pagehide", function onPageHide() {
win.removeEventListener("pagehide", onPageHide, false);
sendAsyncMessage("waitedForPosition", { x, y });
}, false);
alertService.closeAlert();
}, false);
};
Services.ww.registerNotification(windowObserver);
});
});
function promiseAlertPosition(alertService) {
return new Promise(resolve => {
chromeScript.addMessageListener("waitedForPosition", function waitedForPosition(result) {
chromeScript.removeMessageListener("waitedForPosition", waitedForPosition);
resolve(result);
});
chromeScript.sendAsyncMessage("waitForPosition");
alertService.showAlertNotification(null, "title", "body");
ok(true, "Alert shown.");
});
}
add_task(function* test_multiple_alerts() {
if (!("@mozilla.org/alerts-service;1" in Cc)) {
todo(false, "Alerts service does not exist in this application.");
return;
}
ok(true, "Alerts service exists in this application.");
var alertService;
try {
alertService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
ok(true, "Alerts service is available.");
} catch (ex) {
todo(false, "Alerts service is not available.");
return;
}
var firstAlertPosition = yield promiseAlertPosition(alertService);
if (!firstAlertPosition) {
ok(true, "Platform does not use XUL alerts.");
return;
}
var secondAlertPosition = yield promiseAlertPosition(alertService);
is(secondAlertPosition.x, firstAlertPosition.x, "Second alert should be opened in the same position.");
is(secondAlertPosition.y, firstAlertPosition.y, "Second alert should be opened in the same position.");
});
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,122 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Bug 1202933</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="text/javascript">
const Cc = SpecialPowers.Cc;
const Ci = SpecialPowers.Ci;
const Services = SpecialPowers.Services;
const notifier = Cc["@mozilla.org/alerts-service;1"]
.getService(Ci.nsIAlertsService);
const chromeScript = SpecialPowers.loadChromeScript(_ => {
Components.utils.import("resource://gre/modules/Services.jsm");
addMessageListener("anyXULAlertsVisible", function() {
var windows = Services.wm.getEnumerator("alert:alert");
return windows.hasMoreElements();
});
addMessageListener("getAlertSource", function() {
var alertWindows = Services.wm.getEnumerator("alert:alert");
if (!alertWindows) {
return null;
}
var alertWindow = alertWindows.getNext();
return alertWindow.document.getElementById("alertSourceLabel").getAttribute("value");
});
});
function notify(alertName, principal) {
return new Promise((resolve, reject) => {
var source;
function observe(subject, topic, data) {
if (topic == "alertclickcallback") {
reject(new Error("Alerts should not be clicked during test"));
} else if (topic == "alertshow") {
source = chromeScript.sendSyncMessage("getAlertSource")[0][0];
notifier.closeAlert(alertName);
} else {
is(topic, "alertfinished", "Should hide alert");
resolve(source);
}
}
notifier.showAlertNotification(null, "Notification test",
"Surprise! I'm here to test notifications!",
false, alertName, observe, alertName,
null, null, null, principal);
if (SpecialPowers.Services.appinfo.OS == "Darwin") {
notifier.closeAlert(alertName);
}
});
}
function* testNoPrincipal() {
var source = yield notify("noPrincipal", null);
ok(!source, "Should omit source without principal");
}
function* testSystemPrincipal() {
var principal = Services.scriptSecurityManager.getSystemPrincipal();
var source = yield notify("systemPrincipal", principal);
ok(!source, "Should omit source for system principal");
}
function* testNullPrincipal() {
var principal = Services.scriptSecurityManager.createNullPrincipal({});
var source = yield notify("nullPrincipal", principal);
ok(!source, "Should omit source for null principal");
}
function* testNodePrincipal() {
var principal = SpecialPowers.wrap(document).nodePrincipal;
var source = yield notify("nodePrincipal", principal);
var stringBundle = Services.strings.createBundle(
"chrome://alerts/locale/alert.properties"
);
var localizedSource = stringBundle.formatStringFromName(
"source.label", [principal.URI.hostPort], 1);
is(source, localizedSource, "Should include source for node principal");
}
function runTest() {
if (!("@mozilla.org/alerts-service;1" in Cc)) {
todo(false, "Alerts service does not exist in this application");
return;
}
if ("@mozilla.org/system-alerts-service;1" in Cc) {
todo(false, "Native alerts service exists in this application");
return;
}
ok(true, "Alerts service exists in this application");
// sendSyncMessage returns an array of arrays. See the comments in
// test_alerts_noobserve.html and test_SpecialPowersLoadChromeScript.html.
var [[alertsVisible]] = chromeScript.sendSyncMessage("anyXULAlertsVisible");
ok(!alertsVisible, "Alerts should not be present at the start of the test.");
add_task(testNoPrincipal);
add_task(testSystemPrincipal);
add_task(testNullPrincipal);
add_task(testNodePrincipal);
}
runTest();
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,210 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource://gre/modules/AppConstants.jsm");
function AppPicker() {}
AppPicker.prototype =
{
// Class members
_incomingParams:null,
/**
* Init the dialog and populate the application list
*/
appPickerLoad: function appPickerLoad() {
const nsILocalHandlerApp = Components.interfaces.nsILocalHandlerApp;
this._incomingParams = window.arguments[0];
this._incomingParams.handlerApp = null;
document.title = this._incomingParams.title;
// Header creation - at the very least, we must have
// a mime type:
//
// (icon) Zip File
// (icon) filename
//
// (icon) Web Feed
// (icon) mime/type
//
// (icon) mime/type
// (icon)
var mimeInfo = this._incomingParams.mimeInfo;
var filename = this._incomingParams.filename;
if (!filename) {
filename = mimeInfo.MIMEType;
}
var description = this._incomingParams.description;
if (!description) {
description = filename;
filename = "";
}
// Setup the dialog header information
document.getElementById("content-description").setAttribute("value",
description);
document.getElementById("suggested-filename").setAttribute("value",
filename);
document.getElementById("content-icon").setAttribute("src",
"moz-icon://" + filename + "?size=32&contentType=" +
mimeInfo.MIMEType);
// Grab a list of nsILocalHandlerApp application helpers to list
var fileList = mimeInfo.possibleLocalHandlers;
var list = document.getElementById("app-picker-listbox");
var primaryCount = 0;
if (!fileList || fileList.length == 0) {
// display a message saying nothing is configured
document.getElementById("app-picker-notfound").removeAttribute("hidden");
return;
}
for (var idx = 0; idx < fileList.length; idx++) {
var file = fileList.queryElementAt(idx, nsILocalHandlerApp);
try {
if (!file.executable || !file.executable.isFile())
continue;
} catch (err) {
continue;
}
var item = document.createElement("listitem");
item.className = "listitem-iconic";
item.handlerApp = file;
item.setAttribute("label", this.getFileDisplayName(file.executable));
item.setAttribute("image", this.getFileIconURL(file.executable));
list.appendChild(item);
primaryCount++;
}
if ( primaryCount == 0 ) {
// display a message saying nothing is configured
document.getElementById("app-picker-notfound").removeAttribute("hidden");
}
},
/**
* Retrieve the moz-icon for the app
*/
getFileIconURL: function getFileIconURL(file) {
var ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
if (!ios) return "";
const nsIFileProtocolHandler =
Components.interfaces.nsIFileProtocolHandler;
var fph = ios.getProtocolHandler("file")
.QueryInterface(nsIFileProtocolHandler);
if (!fph) return "";
var urlSpec = fph.getURLSpecFromFile(file);
return "moz-icon://" + urlSpec + "?size=32";
},
/**
* Retrieve the pretty description from the file
*/
getFileDisplayName: function getFileDisplayName(file) {
if (AppConstants.platform == "win") {
if (file instanceof Components.interfaces.nsILocalFileWin) {
try {
return file.getVersionInfoField("FileDescription");
} catch (e) {}
}
} else if (AppConstants.platform == "macosx") {
if (file instanceof Components.interfaces.nsILocalFileMac) {
try {
return file.bundleDisplayName;
} catch (e) {}
}
}
return file.leafName;
},
/**
* Double click accepts an app
*/
appDoubleClick: function appDoubleClick() {
var list = document.getElementById("app-picker-listbox");
var selItem = list.selectedItem;
if (!selItem) {
this._incomingParams.handlerApp = null;
return true;
}
this._incomingParams.handlerApp = selItem.handlerApp;
window.close();
return true;
},
appPickerOK: function appPickerOK() {
if (this._incomingParams.handlerApp) return true;
var list = document.getElementById("app-picker-listbox");
var selItem = list.selectedItem;
if (!selItem) {
this._incomingParams.handlerApp = null;
return true;
}
this._incomingParams.handlerApp = selItem.handlerApp;
return true;
},
appPickerCancel: function appPickerCancel() {
this._incomingParams.handlerApp = null;
return true;
},
/**
* User browse for an app.
*/
appPickerBrowse: function appPickerBrowse() {
var nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"].
createInstance(nsIFilePicker);
fp.init(window, this._incomingParams.title, nsIFilePicker.modeOpen);
fp.appendFilters(nsIFilePicker.filterApps);
var fileLoc = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties);
var startLocation;
if (AppConstants.platform == "win") {
startLocation = "ProgF"; // Program Files
} else if (AppConstants.platform == "macosx") {
startLocation = "LocApp"; // Local Applications
} else {
startLocation = "Home";
}
fp.displayDirectory =
fileLoc.get(startLocation, Components.interfaces.nsILocalFile);
if (fp.show() == nsIFilePicker.returnOK && fp.file) {
var localHandlerApp =
Components.classes["@mozilla.org/uriloader/local-handler-app;1"].
createInstance(Components.interfaces.nsILocalHandlerApp);
localHandlerApp.executable = fp.file;
this._incomingParams.handlerApp = localHandlerApp;
window.close();
}
return true;
}
}
// Global object
var g_dialog = new AppPicker();

View file

@ -0,0 +1,40 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/appPicker.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://global/locale/appPicker.dtd" >
<dialog id="app-picker"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="g_dialog.appPickerLoad();"
buttons="accept,cancel,extra2"
buttonlabelextra2="&BrowseButton.label;"
ondialogextra2="g_dialog.appPickerBrowse();"
defaultButton="cancel"
ondialogaccept="return g_dialog.appPickerOK();"
ondialogcancel="return g_dialog.appPickerCancel();"
aria-describedby="content-description suggested-filename"
persist="screenX screenY">
<script type="application/javascript" src="chrome://global/content/appPicker.js"/>
<hbox id="file-info" align="center">
<image id="content-icon" src=""/>
<vbox flex="1">
<label id="content-description" crop="center" value=""/>
<label id="suggested-filename" crop="center" value=""/>
</vbox>
</hbox>
<label id="sendto-message" value="&SendMsg.label;" control="app-picker-listbox"/>
<listbox id="app-picker-listbox" rows="5"
ondblclick="g_dialog.appDoubleClick();"/>
<label id="app-picker-notfound" value="&NoAppFound.label;" hidden="true"/>
</dialog>

View file

@ -0,0 +1,8 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
toolkit.jar:
content/global/appPicker.xul (content/appPicker.xul)
content/global/appPicker.js (content/appPicker.js)

View file

@ -0,0 +1,7 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
JAR_MANIFESTS += ['jar.mn']

Some files were not shown because too many files have changed in this diff Show more