mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-01 04:47:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
206
toolkit/.eslintrc.js
Normal file
206
toolkit/.eslintrc.js
Normal 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,
|
||||
}
|
||||
};
|
||||
11
toolkit/components/.eslintrc.js
Normal file
11
toolkit/components/.eslintrc.js
Normal 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",
|
||||
}]
|
||||
}
|
||||
};
|
||||
44
toolkit/components/aboutcache/content/aboutCache.js
Normal file
44
toolkit/components/aboutcache/content/aboutCache.js
Normal 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;
|
||||
}
|
||||
6
toolkit/components/aboutcache/jar.mn
Normal file
6
toolkit/components/aboutcache/jar.mn
Normal 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)
|
||||
7
toolkit/components/aboutcache/moz.build
Normal file
7
toolkit/components/aboutcache/moz.build
Normal 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']
|
||||
|
|
@ -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%;
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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)">«</button><!-- rewind button -->
|
||||
<button onclick="step(true)"><</button><!-- step back button -->
|
||||
<button onclick="togglePlay()">|| ▶</button><!-- pause button -->
|
||||
<button onclick="stopPlay()">☐</button><!-- stop button -->
|
||||
<button onclick="step(false)">></button><!-- step forward button -->
|
||||
<button onclick="reset(false)">»</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>
|
||||
8
toolkit/components/aboutcheckerboard/jar.mn
Normal file
8
toolkit/components/aboutcheckerboard/jar.mn
Normal 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)
|
||||
10
toolkit/components/aboutcheckerboard/moz.build
Normal file
10
toolkit/components/aboutcheckerboard/moz.build
Normal 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')
|
||||
|
|
@ -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>
|
||||
154
toolkit/components/aboutmemory/content/aboutMemory.css
Normal file
154
toolkit/components/aboutmemory/content/aboutMemory.css
Normal 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;
|
||||
}
|
||||
|
||||
2042
toolkit/components/aboutmemory/content/aboutMemory.js
Normal file
2042
toolkit/components/aboutmemory/content/aboutMemory.js
Normal file
File diff suppressed because it is too large
Load diff
15
toolkit/components/aboutmemory/content/aboutMemory.xhtml
Normal file
15
toolkit/components/aboutmemory/content/aboutMemory.xhtml
Normal 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>
|
||||
8
toolkit/components/aboutmemory/jar.mn
Normal file
8
toolkit/components/aboutmemory/jar.mn
Normal 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)
|
||||
12
toolkit/components/aboutmemory/moz.build
Normal file
12
toolkit/components/aboutmemory/moz.build
Normal 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')
|
||||
7
toolkit/components/aboutmemory/tests/.eslintrc.js
Normal file
7
toolkit/components/aboutmemory/tests/.eslintrc.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../testing/mochitest/chrome.eslintrc.js"
|
||||
]
|
||||
};
|
||||
28
toolkit/components/aboutmemory/tests/chrome.ini
Normal file
28
toolkit/components/aboutmemory/tests/chrome.ini
Normal 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]
|
||||
11
toolkit/components/aboutmemory/tests/crash-dump-diff1.json
Normal file
11
toolkit/components/aboutmemory/tests/crash-dump-diff1.json
Normal 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."}
|
||||
]
|
||||
}
|
||||
}
|
||||
11
toolkit/components/aboutmemory/tests/crash-dump-diff2.json
Normal file
11
toolkit/components/aboutmemory/tests/crash-dump-diff2.json
Normal 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."}
|
||||
]
|
||||
}
|
||||
}
|
||||
14
toolkit/components/aboutmemory/tests/crash-dump-good.json
Normal file
14
toolkit/components/aboutmemory/tests/crash-dump-good.json
Normal 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."}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -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."}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -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."}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -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."}
|
||||
]
|
||||
}
|
||||
12
toolkit/components/aboutmemory/tests/remote.xul
Normal file
12
toolkit/components/aboutmemory/tests/remote.xul
Normal 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>
|
||||
602
toolkit/components/aboutmemory/tests/test_aboutmemory.xul
Normal file
602
toolkit/components/aboutmemory/tests/test_aboutmemory.xul
Normal 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>
|
||||
423
toolkit/components/aboutmemory/tests/test_aboutmemory2.xul
Normal file
423
toolkit/components/aboutmemory/tests/test_aboutmemory2.xul
Normal 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>
|
||||
515
toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
Normal file
515
toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
Normal 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>
|
||||
179
toolkit/components/aboutmemory/tests/test_aboutmemory4.xul
Normal file
179
toolkit/components/aboutmemory/tests/test_aboutmemory4.xul
Normal 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>
|
||||
167
toolkit/components/aboutmemory/tests/test_aboutmemory5.xul
Normal file
167
toolkit/components/aboutmemory/tests/test_aboutmemory5.xul
Normal 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>
|
||||
88
toolkit/components/aboutmemory/tests/test_aboutmemory6.xul
Normal file
88
toolkit/components/aboutmemory/tests/test_aboutmemory6.xul
Normal 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>
|
||||
|
|
@ -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>
|
||||
424
toolkit/components/aboutmemory/tests/test_memoryReporters.xul
Normal file
424
toolkit/components/aboutmemory/tests/test_memoryReporters.xul
Normal 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>
|
||||
108
toolkit/components/aboutmemory/tests/test_memoryReporters2.xul
Normal file
108
toolkit/components/aboutmemory/tests/test_memoryReporters2.xul
Normal 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>
|
||||
|
|
@ -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>
|
||||
1077
toolkit/components/aboutperformance/content/aboutPerformance.js
Normal file
1077
toolkit/components/aboutperformance/content/aboutPerformance.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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>
|
||||
7
toolkit/components/aboutperformance/jar.mn
Normal file
7
toolkit/components/aboutperformance/jar.mn
Normal 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)
|
||||
9
toolkit/components/aboutperformance/moz.build
Normal file
9
toolkit/components/aboutperformance/moz.build
Normal 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']
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../../testing/mochitest/browser.eslintrc.js"
|
||||
]
|
||||
};
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
[DEFAULT]
|
||||
head = head.js
|
||||
support-files =
|
||||
browser_compartments.html
|
||||
browser_compartments_frame.html
|
||||
browser_compartments_script.js
|
||||
|
||||
[browser_aboutperformance.js]
|
||||
|
|
@ -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;
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
52
toolkit/components/aboutperformance/tests/browser/head.js
Normal file
52
toolkit/components/aboutperformance/tests/browser/head.js
Normal 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;
|
||||
}
|
||||
107
toolkit/components/addoncompat/CompatWarning.jsm
Normal file
107
toolkit/components/addoncompat/CompatWarning.jsm
Normal 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.
|
||||
},
|
||||
};
|
||||
557
toolkit/components/addoncompat/Prefetcher.jsm
Normal file
557
toolkit/components/addoncompat/Prefetcher.jsm
Normal 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;
|
||||
576
toolkit/components/addoncompat/RemoteAddonsChild.jsm
Normal file
576
toolkit/components/addoncompat/RemoteAddonsChild.jsm
Normal 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;
|
||||
},
|
||||
};
|
||||
1080
toolkit/components/addoncompat/RemoteAddonsParent.jsm
Normal file
1080
toolkit/components/addoncompat/RemoteAddonsParent.jsm
Normal file
File diff suppressed because it is too large
Load diff
15
toolkit/components/addoncompat/ShimWaiver.jsm
Normal file
15
toolkit/components/addoncompat/ShimWaiver.jsm
Normal 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;
|
||||
}
|
||||
};
|
||||
4
toolkit/components/addoncompat/addoncompat.manifest
Normal file
4
toolkit/components/addoncompat/addoncompat.manifest
Normal 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}
|
||||
39
toolkit/components/addoncompat/defaultShims.js
Normal file
39
toolkit/components/addoncompat/defaultShims.js
Normal 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]);
|
||||
21
toolkit/components/addoncompat/moz.build
Normal file
21
toolkit/components/addoncompat/moz.build
Normal 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'
|
||||
]
|
||||
182
toolkit/components/addoncompat/multiprocessShims.js
Normal file
182
toolkit/components/addoncompat/multiprocessShims.js
Normal 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]);
|
||||
653
toolkit/components/addoncompat/tests/addon/bootstrap.js
vendored
Normal file
653
toolkit/components/addoncompat/tests/addon/bootstrap.js
vendored
Normal 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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
content addonshim1 content/
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<html>
|
||||
</html>
|
||||
37
toolkit/components/addoncompat/tests/addon/install.rdf
Normal file
37
toolkit/components/addoncompat/tests/addon/install.rdf
Normal 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>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../../testing/mochitest/browser.eslintrc.js"
|
||||
]
|
||||
};
|
||||
BIN
toolkit/components/addoncompat/tests/browser/addon.xpi
Normal file
BIN
toolkit/components/addoncompat/tests/browser/addon.xpi
Normal file
Binary file not shown.
9
toolkit/components/addoncompat/tests/browser/browser.ini
Normal file
9
toolkit/components/addoncompat/tests/browser/browser.ini
Normal 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]
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
BIN
toolkit/components/addoncompat/tests/browser/compat-addon.xpi
Normal file
BIN
toolkit/components/addoncompat/tests/browser/compat-addon.xpi
Normal file
Binary file not shown.
99
toolkit/components/addoncompat/tests/compat-addon/bootstrap.js
vendored
Normal file
99
toolkit/components/addoncompat/tests/compat-addon/bootstrap.js
vendored
Normal 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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -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>
|
||||
7
toolkit/components/addoncompat/tests/moz.build
Normal file
7
toolkit/components/addoncompat/tests/moz.build
Normal 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']
|
||||
361
toolkit/components/alerts/AlertNotification.cpp
Normal file
361
toolkit/components/alerts/AlertNotification.cpp
Normal 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
|
||||
81
toolkit/components/alerts/AlertNotification.h
Normal file
81
toolkit/components/alerts/AlertNotification.h
Normal 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__ */
|
||||
122
toolkit/components/alerts/AlertNotificationIPCSerializer.h
Normal file
122
toolkit/components/alerts/AlertNotificationIPCSerializer.h
Normal 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__ */
|
||||
8
toolkit/components/alerts/jar.mn
Normal file
8
toolkit/components/alerts/jar.mn
Normal 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)
|
||||
38
toolkit/components/alerts/moz.build
Normal file
38
toolkit/components/alerts/moz.build
Normal 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')
|
||||
320
toolkit/components/alerts/nsAlertsService.cpp
Normal file
320
toolkit/components/alerts/nsAlertsService.cpp
Normal 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();
|
||||
}
|
||||
48
toolkit/components/alerts/nsAlertsService.h
Normal file
48
toolkit/components/alerts/nsAlertsService.h
Normal 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__ */
|
||||
43
toolkit/components/alerts/nsAlertsUtils.cpp
Normal file
43
toolkit/components/alerts/nsAlertsUtils.cpp
Normal 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);
|
||||
}
|
||||
32
toolkit/components/alerts/nsAlertsUtils.h
Normal file
32
toolkit/components/alerts/nsAlertsUtils.h
Normal 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 */
|
||||
259
toolkit/components/alerts/nsIAlertsService.idl
Normal file
259
toolkit/components/alerts/nsIAlertsService.idl
Normal 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);
|
||||
};
|
||||
398
toolkit/components/alerts/nsXULAlerts.cpp
Normal file
398
toolkit/components/alerts/nsXULAlerts.cpp
Normal 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;
|
||||
}
|
||||
|
||||
84
toolkit/components/alerts/nsXULAlerts.h
Normal file
84
toolkit/components/alerts/nsXULAlerts.h
Normal 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__ */
|
||||
|
||||
34
toolkit/components/alerts/resources/content/alert.css
Normal file
34
toolkit/components/alerts/resources/content/alert.css
Normal 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;
|
||||
}
|
||||
}
|
||||
332
toolkit/components/alerts/resources/content/alert.js
Normal file
332
toolkit/components/alerts/resources/content/alert.js
Normal 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();
|
||||
}
|
||||
}
|
||||
67
toolkit/components/alerts/resources/content/alert.xul
Normal file
67
toolkit/components/alerts/resources/content/alert.xul
Normal 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>
|
||||
|
||||
7
toolkit/components/alerts/test/.eslintrc.js
Normal file
7
toolkit/components/alerts/test/.eslintrc.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../testing/mochitest/mochitest.eslintrc.js"
|
||||
]
|
||||
};
|
||||
BIN
toolkit/components/alerts/test/image.gif
Normal file
BIN
toolkit/components/alerts/test/image.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
BIN
toolkit/components/alerts/test/image.png
Normal file
BIN
toolkit/components/alerts/test/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
82
toolkit/components/alerts/test/image_server.sjs
Normal file
82
toolkit/components/alerts/test/image_server.sjs
Normal 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();
|
||||
}
|
||||
16
toolkit/components/alerts/test/mochitest.ini
Normal file
16
toolkit/components/alerts/test/mochitest.ini
Normal 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'
|
||||
89
toolkit/components/alerts/test/test_alerts.html
Normal file
89
toolkit/components/alerts/test/test_alerts.html
Normal 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>
|
||||
96
toolkit/components/alerts/test/test_alerts_noobserve.html
Normal file
96
toolkit/components/alerts/test/test_alerts_noobserve.html
Normal 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>
|
||||
|
|
@ -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>
|
||||
118
toolkit/components/alerts/test/test_image.html
Normal file
118
toolkit/components/alerts/test/test_image.html
Normal 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>
|
||||
103
toolkit/components/alerts/test/test_multiple_alerts.html
Normal file
103
toolkit/components/alerts/test/test_multiple_alerts.html
Normal 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>
|
||||
122
toolkit/components/alerts/test/test_principal.html
Normal file
122
toolkit/components/alerts/test/test_principal.html
Normal 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>
|
||||
210
toolkit/components/apppicker/content/appPicker.js
Normal file
210
toolkit/components/apppicker/content/appPicker.js
Normal 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();
|
||||
40
toolkit/components/apppicker/content/appPicker.xul
Normal file
40
toolkit/components/apppicker/content/appPicker.xul
Normal 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>
|
||||
8
toolkit/components/apppicker/jar.mn
Normal file
8
toolkit/components/apppicker/jar.mn
Normal 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)
|
||||
|
||||
7
toolkit/components/apppicker/moz.build
Normal file
7
toolkit/components/apppicker/moz.build
Normal 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
Loading…
Add table
Add a link
Reference in a new issue