Merge m-c to b2g-inbound a=merge

This commit is contained in:
Wes Kocher 2014-06-24 18:47:03 -07:00
commit c0dc2887cb
465 changed files with 7750 additions and 4023 deletions

View File

@ -233,7 +233,6 @@ function startup(data, reasonCode) {
// Arguments related to test runner.
modules: {
'@test/options': {
allTestModules: options.allTestModules,
iterations: options.iterations,
filter: options.filter,
profileMemory: options.profileMemory,

View File

@ -54,7 +54,6 @@ exports.install = function install(xpiPath) {
setTimeout(resolve, 0, aAddon.id);
},
onInstallFailed: function (aInstall) {
console.log("failed");
aInstall.removeListener(listener);
reject(aInstall.error);
},
@ -114,8 +113,9 @@ exports.isActive = function isActive(addonId) {
return getAddon(addonId).then(addon => addon.isActive && !addon.appDisabled);
};
function getAddon (id) {
const getAddon = function getAddon (id) {
let { promise, resolve, reject } = defer();
AddonManager.getAddonByID(id, addon => addon ? resolve(addon) : reject());
return promise;
}
exports.getAddon = getAddon;

View File

@ -56,8 +56,6 @@ eventTarget.addEventListener("DOMContentLoaded", function handler(event) {
resolve();
}, false);
exports.ready = promise;
exports.window = window;

View File

@ -1,7 +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/. */
"use strict";
module.metadata = {
@ -10,9 +9,109 @@ module.metadata = {
const file = require("../io/file");
const memory = require('./memory');
const suites = require('@test/options').allTestModules;
const { Loader } = require("sdk/test/loader");
const cuddlefish = require("sdk/loader/cuddlefish");
const { Loader } = require("../test/loader");
const cuddlefish = require("../loader/cuddlefish");
const { defer, resolve } = require("../core/promise");
const { getAddon } = require("../addon/installer");
const { id } = require("sdk/self");
const { newURI } = require('sdk/url/utils');
const { getZipReader } = require("../zip/utils");
const { Cc, Ci, Cu } = require("chrome");
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
var ios = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);
const TEST_REGEX = /(([^\/]+\/)(?:lib\/)?)(tests?\/test-[^\.\/]+)\.js$/;
const { mapcat, map, filter, fromEnumerator } = require("sdk/util/sequence");
const toFile = x => x.QueryInterface(Ci.nsIFile);
const isTestFile = ({leafName}) => leafName.substr(0, 5) == "test-" && leafName.substr(-3, 3) == ".js";
const getFileURI = x => ios.newFileURI(x).spec;
const getDirectoryEntries = file => map(toFile, fromEnumerator(_ => file.directoryEntries));
const getTestFiles = directory => filter(isTestFile, getDirectoryEntries(directory));
const getTestURIs = directory => map(getFileURI, getTestFiles(directory));
const isDirectory = x => x.isDirectory();
const getTestEntries = directory => mapcat(entry =>
/^tests?$/.test(entry.leafName) ? getTestURIs(entry) : getTestEntries(entry),
filter(isDirectory, getDirectoryEntries(directory)));
const removeDups = (array) => array.reduce((result, value) => {
if (value != result[result.length - 1]) {
result.push(value);
}
return result;
}, []);
const getSuites = function getSuites({ id }) {
return getAddon(id).then(addon => {
let fileURI = addon.getResourceURI("tests/");
let isPacked = fileURI.scheme == "jar";
let xpiURI = addon.getResourceURI();
let file = xpiURI.QueryInterface(Ci.nsIFileURL).file;
let suites = [];
let addEntry = (entry) => {
if (TEST_REGEX.test(entry)) {
let suite = RegExp.$2 + RegExp.$3;
suites.push(suite);
}
}
if (isPacked) {
return getZipReader(file).then(zip => {
let entries = zip.findEntries(null);
while (entries.hasMore()) {
let entry = entries.getNext();
addEntry(entry);
}
zip.close();
// sort and remove dups
suites = removeDups(suites.sort());
return suites;
})
} else {
let tests = getTestEntries(file);
[...tests].forEach(addEntry);
}
// sort and remove dups
suites = removeDups(suites.sort());
return suites;
});
}
exports.getSuites = getSuites;
const makeFilter = function makeFilter(options) {
// A filter string is {fileNameRegex}[:{testNameRegex}] - ie, a colon
// optionally separates a regex for the test fileName from a regex for the
// testName.
if (options.filter) {
let colonPos = options.filter.indexOf(':');
let filterFileRegex, filterNameRegex;
if (colonPos === -1) {
filterFileRegex = new RegExp(options.filter);
} else {
filterFileRegex = new RegExp(options.filter.substr(0, colonPos));
filterNameRegex = new RegExp(options.filter.substr(colonPos + 1));
}
// This function will first be called with just the filename; if
// it returns true the module will be loaded then the function
// called again with both the filename and the testname.
return (filename, testname) => {
return filterFileRegex.test(filename) &&
((testname && filterNameRegex) ? filterNameRegex.test(testname)
: true);
};
}
return () => true;
}
exports.makeFilter = makeFilter;
let loader = Loader(module);
const NOT_TESTS = ['setup', 'teardown'];
@ -25,45 +124,20 @@ var TestFinder = exports.TestFinder = function TestFinder(options) {
};
TestFinder.prototype = {
findTests: function findTests(cb) {
var self = this;
var tests = [];
var filter;
// A filter string is {fileNameRegex}[:{testNameRegex}] - ie, a colon
// optionally separates a regex for the test fileName from a regex for the
// testName.
if (this.filter) {
var colonPos = this.filter.indexOf(':');
var filterFileRegex, filterNameRegex;
if (colonPos === -1) {
filterFileRegex = new RegExp(self.filter);
} else {
filterFileRegex = new RegExp(self.filter.substr(0, colonPos));
filterNameRegex = new RegExp(self.filter.substr(colonPos + 1));
}
// This function will first be called with just the filename; if
// it returns true the module will be loaded then the function
// called again with both the filename and the testname.
filter = function(filename, testname) {
return filterFileRegex.test(filename) &&
((testname && filterNameRegex) ? filterNameRegex.test(testname)
: true);
};
} else
filter = function() {return true};
findTests: function findTests() {
return getSuites({ id: id }).then(suites => {
let filter = makeFilter({ filter: this.filter });
let tests = [];
suites.forEach(function(suite) {
suites.forEach(suite => {
// Load each test file as a main module in its own loader instance
// `suite` is defined by cuddlefish/manifest.py:ManifestBuilder.build
let suiteModule;
try {
suiteModule = cuddlefish.main(loader, suite);
}
catch (e) {
if (!/^Unsupported Application/.test(e.message))
throw e;
// If `Unsupported Application` error thrown during test,
// skip the test suite
suiteModule = {
@ -71,9 +145,9 @@ TestFinder.prototype = {
};
}
if (self.testInProcess)
if (this.testInProcess) {
for each (let name in Object.keys(suiteModule).sort()) {
if(NOT_TESTS.indexOf(name) === -1 && filter(suite, name)) {
if (NOT_TESTS.indexOf(name) === -1 && filter(suite, name)) {
tests.push({
setup: suiteModule.setup,
teardown: suiteModule.teardown,
@ -82,8 +156,10 @@ TestFinder.prototype = {
});
}
}
});
}
})
cb(tests);
return tests;
});
}
};

View File

@ -1,7 +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/. */
"use strict";
module.metadata = {
@ -9,12 +8,13 @@ module.metadata = {
};
const memory = require('./memory');
var timer = require("../timers");
const timer = require("../timers");
var cfxArgs = require("@test/options");
const { getTabs, getURI } = require("../tabs/utils");
const { windows, isBrowser } = require("../window/utils");
const { defer } = require("../core/promise");
exports.findAndRunTests = function findAndRunTests(options) {
const findAndRunTests = function findAndRunTests(options) {
var TestFinder = require("./unit-test-finder").TestFinder;
var finder = new TestFinder({
filter: options.filter,
@ -22,15 +22,17 @@ exports.findAndRunTests = function findAndRunTests(options) {
testOutOfProcess: options.testOutOfProcess
});
var runner = new TestRunner({fs: options.fs});
finder.findTests(
function (tests) {
runner.startMany({tests: tests,
finder.findTests().then(tests => {
runner.startMany({
tests: tests,
stopOnError: options.stopOnError,
onDone: options.onDone});
onDone: options.onDone
});
});
};
exports.findAndRunTests = findAndRunTests;
var TestRunner = exports.TestRunner = function TestRunner(options) {
const TestRunner = function TestRunner(options) {
if (options) {
this.fs = options.fs;
}
@ -40,6 +42,7 @@ var TestRunner = exports.TestRunner = function TestRunner(options) {
this.failed = 0;
this.testRunSummary = [];
this.expectFailNesting = 0;
this.done = TestRunner.prototype.done.bind(this);
};
TestRunner.prototype = {
@ -351,6 +354,7 @@ TestRunner.prototype = {
* assertion values.
*/
_waitUntil: function waitUntil(assertionMethod, args) {
let { promise, resolve } = defer();
let count = 0;
let maxCount = this.DEFAULT_PAUSE_TIMEOUT / this.PAUSE_DELAY;
@ -358,9 +362,7 @@ TestRunner.prototype = {
if (!this.waitTimeout)
this.waitUntilDone(this.DEFAULT_PAUSE_TIMEOUT);
let callback = null;
let finished = false;
let test = this;
// capture a traceback before we go async.
@ -380,9 +382,8 @@ TestRunner.prototype = {
pass: function (msg) {
test.pass(msg);
test.waitUntilCallback = null;
if (callback && !stopIt)
callback();
finished = true;
if (!stopIt)
resolve();
},
fail: function (msg) {
// If we are called on test timeout, we stop the loop
@ -411,8 +412,7 @@ TestRunner.prototype = {
catch(e) {
test.fail("Exception when calling asynchronous assertion: " + e +
"\n" + e.stack);
finished = true;
return;
return resolve();
}
}
appliedArgs.push(a);
@ -424,18 +424,7 @@ TestRunner.prototype = {
loop();
this.waitUntilCallback = loop;
// Return an object with `then` method, to offer a way to execute
// some code when the assertion passed or failed
return {
then: function (c) {
callback = c;
// In case of immediate positive result, we need to execute callback
// immediately here:
if (finished)
callback();
}
};
return promise;
},
waitUntilDone: function waitUntilDone(ms) {
@ -514,3 +503,4 @@ TestRunner.prototype = {
this.done();
}
};
exports.TestRunner = TestRunner;

View File

@ -621,7 +621,4 @@ var runTests = exports.runTests = function runTests(options) {
}
};
unload(function() {
cService.unregisterListener(consoleListener);
});
unload(_ => cService.unregisterListener(consoleListener));

View File

@ -33,8 +33,7 @@ function runTests(findAndRunTests) {
// are not correctly updated.
// For ex: nsIFocusManager.getFocusedElementForWindow may throw
// NS_ERROR_ILLEGAL_VALUE exception.
require("../timers").setTimeout(function () {
harness.runTests({
require("../timers").setTimeout(_ => harness.runTests({
findAndRunTests: findAndRunTests,
iterations: cfxArgs.iterations || 1,
filter: cfxArgs.filter,
@ -44,13 +43,12 @@ function runTests(findAndRunTests) {
parseable: cfxArgs.parseable,
print: stdout.write,
onDone: onDone
});
}, 0);
}));
}
function printFailedTests(tests, print) {
let iterationNumber = 0;
let singleIteration = tests.testRuns.length == 1;
let singleIteration = (tests.testRuns || []).length == 1;
let padding = singleIteration ? "" : " ";
print("\nThe following tests failed:\n");
@ -94,7 +92,7 @@ exports.runTestsFromModule = function runTestsFromModule(module) {
let id = module.id;
// Make a copy of exports as it may already be frozen by module loader
let exports = {};
Object.keys(module.exports).forEach(function(key) {
Object.keys(module.exports).forEach(key => {
exports[key] = module.exports[key];
});
@ -102,7 +100,7 @@ exports.runTestsFromModule = function runTestsFromModule(module) {
// Consider that all these tests are CommonJS ones
loader.require('../../test').run(exports);
// Reproduce what is done in unit-test-finder.findTests()
// Reproduce what is done in sdk/deprecated/unit-test-finder.findTests()
let tests = [];
for each (let name in Object.keys(exports).sort()) {
tests.push({

View File

@ -0,0 +1,22 @@
/* 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 { Cc, Ci, Cu } = require("chrome");
const { defer } = require("../core/promise");
const getZipReader = function getZipReader(aFile) {
let { promise, resolve, reject } = defer();
let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(Ci.nsIZipReader);
try {
zipReader.open(aFile);
}
catch(e){
reject(e);
}
resolve(zipReader);
return promise;
};
exports.getZipReader = getZipReader;

View File

@ -6,5 +6,6 @@
"xulrunner", "firefox", "browser"
],
"loader": "lib/sdk/loader/cuddlefish.js",
"license": "MPL 2.0"
"license": "MPL 2.0",
"unpack": true
}

View File

@ -836,12 +836,6 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
# Pass a flag in order to force using sdk modules shipped in the xpi
harness_options['force-use-bundled-sdk'] = True
# Pass the list of absolute path for all test modules
if command == "test":
harness_options['allTestModules'] = manifest.get_all_test_modules()
if len(harness_options['allTestModules']) == 0:
sys.exit(0)
from cuddlefish.rdf import gen_manifest, RDFUpdate
manifest_rdf = gen_manifest(template_root_dir=app_extension_dir,

View File

@ -278,9 +278,6 @@ class ManifestBuilder:
if me.packageName != "addon-sdk" or bundle_sdk_modules:
yield me.js_filename
def get_all_test_modules(self):
return self.test_modules
def get_harness_options_manifest(self, bundle_sdk_modules):
manifest = {}
for me in self.get_module_entries():

View File

@ -539,7 +539,10 @@ def run_app(harness_root_dir, manifest_rdf, harness_options,
outfile_tail = follow_file(outfile)
def maybe_remove_outfile():
if os.path.exists(outfile):
try:
os.remove(outfile)
except Exception, e:
print "Error Cleaning up: " + str(e)
atexit.register(maybe_remove_outfile)
outf = open(outfile, "w")
popen_kwargs = { 'stdout': outf, 'stderr': outf}

View File

@ -190,8 +190,7 @@ class TestCfxQuits(unittest.TestCase):
self.assertIn("Program terminated successfully.", err)
def test_cfx_test(self):
addon_path = os.path.join(tests_path,
"addons", "simplest-test")
addon_path = os.path.join(tests_path, "addons", "simplest-test")
rc, out, err = self.run_cfx(addon_path, ["test"])
self.assertEqual(rc, 0)
self.assertIn("1 of 1 tests passed.", err)

View File

@ -244,91 +244,6 @@ class SmallXPI(unittest.TestCase):
"uft8_value": "\u00e9"
}'''))
def test_scantests(self):
target_cfg = self.get_pkg("three")
package_path = [self.get_linker_files_dir("three-deps")]
pkg_cfg = packaging.build_config(self.root, target_cfg,
packagepath=package_path)
deps = packaging.get_deps_for_targets(pkg_cfg,
[target_cfg.name, "addon-sdk"])
m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=True)
self.failUnlessEqual(sorted(m.get_all_test_modules()),
sorted(["three/tests/test-one", "three/tests/test-two"]))
# the current __init__.py code omits limit_to=used_files for 'cfx
# test', so all test files are included in the XPI. But the test
# runner will only execute the tests that m.get_all_test_modules()
# tells us about (which are put into the .allTestModules property of
# harness-options.json).
used_deps = m.get_used_packages()
build = packaging.generate_build_for_target(pkg_cfg, target_cfg.name,
used_deps,
include_tests=True)
options = {'main': target_cfg.main}
options.update(build)
basedir = self.make_basedir()
xpi_name = os.path.join(basedir, "contents.xpi")
xpi.build_xpi(template_root_dir=xpi_template_path,
manifest=fake_manifest,
xpi_path=xpi_name,
harness_options=options,
limit_to=None)
x = zipfile.ZipFile(xpi_name, "r")
names = x.namelist()
self.failUnless("resources/addon-sdk/lib/sdk/deprecated/unit-test.js" in names, names)
self.failUnless("resources/addon-sdk/lib/sdk/deprecated/unit-test-finder.js" in names, names)
self.failUnless("resources/addon-sdk/lib/sdk/test/harness.js" in names, names)
self.failUnless("resources/addon-sdk/lib/sdk/test/runner.js" in names, names)
# all files are copied into the XPI, even the things that don't look
# like tests.
self.failUnless("resources/three/tests/test-one.js" in names, names)
self.failUnless("resources/three/tests/test-two.js" in names, names)
self.failUnless("resources/three/tests/nontest.js" in names, names)
def test_scantests_filter(self):
target_cfg = self.get_pkg("three")
package_path = [self.get_linker_files_dir("three-deps")]
pkg_cfg = packaging.build_config(self.root, target_cfg,
packagepath=package_path)
deps = packaging.get_deps_for_targets(pkg_cfg,
[target_cfg.name, "addon-sdk"])
FILTER = ".*one.*"
m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=True,
test_filter_re=FILTER)
self.failUnlessEqual(sorted(m.get_all_test_modules()),
sorted(["three/tests/test-one"]))
# the current __init__.py code omits limit_to=used_files for 'cfx
# test', so all test files are included in the XPI. But the test
# runner will only execute the tests that m.get_all_test_modules()
# tells us about (which are put into the .allTestModules property of
# harness-options.json).
used_deps = m.get_used_packages()
build = packaging.generate_build_for_target(pkg_cfg, target_cfg.name,
used_deps,
include_tests=True)
options = {'main': target_cfg.main}
options.update(build)
basedir = self.make_basedir()
xpi_name = os.path.join(basedir, "contents.xpi")
xpi.build_xpi(template_root_dir=xpi_template_path,
manifest=fake_manifest,
xpi_path=xpi_name,
harness_options=options,
limit_to=None)
x = zipfile.ZipFile(xpi_name, "r")
names = x.namelist()
self.failUnless("resources/addon-sdk/lib/sdk/deprecated/unit-test.js" in names, names)
self.failUnless("resources/addon-sdk/lib/sdk/deprecated/unit-test-finder.js" in names, names)
self.failUnless("resources/addon-sdk/lib/sdk/test/harness.js" in names, names)
self.failUnless("resources/addon-sdk/lib/sdk/test/runner.js" in names, names)
# get_all_test_modules() respects the filter. But all files are still
# copied into the XPI.
self.failUnless("resources/three/tests/test-one.js" in names, names)
self.failUnless("resources/three/tests/test-two.js" in names, names)
self.failUnless("resources/three/tests/nontest.js" in names, names)
def document_dir(name):
if name in ['packages', 'xpi-template']:

View File

@ -24,8 +24,9 @@ def mkzipdir(zf, path):
def build_xpi(template_root_dir, manifest, xpi_path,
harness_options, limit_to=None, extra_harness_options={},
bundle_sdk=True, pkgdir=""):
IGNORED_FILES = [".hgignore", ".DS_Store", "install.rdf",
IGNORED_FILES = [".hgignore", ".DS_Store",
"application.ini", xpi_path]
IGNORED_TOP_LVL_FILES = ["install.rdf"]
files_to_copy = {} # maps zipfile path to local-disk abspath
dirs_to_create = set() # zipfile paths, no trailing slash
@ -71,6 +72,8 @@ def build_xpi(template_root_dir, manifest, xpi_path,
files_to_copy[str(arcpath)] = str(abspath)
for dirpath, dirnames, filenames in os.walk(template_root_dir):
if template_root_dir == dirpath:
filenames = list(filter_filenames(filenames, IGNORED_TOP_LVL_FILES))
filenames = list(filter_filenames(filenames, IGNORED_FILES))
dirnames[:] = filter_dirnames(dirnames)
for dirname in dirnames:

View File

@ -21,7 +21,6 @@ exports.testNoTabCloseOnStartup = function(assert, done) {
});
}
exports.main = function() {
tabs.on('close', closeEventDetector);

View File

@ -34,11 +34,10 @@ exports.testASetupTeardownSyncTestPart2 = function(test) {
exports.testATeardownAsyncTestPart1 = function(test) {
teardownCalled = false;
timer.setTimeout(function() {
timer.setTimeout(_ => {
test.assertEqual(false, teardownCalled, "teardown not called until done");
test.done();
}, 200);
}, 20);
test.waitUntilDone();
};
@ -57,59 +56,62 @@ exports.testWaitUntil = function(test) {
test.waitUntilDone();
let succeed = false;
test.waitUntil(function () succeed, "waitUntil pass")
.then(function () test.done());
test.waitUntil(_ => succeed, "waitUntil pass")
.then(test.done);
timer.setTimeout(function () {
timer.setTimeout(_ => {
test.pass("succeed");
succeed = true;
}, 200);
}, 20);
}
exports.testWaitUntilEqual = function(test) {
test.waitUntilDone();
let succeed = false;
test.waitUntilEqual("foo", function () succeed ? "foo" : "bar",
test.waitUntilEqual("foo", _ => succeed ? "foo" : "bar",
"waitUntilEqual pass")
.then(function () test.done());
.then(test.done);
timer.setTimeout(function () {
timer.setTimeout(_ => {
test.pass("succeed");
succeed = true;
}, 200);
}, 20);
}
exports.testWaitUntilNotEqual = function(test) {
test.waitUntilDone();
let succeed = false;
test.waitUntilNotEqual("foo", function () succeed ? "bar" : "foo",
test.waitUntilNotEqual("foo", _ => succeed ? "bar" : "foo",
"waitUntilNotEqual pass")
.then(function () test.done());
.then(test.done);
timer.setTimeout(function () {
timer.setTimeout(_ => {
test.pass("succeed");
succeed = true;
}, 200);
}, 20);
}
exports.testWaitUntilMatches = function(test) {
test.waitUntilDone();
let succeed = false;
test.waitUntilMatches(function () succeed ? "foo" : "bar",
test.waitUntilMatches(_ => succeed ? "foo" : "bar",
/foo/, "waitUntilEqual pass")
.then(function () test.done());
.then(test.done);
timer.setTimeout(function () {
timer.setTimeout(_ => {
test.pass("succeed");
succeed = true;
}, 200);
}, 20);
}
exports.testWaitUntilErrorInCallback = function(test) {
test.waitUntilDone();
test.expectFail(function() {
test.waitUntil(function () {throw "oops"}, "waitUntil pass")
.then(function () test.done());
test.expectFail(_ => {
test.waitUntil(_ => { throw "oops"; }, "waitUntil pass")
.then(test.done);
});
}

View File

@ -486,7 +486,7 @@
@BINPATH@/components/FormHistoryStartup.js
@BINPATH@/components/nsInputListAutoComplete.js
@BINPATH@/components/formautofill.manifest
@BINPATH@/components/AutofillController.js
@BINPATH@/components/FormAutofillContentService.js
@BINPATH@/components/contentSecurityPolicy.manifest
@BINPATH@/components/contentSecurityPolicy.js
@BINPATH@/components/contentAreaDropListener.manifest

View File

@ -1607,8 +1607,6 @@
// allows the TabLabelModified event to be properly dispatched.
if (!aURI || isBlankPageURL(aURI)) {
t.label = this.mStringBundle.getString("tabs.emptyTabTitle");
} else {
t.label = aURI;
}
this.tabContainer.updateVisibility();

View File

@ -372,16 +372,10 @@ function test18a() {
var updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
ok(updateLink.style.visibility != "hidden", "Test 18a, Plugin should have an update link");
var tabOpenListener = new TabOpenListener(Services.urlFormatter.formatURLPref("plugins.update.url"), false, false);
tabOpenListener.handleEvent = function(event) {
if (event.type == "TabOpen") {
gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
this.tab = event.originalTarget;
is(event.target.label, this.url, "Test 18a, Update link should open up the plugin check page");
gBrowser.removeTab(this.tab);
test18b();
}
};
var pluginUpdateURL = Services.urlFormatter.formatURLPref("plugins.update.url");
var tabOpenListener = new TabOpenListener(pluginUpdateURL, function(tab) {
gBrowser.removeTab(tab);
}, test18b);
EventUtils.synthesizeMouseAtCenter(updateLink, {}, gTestBrowser.contentWindow);
}

View File

@ -56,7 +56,7 @@
#ifdef USE_WIN_TITLE_STYLE
title="&prefWindow.titleWin;">
#else
title="&prefWindow.titleGNOME;">
title="&prefWindow.title;">
#endif
<html:link rel="shortcut icon"

View File

@ -9,6 +9,10 @@ this.EXPORTED_SYMBOLS = ["PrivacyLevel"];
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
"@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
const PREF_NORMAL = "browser.sessionstore.privacy_level";
const PREF_DEFERRED = "browser.sessionstore.privacy_level_deferred";
@ -24,14 +28,6 @@ const PRIVACY_ENCRYPTED = 1;
// Collect no data.
const PRIVACY_FULL = 2;
/**
* Returns whether we will resume the session automatically on next startup.
*/
function willResumeAutomatically() {
return Services.prefs.getIntPref("browser.startup.page") == 3 ||
Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
}
/**
* Determines the current privacy level as set by the user.
*
@ -44,7 +40,7 @@ function getCurrentLevel(isPinned) {
// If we're in the process of quitting and we're not autoresuming the session
// then we will use the deferred privacy level for non-pinned tabs.
if (!isPinned && Services.startup.shuttingDown && !willResumeAutomatically()) {
if (!isPinned && Services.startup.shuttingDown && !gSessionStartup.isAutomaticRestoreEnabled()) {
pref = PREF_DEFERRED;
}

View File

@ -45,6 +45,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
"@mozilla.org/base/telemetry;1", "nsITelemetry");
XPCOMUtils.defineLazyServiceGetter(this, "sessionStartup",
"@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
XPCOMUtils.defineLazyModuleGetter(this, "SessionWorker",
"resource:///modules/sessionstore/SessionWorker.jsm");
const PREF_UPGRADE_BACKUP = "browser.sessionstore.upgradeBackup.latestBuildID";
this.SessionFile = {
/**
@ -75,43 +81,117 @@ this.SessionFile = {
gatherTelemetry: function(aData) {
return SessionFileInternal.gatherTelemetry(aData);
},
/**
* Create a backup copy, asynchronously.
* This is designed to perform backup on upgrade.
*/
createBackupCopy: function (ext) {
return SessionFileInternal.createBackupCopy(ext);
},
/**
* Remove a backup copy, asynchronously.
* This is designed to clean up a backup on upgrade.
*/
removeBackupCopy: function (ext) {
return SessionFileInternal.removeBackupCopy(ext);
},
/**
* Wipe the contents of the session file, asynchronously.
*/
wipe: function () {
SessionFileInternal.wipe();
return SessionFileInternal.wipe();
},
/**
* Return the paths to the files used to store, backup, etc.
* the state of the file.
*/
get Paths() {
return SessionFileInternal.Paths;
}
};
Object.freeze(SessionFile);
/**
* Utilities for dealing with promises and Task.jsm
*/
let Path = OS.Path;
let profileDir = OS.Constants.Path.profileDir;
let SessionFileInternal = {
/**
* The path to sessionstore.js
*/
path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"),
Paths: Object.freeze({
// The path to the latest version of sessionstore written during a clean
// shutdown. After startup, it is renamed `cleanBackup`.
clean: Path.join(profileDir, "sessionstore.js"),
// The path at which we store the previous version of `clean`. Updated
// whenever we successfully load from `clean`.
cleanBackup: Path.join(profileDir, "sessionstore-backups", "previous.js"),
// The directory containing all sessionstore backups.
backups: Path.join(profileDir, "sessionstore-backups"),
// The path to the latest version of the sessionstore written
// during runtime. Generally, this file contains more
// privacy-sensitive information than |clean|, and this file is
// therefore removed during clean shutdown. This file is designed to protect
// against crashes / sudden shutdown.
recovery: Path.join(profileDir, "sessionstore-backups", "recovery.js"),
// The path to the previous version of the sessionstore written
// during runtime (e.g. 15 seconds before recovery). In case of a
// clean shutdown, this file is removed. Generally, this file
// contains more privacy-sensitive information than |clean|, and
// this file is therefore removed during clean shutdown. This
// file is designed to protect against crashes that are nasty
// enough to corrupt |recovery|.
recoveryBackup: Path.join(profileDir, "sessionstore-backups", "recovery.bak"),
// The path to a backup created during an upgrade of Firefox.
// Having this backup protects the user essentially from bugs in
// Firefox or add-ons, especially for users of Nightly. This file
// does not contain any information more sensitive than |clean|.
upgradeBackupPrefix: Path.join(profileDir, "sessionstore-backups", "upgrade.js-"),
// The path to the backup of the version of the session store used
// during the latest upgrade of Firefox. During load/recovery,
// this file should be used if both |path|, |backupPath| and
// |latestStartPath| are absent/incorrect. May be "" if no
// upgrade backup has ever been performed. This file does not
// contain any information more sensitive than |clean|.
get upgradeBackup() {
let latestBackupID = SessionFileInternal.latestUpgradeBackupID;
if (!latestBackupID) {
return "";
}
return this.upgradeBackupPrefix + latestBackupID;
},
// The path to a backup created during an upgrade of Firefox.
// Having this backup protects the user essentially from bugs in
// Firefox, especially for users of Nightly.
get nextUpgradeBackup() {
return this.upgradeBackupPrefix + Services.appinfo.platformBuildID;
},
/**
* The path to sessionstore.bak
* The order in which to search for a valid sessionstore file.
*/
backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
get loadOrder() {
// If `clean` exists and has been written without corruption during
// the latest shutdown, we need to use it.
//
// Otherwise, `recovery` and `recoveryBackup` represent the most
// recent state of the session store.
//
// Finally, if nothing works, fall back to the last known state
// that can be loaded (`cleanBackup`) or, if available, to the
// backup performed during the latest upgrade.
let order = ["clean",
"recovery",
"recoveryBackup",
"cleanBackup"];
if (SessionFileInternal.latestUpgradeBackupID) {
// We have an upgradeBackup
order.push("upgradeBackup");
}
return order;
},
}),
// The ID of the latest version of Gecko for which we have an upgrade backup
// or |undefined| if no upgrade backup was ever written.
get latestUpgradeBackupID() {
try {
return Services.prefs.getCharPref(PREF_UPGRADE_BACKUP);
} catch (ex) {
return undefined;
}
},
/**
* The promise returned by the latest call to |write|.
@ -125,32 +205,57 @@ let SessionFileInternal = {
*/
_isClosed: false,
read: function () {
// We must initialize the worker during startup so it will be ready to
// perform the final write. If shutdown happens soon after startup and
// the worker has not started yet we may not write.
// See Bug 964531.
SessionWorker.post("init");
return Task.spawn(function*() {
for (let filename of [this.path, this.backupPath]) {
read: Task.async(function* () {
let result;
// Attempt to load by order of priority from the various backups
for (let key of this.Paths.loadOrder) {
let corrupted = false;
let exists = true;
try {
let path = this.Paths[key];
let startMs = Date.now();
let data = yield OS.File.read(filename, { encoding: "utf-8" });
Telemetry.getHistogramById("FX_SESSION_RESTORE_READ_FILE_MS")
.add(Date.now() - startMs);
return data;
let source = yield OS.File.read(path, { encoding: "utf-8" });
let parsed = JSON.parse(source);
result = {
origin: key,
source: source,
parsed: parsed
};
Telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").
add(false);
Telemetry.getHistogramById("FX_SESSION_RESTORE_READ_FILE_MS").
add(Date.now() - startMs);
break;
} catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
// Ignore exceptions about non-existent files.
exists = false;
} catch (ex if ex instanceof SyntaxError) {
// File is corrupted, try next file
corrupted = true;
} finally {
if (exists) {
Telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").
add(corrupted);
}
}
}
if (!result) {
// If everything fails, start with an empty session.
result = {
origin: "empty",
source: "",
parsed: null
};
}
return "";
}.bind(this));
},
// Initialize the worker to let it handle backups and also
// as a workaround for bug 964531.
SessionWorker.post("init", [
result.origin,
this.Paths,
]);
return result;
}),
gatherTelemetry: function(aStateString) {
return Task.spawn(function() {
@ -173,20 +278,32 @@ let SessionFileInternal = {
isFinalWrite = this._isClosed = true;
}
return this._latestWrite = Task.spawn(function task() {
return this._latestWrite = Task.spawn(function* task() {
TelemetryStopwatch.start("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
try {
let promise = SessionWorker.post("write", [aData]);
let performShutdownCleanup = isFinalWrite &&
!sessionStartup.isAutomaticRestoreEnabled();
let options = {
isFinalWrite: isFinalWrite,
performShutdownCleanup: performShutdownCleanup
};
let promise = SessionWorker.post("write", [aData, options]);
// At this point, we measure how long we stop the main thread
TelemetryStopwatch.finish("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
// Now wait for the result and record how long the write took
let msg = yield promise;
this._recordTelemetry(msg.telemetry);
if (msg.ok && msg.ok.upgradeBackup) {
// We have just completed a backup-on-upgrade, store the information
// in preferences.
Services.prefs.setCharPref(PREF_UPGRADE_BACKUP, Services.appinfo.platformBuildID);
}
} catch (ex) {
TelemetryStopwatch.cancel("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
console.error("Could not write session state file ", this.path, ex);
console.error("Could not write session state file ", ex, ex.stack);
}
if (isFinalWrite) {
@ -195,16 +312,8 @@ let SessionFileInternal = {
}.bind(this));
},
createBackupCopy: function (ext) {
return SessionWorker.post("createBackupCopy", [ext]);
},
removeBackupCopy: function (ext) {
return SessionWorker.post("removeBackupCopy", [ext]);
},
wipe: function () {
SessionWorker.post("wipe");
return SessionWorker.post("wipe");
},
_recordTelemetry: function(telemetry) {
@ -224,31 +333,6 @@ let SessionFileInternal = {
}
};
// Interface to a dedicated thread handling I/O
let SessionWorker = (function () {
let worker = new PromiseWorker("resource:///modules/sessionstore/SessionWorker.js",
OS.Shared.LOG.bind("SessionWorker"));
return {
post: function post(...args) {
let promise = worker.post.apply(worker, args);
return promise.then(
null,
function onError(error) {
// Decode any serialized error
if (error instanceof PromiseWorker.WorkerError) {
throw OS.File.Error.fromMsg(error.data);
}
// Extract something meaningful from ErrorEvent
if (error instanceof ErrorEvent) {
throw new Error(error.message, error.filename, error.lineno);
}
throw error;
}
);
}
};
})();
// Ensure that we can write sessionstore.js cleanly before the profile
// becomes unaccessible.
AsyncShutdown.profileBeforeChange.addBlocker(

View File

@ -464,41 +464,10 @@ let SessionStoreInternal = {
this._prefBranch.getBoolPref("sessionstore.resume_session_once"))
this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
this._performUpgradeBackup();
TelemetryStopwatch.finish("FX_SESSION_RESTORE_STARTUP_INIT_SESSION_MS");
return state;
},
/**
* If this is the first time we launc this build of Firefox,
* backup sessionstore.js.
*/
_performUpgradeBackup: function ssi_performUpgradeBackup() {
// Perform upgrade backup, if necessary
const PREF_UPGRADE = "sessionstore.upgradeBackup.latestBuildID";
let buildID = Services.appinfo.platformBuildID;
let latestBackup = this._prefBranch.getCharPref(PREF_UPGRADE);
if (latestBackup == buildID) {
return Promise.resolve();
}
return Task.spawn(function task() {
try {
// Perform background backup
yield SessionFile.createBackupCopy("-" + buildID);
this._prefBranch.setCharPref(PREF_UPGRADE, buildID);
// In case of success, remove previous backup.
yield SessionFile.removeBackupCopy("-" + latestBackup);
} catch (ex) {
debug("Could not perform upgrade backup " + ex);
debug(ex.stack);
}
}.bind(this));
},
_initPrefs : function() {
this._prefBranch = Services.prefs.getBranch("browser.");

View File

@ -53,6 +53,39 @@ self.onmessage = function (msg) {
});
};
// The various possible states
/**
* We just started (we haven't written anything to disk yet) from
* `Paths.clean`. The backup directory may not exist.
*/
const STATE_CLEAN = "clean";
/**
* We know that `Paths.recovery` is good, either because we just read
* it (we haven't written anything to disk yet) or because have
* already written once to `Paths.recovery` during this session.
* `Paths.clean` is absent or invalid. The backup directory exists.
*/
const STATE_RECOVERY = "recovery";
/**
* We just started from `Paths.recoverBackupy` (we haven't written
* anything to disk yet). Both `Paths.clean` and `Paths.recovery` are
* absent or invalid. The backup directory exists.
*/
const STATE_RECOVERY_BACKUP = "recoveryBackup";
/**
* We just started from `Paths.upgradeBackup` (we haven't written
* anything to disk yet). Both `Paths.clean`, `Paths.recovery` and
* `Paths.recoveryBackup` are absent or invalid. The backup directory
* exists.
*/
const STATE_UPGRADE_BACKUP = "upgradeBackup";
/**
* We just started without a valid session store file (we haven't
* written anything to disk yet). The backup directory may not exist.
*/
const STATE_EMPTY = "empty";
let Agent = {
// Boolean that tells whether we already made a
// call to write(). We will only attempt to move
@ -60,49 +93,154 @@ let Agent = {
// first write.
hasWrittenState: false,
// The path to sessionstore.js
path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"),
// The path to sessionstore.bak
backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
// Path to the files used by the SessionWorker
Paths: null,
/**
* NO-OP to start the worker.
* The current state of the worker, as one of the following strings:
* - "permanent", once the first write has been completed;
* - "empty", before the first write has been completed,
* if we have started without any sessionstore;
* - one of "clean", "recovery", "recoveryBackup", "cleanBackup",
* "upgradeBackup", before the first write has been completed, if
* we have started by loading the corresponding file.
*/
init: function () {
state: null,
/**
* Initialize (or reinitialize) the worker
*
* @param {string} origin Which of sessionstore.js or its backups
* was used. One of the `STATE_*` constants defined above.
* @param {object} paths The paths at which to find the various files.
*/
init: function (origin, paths) {
if (!(origin in paths || origin == STATE_EMPTY)) {
throw new TypeError("Invalid origin: " + origin);
}
this.state = origin;
this.Paths = paths;
this.upgradeBackupNeeded = paths.nextUpgradeBackup != paths.upgradeBackup;
return {result: true};
},
/**
* Write the session to disk.
* Write the session to disk, performing any necessary backup
* along the way.
*
* @param {string} stateString The state to write to disk.
* @param {object} options
* - performShutdownCleanup If |true|, we should
* perform shutdown-time cleanup to ensure that private data
* is not left lying around;
* - isFinalWrite If |true|, write to Paths.clean instead of
* Paths.recovery
*/
write: function (stateString) {
write: function (stateString, options = {}) {
let exn;
let telemetry = {};
if (!this.hasWrittenState) {
let data = Encoder.encode(stateString);
let startWriteMs, stopWriteMs;
try {
let startMs = Date.now();
File.move(this.path, this.backupPath);
telemetry.FX_SESSION_RESTORE_BACKUP_FILE_MS = Date.now() - startMs;
} catch (ex if isNoSuchFileEx(ex)) {
// Ignore exceptions about non-existent files.
if (this.state == STATE_CLEAN || this.state == STATE_EMPTY) {
// The backups directory may not exist yet. In all other cases,
// we have either already read from or already written to this
// directory, so we are satisfied that it exists.
File.makeDir(this.Paths.backups);
}
if (this.state == STATE_CLEAN) {
// Move $Path.clean out of the way, to avoid any ambiguity as
// to which file is more recent.
File.move(this.Paths.clean, this.Paths.cleanBackup);
}
startWriteMs = Date.now();
if (options.isFinalWrite) {
// We are shutting down. At this stage, we know that
// $Paths.clean is either absent or corrupted. If it was
// originally present and valid, it has been moved to
// $Paths.cleanBackup a long time ago. We can therefore write
// with the guarantees that we erase no important data.
File.writeAtomic(this.Paths.clean, data, {
tmpPath: this.Paths.clean + ".tmp"
});
} else if (this.state == STATE_RECOVERY) {
// At this stage, either $Paths.recovery was written >= 15
// seconds ago during this session or we have just started
// from $Paths.recovery left from the previous session. Either
// way, $Paths.recovery is good. We can move $Path.backup to
// $Path.recoveryBackup without erasing a good file with a bad
// file.
File.writeAtomic(this.Paths.recovery, data, {
tmpPath: this.Paths.recovery + ".tmp",
backupTo: this.Paths.recoveryBackup
});
} else {
// In other cases, either $Path.recovery is not necessary, or
// it doesn't exist or it has been corrupted. Regardless,
// don't backup $Path.recovery.
File.writeAtomic(this.Paths.recovery, data, {
tmpPath: this.Paths.recovery + ".tmp"
});
}
stopWriteMs = Date.now();
} catch (ex) {
// Throw the exception after we wrote the state to disk
// so that the backup can't interfere with the actual write.
exn = ex;
// Don't throw immediately
exn = exn || ex;
}
this.hasWrittenState = true;
// If necessary, perform an upgrade backup
let upgradeBackupComplete = false;
if (this.upgradeBackupNeeded
&& (this.state == STATE_CLEAN || this.state == STATE_UPGRADE_BACKUP)) {
try {
// If we loaded from `clean`, the file has since then been renamed to `cleanBackup`.
let path = this.state == STATE_CLEAN ? this.Paths.cleanBackup : this.Paths.upgradeBackup;
File.copy(path, this.Paths.nextUpgradeBackup);
this.upgradeBackupNeeded = false;
upgradeBackupComplete = true;
} catch (ex) {
// Don't throw immediately
exn = exn || ex;
}
}
let ret = this._write(stateString, telemetry);
if (options.performShutdownCleanup && !exn) {
// During shutdown, if auto-restore is disabled, we need to
// remove possibly sensitive data that has been stored purely
// for crash recovery. Note that this slightly decreases our
// ability to recover from OS-level/hardware-level issue.
// If an exception was raised, we assume that we still need
// these files.
File.remove(this.Paths.recoveryBackup);
File.remove(this.Paths.recovery);
}
this.state = STATE_RECOVERY;
if (exn) {
throw exn;
}
return ret;
return {
result: {
upgradeBackup: upgradeBackupComplete
},
telemetry: {
FX_SESSION_RESTORE_WRITE_FILE_MS: stopWriteMs - startWriteMs,
FX_SESSION_RESTORE_FILE_SIZE_BYTES: data.byteLength,
}
};
},
/**
@ -115,66 +253,79 @@ let Agent = {
return Statistics.collect(stateString);
},
/**
* Write a stateString to disk
*/
_write: function (stateString, telemetry = {}) {
let bytes = Encoder.encode(stateString);
let startMs = Date.now();
let result = File.writeAtomic(this.path, bytes, {tmpPath: this.path + ".tmp"});
telemetry.FX_SESSION_RESTORE_WRITE_FILE_MS = Date.now() - startMs;
telemetry.FX_SESSION_RESTORE_FILE_SIZE_BYTES = bytes.byteLength;
return {result: result, telemetry: telemetry};
},
/**
* Creates a copy of sessionstore.js.
*/
createBackupCopy: function (ext) {
try {
return {result: File.copy(this.path, this.backupPath + ext)};
} catch (ex if isNoSuchFileEx(ex)) {
// Ignore exceptions about non-existent files.
return {result: true};
}
},
/**
* Removes a backup copy.
*/
removeBackupCopy: function (ext) {
try {
return {result: File.remove(this.backupPath + ext)};
} catch (ex if isNoSuchFileEx(ex)) {
// Ignore exceptions about non-existent files.
return {result: true};
}
},
/**
* Wipes all files holding session data from disk.
*/
wipe: function () {
let exn;
// Erase session state file
// Don't stop immediately in case of error.
let exn = null;
// Erase main session state file
try {
File.remove(this.path);
} catch (ex if isNoSuchFileEx(ex)) {
// Ignore exceptions about non-existent files.
File.remove(this.Paths.clean);
} catch (ex) {
// Don't stop immediately.
exn = ex;
exn = exn || ex;
}
// Erase any backup, any file named "sessionstore.bak[-buildID]".
let iter = new File.DirectoryIterator(OS.Constants.Path.profileDir);
for (let entry in iter) {
if (!entry.isDir && entry.path.startsWith(this.backupPath)) {
// Wipe the Session Restore directory
try {
this._wipeFromDir(this.Paths.backups, null);
} catch (ex) {
exn = exn || ex;
}
try {
File.removeDir(this.Paths.backups);
} catch (ex) {
exn = exn || ex;
}
// Wipe legacy Ression Restore files from the profile directory
try {
this._wipeFromDir(OS.Constants.Path.profileDir, "sessionstore.bak");
} catch (ex) {
exn = exn || ex;
}
this.state = STATE_EMPTY;
if (exn) {
throw exn;
}
return { result: true };
},
/**
* Wipe a number of files from a directory.
*
* @param {string} path The directory.
* @param {string|null} prefix If provided, only remove files whose
* name starts with a specific prefix.
*/
_wipeFromDir: function(path, prefix) {
// Sanity check
if (typeof prefix == "undefined" || prefix == "") {
throw new TypeError();
}
let exn = null;
let iterator = new File.DirectoryIterator(path);
if (!iterator.exists()) {
return;
}
for (let entry in iterator) {
if (entry.isDir) {
continue;
}
if (!prefix || entry.name.startsWith(prefix)) {
try {
File.remove(entry.path);
} catch (ex) {
// Don't stop immediately.
// Don't stop immediately
exn = exn || ex;
}
}
@ -183,9 +334,7 @@ let Agent = {
if (exn) {
throw exn;
}
return {result: true};
}
},
};
function isNoSuchFileEx(aReason) {

View File

@ -0,0 +1,43 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Interface to a dedicated thread handling I/O
*/
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
this.EXPORTED_SYMBOLS = ["SessionWorker"];
this.SessionWorker = (function () {
let worker = new PromiseWorker("resource:///modules/sessionstore/SessionWorker.js",
OS.Shared.LOG.bind("SessionWorker"));
return {
post: function post(...args) {
let promise = worker.post.apply(worker, args);
return promise.then(
null,
function onError(error) {
// Decode any serialized error
if (error instanceof PromiseWorker.WorkerError) {
throw OS.File.Error.fromMsg(error.data);
}
// Extract something meaningful from ErrorEvent
if (error instanceof ErrorEvent) {
throw new Error(error.message, error.filename, error.lineno);
}
throw error;
}
);
}
};
})();

View File

@ -27,6 +27,7 @@ EXTRA_JS_MODULES = [
'SessionMigration.jsm',
'SessionStorage.jsm',
'SessionWorker.js',
'SessionWorker.jsm',
'TabAttributes.jsm',
'TabState.jsm',
'TabStateCache.jsm',

View File

@ -57,6 +57,11 @@ function debug(aMsg) {
aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n");
Services.console.logStringMessage(aMsg);
}
function warning(aMsg, aException) {
let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
consoleMsg.init(aMsg, aException.fileName, null, aException.lineNumber, 0, Ci.nsIScriptError.warningFlag, "component javascript");
Services.console.logMessage(consoleMsg);
}
let gOnceInitializedDeferred = Promise.defer();
@ -107,27 +112,39 @@ SessionStartup.prototype = {
/**
* Complete initialization once the Session File has been read
*
* @param stateString
* string The Session State string read from disk
* @param source The Session State string read from disk.
* @param parsed The object obtained by parsing |source| as JSON.
*/
_onSessionFileRead: function (stateString) {
_onSessionFileRead: function ({source, parsed}) {
this._initialized = true;
// Let observers modify the state before it is used
let supportsStateString = this._createSupportsString(stateString);
let supportsStateString = this._createSupportsString(source);
Services.obs.notifyObservers(supportsStateString, "sessionstore-state-read", "");
stateString = supportsStateString.data;
let stateString = supportsStateString.data;
if (stateString != source) {
// The session has been modified by an add-on, reparse.
try {
this._initialState = JSON.parse(stateString);
} catch (ex) {
// That's not very good, an add-on has rewritten the initial
// state to something that won't parse.
warning("Observer rewrote the state to something that won't parse", ex);
}
} else {
// No need to reparse
this._initialState = parsed;
}
if (this._initialState == null) {
// No valid session found.
if (!stateString) {
this._sessionType = Ci.nsISessionStartup.NO_SESSION;
Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
gOnceInitializedDeferred.resolve();
return;
}
this._initialState = this._parseStateString(stateString);
let shouldResumeSessionOnce = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
let shouldResumeSession = shouldResumeSessionOnce ||
Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
@ -194,29 +211,6 @@ SessionStartup.prototype = {
});
},
/**
* Convert the Session State string into a state object
*
* @param stateString
* string The Session State string read from disk
* @returns {State} a Session State object
*/
_parseStateString: function (stateString) {
let state = null;
let corruptFile = false;
try {
state = JSON.parse(stateString);
} catch (ex) {
debug("The session file contained un-parse-able JSON: " + ex);
corruptFile = true;
}
Services.telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").add(corruptFile);
return state;
},
/**
* Handle notifications
*/

View File

@ -60,6 +60,7 @@ support-files =
[browser_aboutPrivateBrowsing.js]
[browser_aboutSessionRestore.js]
[browser_attributes.js]
[browser_backup_recovery.js]
[browser_broadcast.js]
[browser_capabilities.js]
[browser_cleaner.js]
@ -180,7 +181,6 @@ skip-if = true # Needs to be rewritten as Marionette test, bug 995916
[browser_739805.js]
[browser_819510_perwindowpb.js]
skip-if = os == "linux" # Intermittent failures, bug 894063
[browser_833286_atomic_backup.js]
# Disabled for frequent intermittent failures
[browser_464620_a.js]

View File

@ -3,63 +3,56 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** Private Browsing Test for Bug 394759 **/
function test() {
waitForExplicitFinish();
let windowsToClose = [];
let closedWindowCount = 0;
// Prevent VM timers issues, cache now and increment it manually.
let now = Date.now();
const TESTS = [
let closedWindowCount = 0;
// Prevent VM timers issues, cache now and increment it manually.
let now = Date.now();
const TESTS = [
{ url: "about:config",
key: "bug 394759 Non-PB",
value: "uniq" + (++now) },
{ url: "about:mozilla",
key: "bug 394759 PB",
value: "uniq" + (++now) },
];
];
registerCleanupFunction(function() {
Services.prefs.clearUserPref("browser.sessionstore.interval");
windowsToClose.forEach(function(win) {
win.close();
});
});
function testOpenCloseWindow(aIsPrivate, aTest, aCallback) {
whenNewWindowLoaded({ private: aIsPrivate }, function(win) {
whenBrowserLoaded(win.gBrowser.selectedBrowser, function() {
executeSoon(function() {
function promiseTestOpenCloseWindow(aIsPrivate, aTest) {
return Task.spawn(function*() {
let win = yield promiseNewWindowLoaded({ "private": aIsPrivate });
win.gBrowser.selectedBrowser.loadURI(aTest.url);
yield promiseBrowserLoaded(win.gBrowser.selectedBrowser);
yield Promise.resolve();
// Mark the window with some unique data to be restored later on.
ss.setWindowValue(win, aTest.key, aTest.value);
// Close.
win.close();
aCallback();
yield promiseWindowClosed(win);
});
});
win.gBrowser.selectedBrowser.loadURI(aTest.url);
});
}
}
function testOnWindow(aIsPrivate, aValue, aCallback) {
whenNewWindowLoaded({ private: aIsPrivate }, function(win) {
windowsToClose.push(win);
executeSoon(function() checkClosedWindows(aIsPrivate, aValue, aCallback));
function promiseTestOnWindow(aIsPrivate, aValue) {
return Task.spawn(function*() {
let win = yield promiseNewWindowLoaded({ "private": aIsPrivate });
yield promiseCheckClosedWindows(aIsPrivate, aValue);
registerCleanupFunction(() => promiseWindowClosed(win));
});
}
}
function checkClosedWindows(aIsPrivate, aValue, aCallback) {
function promiseCheckClosedWindows(aIsPrivate, aValue) {
return Task.spawn(function*() {
let data = JSON.parse(ss.getClosedWindowData())[0];
is(ss.getClosedWindowCount(), 1, "Check the closed window count");
is(ss.getClosedWindowCount(), 1, "Check that the closed window count hasn't changed");
ok(JSON.stringify(data).indexOf(aValue) > -1,
"Check the closed window data was stored correctly");
aCallback();
}
});
}
function setupBlankState(aCallback) {
function promiseBlankState() {
return Task.spawn(function*() {
// Set interval to a large time so state won't be written while we setup
// environment.
Services.prefs.setIntPref("browser.sessionstore.interval", 100000);
registerCleanupFunction(() => Services.prefs.clearUserPref("browser.sessionstore.interval"));
// Set up the browser in a blank state. Popup windows in previous tests
// result in different states on different platforms.
@ -70,40 +63,39 @@ function test() {
}],
_closedWindows: []
});
ss.setBrowserState(blankState);
// Wait for the sessionstore.js file to be written before going on.
// Note: we don't wait for the complete event, since if asyncCopy fails we
// would timeout.
waitForSaveState(function(writing) {
ok(writing, "sessionstore.js is being written");
yield forceSaveState();
closedWindowCount = ss.getClosedWindowCount();
is(closedWindowCount, 0, "Correctly set window count");
executeSoon(aCallback);
});
// Remove the sessionstore.js file before setting the interval to 0
let profilePath = Services.dirsvc.get("ProfD", Ci.nsIFile);
let sessionStoreJS = profilePath.clone();
sessionStoreJS.append("sessionstore.js");
if (sessionStoreJS.exists())
sessionStoreJS.remove(false);
info("sessionstore.js was correctly removed: " + (!sessionStoreJS.exists()));
yield SessionFile.wipe();
// Make sure that sessionstore.js can be forced to be created by setting
// the interval pref to 0.
Services.prefs.setIntPref("browser.sessionstore.interval", 0);
}
setupBlankState(function() {
testOpenCloseWindow(false, TESTS[0], function() {
testOpenCloseWindow(true, TESTS[1], function() {
testOnWindow(false, TESTS[0].value, function() {
testOnWindow(true, TESTS[0].value, finish);
});
});
});
yield forceSaveState();
});
}
add_task(function* init() {
while (ss.getClosedWindowCount() > 0) {
ss.forgetClosedWindow(0);
}
while (ss.getClosedTabCount(window) > 0) {
ss.forgetClosedTab(window, 0);
}
});
add_task(function* main() {
yield promiseTestOpenCloseWindow(false, TESTS[0]);
yield promiseTestOpenCloseWindow(true, TESTS[1]);
yield promiseTestOnWindow(false, TESTS[0].value);
yield promiseTestOnWindow(true, TESTS[0].value);
});

View File

@ -13,7 +13,7 @@ const PASS = "pwd-" + Math.random();
/**
* Bug 454908 - Don't save/restore values of password fields.
*/
add_task(function test_dont_save_passwords() {
add_task(function* test_dont_save_passwords() {
// Make sure we do save form data.
Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
@ -40,13 +40,12 @@ add_task(function test_dont_save_passwords() {
is(passwd, "", "password wasn't saved/restored");
// Write to disk and read our file.
yield SessionSaver.run();
let path = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
let data = yield OS.File.read(path);
let state = new TextDecoder().decode(data);
yield forceSaveState();
yield promiseForEachSessionRestoreFile((state, key) =>
// Ensure that we have not saved our password.
ok(!state.contains(PASS), "password has not been written to file " + key)
);
// Ensure that sessionstore.js doesn't contain our password.
is(state.indexOf(PASS), -1, "password has not been written to disk");
// Cleanup.
gBrowser.removeTab(tab);

View File

@ -36,7 +36,7 @@ add_task(function* new_window() {
yield promiseWindowClosed(newWin);
newWin = null;
let state = JSON.parse((yield promiseSaveFileContents()));
let state = JSON.parse((yield promiseRecoveryFileContents()));
is(state.windows.length, 2,
"observe1: 2 windows in data written to disk");
is(state._closedWindows.length, 0,
@ -60,7 +60,7 @@ add_task(function* new_tab() {
try {
newTab = gBrowser.addTab("about:mozilla");
let state = JSON.parse((yield promiseSaveFileContents()));
let state = JSON.parse((yield promiseRecoveryFileContents()));
is(state.windows.length, 1,
"observe2: 1 window in data being written to disk");
is(state._closedWindows.length, 1,

View File

@ -164,7 +164,7 @@ function waitForWindowClose(aWin, aCallback) {
}
function forceWriteState(aCallback) {
return promiseSaveFileContents().then(function(data) {
return promiseRecoveryFileContents().then(function(data) {
aCallback(JSON.parse(data));
});
}

View File

@ -1,99 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// This tests are for a sessionstore.js atomic backup.
// Each test will wait for a write to the Session Store
// before executing.
let tmp = {};
Cu.import("resource://gre/modules/osfile.jsm", tmp);
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", tmp);
const {OS, SessionFile} = tmp;
const PREF_SS_INTERVAL = "browser.sessionstore.interval";
// Full paths for sessionstore.js and sessionstore.bak.
const path = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
const backupPath = OS.Path.join(OS.Constants.Path.profileDir,
"sessionstore.bak");
// A text decoder.
let gDecoder = new TextDecoder();
// Global variables that contain sessionstore.js and sessionstore.bak data for
// comparison between tests.
let gSSData;
let gSSBakData;
add_task(function* testAfterFirstWrite() {
// Ensure sessionstore.bak is not created. We start with a clean
// profile so there was nothing to move to sessionstore.bak before
// initially writing sessionstore.js
let ssExists = yield OS.File.exists(path);
let ssBackupExists = yield OS.File.exists(backupPath);
ok(ssExists, "sessionstore.js should exist.");
ok(!ssBackupExists, "sessionstore.bak should not have been created, yet");
// Save sessionstore.js data to compare to the sessionstore.bak data in the
// next test.
let array = yield OS.File.read(path);
gSSData = gDecoder.decode(array);
// Manually move to the backup since the first write has already happened
// and a backup would not be triggered again.
yield OS.File.move(path, backupPath);
yield forceSaveState();
});
add_task(function* testReadBackup() {
// Ensure sessionstore.bak is finally created.
let ssExists = yield OS.File.exists(path);
let ssBackupExists = yield OS.File.exists(backupPath);
ok(ssExists, "sessionstore.js exists.");
ok(ssBackupExists, "sessionstore.bak should now be created.");
// Read sessionstore.bak data.
let array = yield OS.File.read(backupPath);
gSSBakData = gDecoder.decode(array);
// Make sure that the sessionstore.bak is identical to the last
// sessionstore.js.
is(gSSBakData, gSSData, "sessionstore.js is backed up correctly.");
// Read latest sessionstore.js.
array = yield OS.File.read(path);
gSSData = gDecoder.decode(array);
// Read sessionstore.js with SessionFile.read.
let ssDataRead = yield SessionFile.read();
is(ssDataRead, gSSData, "SessionFile.read read sessionstore.js correctly.");
// Remove sessionstore.js to test fallback onto sessionstore.bak.
yield OS.File.remove(path);
ssExists = yield OS.File.exists(path);
ok(!ssExists, "sessionstore.js should be removed now.");
// Read sessionstore.bak with SessionFile.read.
ssDataRead = yield SessionFile.read();
is(ssDataRead, gSSBakData,
"SessionFile.read read sessionstore.bak correctly.");
yield forceSaveState();
});
add_task(function* testBackupUnchanged() {
// Ensure sessionstore.bak is backed up only once.
// Read sessionstore.bak data.
let array = yield OS.File.read(backupPath);
let ssBakData = gDecoder.decode(array);
// Ensure the sessionstore.bak did not change.
is(ssBakData, gSSBakData, "sessionstore.bak is unchanged.");
});
add_task(function* cleanup() {
// Cleaning up after the test: removing the sessionstore.bak file.
yield OS.File.remove(backupPath);
});

View File

@ -0,0 +1,132 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// This tests are for a sessionstore.js atomic backup.
// Each test will wait for a write to the Session Store
// before executing.
let OS = Cu.import("resource://gre/modules/osfile.jsm", {}).OS;
let {File, Constants, Path} = OS;
const PREF_SS_INTERVAL = "browser.sessionstore.interval";
const Paths = SessionFile.Paths;
// A text decoder.
let gDecoder = new TextDecoder();
// Global variables that contain sessionstore.js and sessionstore.bak data for
// comparison between tests.
let gSSData;
let gSSBakData;
function promiseRead(path) {
return File.read(path, {encoding: "utf-8"});
}
add_task(function* init() {
// Make sure that we are not racing with SessionSaver's time based
// saves.
Services.prefs.setIntPref(PREF_SS_INTERVAL, 10000000);
registerCleanupFunction(() => Services.prefs.clearUserPref(PREF_SS_INTERVAL));
});
add_task(function* test_creation() {
let OLD_BACKUP = Path.join(Constants.Path.profileDir, "sessionstore.bak");
let OLD_UPGRADE_BACKUP = Path.join(Constants.Path.profileDir, "sessionstore.bak-0000000");
yield File.writeAtomic(OLD_BACKUP, "sessionstore.bak");
yield File.writeAtomic(OLD_UPGRADE_BACKUP, "sessionstore upgrade backup");
yield SessionFile.wipe();
yield SessionFile.read(); // Reinitializes SessionFile
for (let k of Paths.loadOrder) {
ok(!(yield File.exists(Paths[k])), "After wipe " + k + " sessionstore file doesn't exist");
}
ok(!(yield File.exists(OLD_BACKUP)), "After wipe, old backup doesn't exist");
ok(!(yield File.exists(OLD_UPGRADE_BACKUP)), "After wipe, old upgrade backup doesn't exist");
let URL_BASE = "http://example.com/?atomic_backup_test_creation=" + Math.random();
let URL = URL_BASE + "?first_write";
let tab = gBrowser.addTab(URL);
info("Testing situation after a single write");
yield promiseBrowserLoaded(tab.linkedBrowser);
SyncHandlers.get(tab.linkedBrowser).flush();
yield SessionSaver.run();
ok((yield File.exists(Paths.recovery)), "After write, recovery sessionstore file exists again");
ok(!(yield File.exists(Paths.recoveryBackup)), "After write, recoveryBackup sessionstore doesn't exist");
ok((yield promiseRead(Paths.recovery)).indexOf(URL) != -1, "Recovery sessionstore file contains the required tab");
ok(!(yield File.exists(Paths.clean)), "After first write, clean shutdown sessionstore doesn't exist, since we haven't shutdown yet");
info("Testing situation after a second write");
let URL2 = URL_BASE + "?second_write";
tab.linkedBrowser.loadURI(URL2);
yield promiseBrowserLoaded(tab.linkedBrowser);
SyncHandlers.get(tab.linkedBrowser).flush();
yield SessionSaver.run();
ok((yield File.exists(Paths.recovery)), "After second write, recovery sessionstore file still exists");
ok((yield promiseRead(Paths.recovery)).indexOf(URL2) != -1, "Recovery sessionstore file contains the latest url");
ok((yield File.exists(Paths.recoveryBackup)), "After write, recoveryBackup sessionstore now exists");
let backup = yield promiseRead(Paths.recoveryBackup);
ok(backup.indexOf(URL2) == -1, "Recovery backup doesn't contain the latest url");
ok(backup.indexOf(URL) != -1, "Recovery backup contains the original url");
ok(!(yield File.exists(Paths.clean)), "After first write, clean shutdown sessinstore doesn't exist, since we haven't shutdown yet");
info("Reinitialize, ensure that we haven't leaked sensitive files");
yield SessionFile.read(); // Reinitializes SessionFile
yield SessionSaver.run();
ok(!(yield File.exists(Paths.clean)), "After second write, clean shutdown sessonstore doesn't exist, since we haven't shutdown yet");
ok(!(yield File.exists(Paths.upgradeBackup)), "After second write, clean shutdwn sessionstore doesn't exist, since we haven't shutdown yet");
ok(!(yield File.exists(Paths.nextUpgradeBackup)), "After second write, clean sutdown sessionstore doesn't exist, since we haven't shutdown yet");
gBrowser.removeTab(tab);
yield SessionFile.wipe();
});
let promiseSource = Task.async(function*(name) {
let URL = "http://example.com/?atomic_backup_test_recovery=" + Math.random() + "&name=" + name;
let tab = gBrowser.addTab(URL);
yield promiseBrowserLoaded(tab.linkedBrowser);
SyncHandlers.get(tab.linkedBrowser).flush();
yield SessionSaver.run();
gBrowser.removeTab(tab);
let SOURCE = yield promiseRead(Paths.recovery);
yield SessionFile.wipe();
return SOURCE;
});
add_task(function* test_recovery() {
yield SessionFile.wipe();
info("Attempting to recover from the recovery file");
let SOURCE = yield promiseSource("Paths.recovery");
// Ensure that we can recover from Paths.recovery
yield File.makeDir(Paths.backups);
yield File.writeAtomic(Paths.recovery, SOURCE);
is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the recovery file");
yield SessionFile.wipe();
info("Corrupting recovery file, attempting to recover from recovery backup");
SOURCE = yield promiseSource("Paths.recoveryBackup");
yield File.makeDir(Paths.backups);
yield File.writeAtomic(Paths.recoveryBackup, SOURCE);
yield File.writeAtomic(Paths.recovery, "<Invalid JSON>");
is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the recovery file");
});
add_task(function* test_clean() {
yield SessionFile.wipe();
let SOURCE = yield promiseSource("Paths.clean");
yield File.writeAtomic(Paths.clean, SOURCE);
yield SessionFile.read();
yield SessionSaver.run();
is((yield promiseRead(Paths.cleanBackup)), SOURCE, "After first read/write, clean shutdown file has been moved to cleanBackup");
});
add_task(function* cleanup() {
yield SessionFile.wipe();
});

View File

@ -34,10 +34,8 @@ add_task(function() {
SyncHandlers.get(tab2.linkedBrowser).flush();
info("Checking out state");
yield SessionSaver.run();
let path = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
let data = yield OS.File.read(path);
let state = new TextDecoder().decode(data);
let state = yield promiseRecoveryFileContents();
info("State: " + state);
// Ensure that sessionstore.js only knows about the public tab
ok(state.indexOf(URL_PUBLIC) != -1, "State contains public tab");

View File

@ -5,45 +5,36 @@ Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
function test() {
waitForExplicitFinish();
const Paths = SessionFile.Paths;
Task.spawn(function task() {
try {
add_task(function* init() {
// Wait until initialization is complete
yield SessionStore.promiseInitialized;
yield SessionFile.wipe();
});
add_task(function* test_upgrade_backup() {
const PREF_UPGRADE = "browser.sessionstore.upgradeBackup.latestBuildID";
let buildID = Services.appinfo.platformBuildID;
// Write state once before starting the test to
// ensure sessionstore.js writes won't happen in between.
yield forceSaveState();
// Force backup to take place with a file decided by us
info("Let's check if we create an upgrade backup");
Services.prefs.setCharPref(PREF_UPGRADE, "");
let contents = "browser_upgrade_backup.js";
let pathStore = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
yield OS.File.writeAtomic(pathStore, contents, { tmpPath: pathStore + ".tmp" });
yield SessionStore._internal._performUpgradeBackup();
is(Services.prefs.getCharPref(PREF_UPGRADE), buildID, "upgrade backup should be set (again)");
let contents = JSON.stringify({"browser_upgrade_backup.js": Math.random()});
yield OS.File.writeAtomic(Paths.clean, contents);
yield SessionFile.read(); // First call to read() initializes the SessionWorker
yield SessionFile.write(""); // First call to write() triggers the backup
let pathBackup = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak-" + Services.appinfo.platformBuildID);
is((yield OS.File.exists(pathBackup)), true, "upgrade backup file has been created");
is(Services.prefs.getCharPref(PREF_UPGRADE), buildID, "upgrade backup should be set");
let data = yield OS.File.read(pathBackup);
is(new TextDecoder().decode(data), contents, "upgrade backup contains the expected contents");
is((yield OS.File.exists(Paths.upgradeBackup)), true, "upgrade backup file has been created");
// Ensure that we don't re-backup by accident
yield OS.File.writeAtomic(pathStore, "something else entirely", { tmpPath: pathStore + ".tmp" });
yield SessionStore._internal._performUpgradeBackup();
data = yield OS.File.read(pathBackup);
is(new TextDecoder().decode(data), contents, "upgrade backup hasn't changed");
let data = yield OS.File.read(Paths.upgradeBackup);
is(contents, (new TextDecoder()).decode(data), "upgrade backup contains the expected contents");
} catch (ex) {
ok(false, "Uncaught error: " + ex + " at " + ex.stack);
} finally {
finish();
}
});
}
info("Let's check that we don't overwrite this upgrade backup");
let new_contents = JSON.stringify({"something else entirely": Math.random()});
yield OS.File.writeAtomic(Paths.clean, new_contents);
yield SessionFile.read(); // Reinitialize the SessionWorker
yield SessionFile.write(""); // Next call to write() shouldn't trigger the backup
data = yield OS.File.read(Paths.upgradeBackup);
is(contents, (new TextDecoder()).decode(data), "upgrade backup hasn't changed");
});

View File

@ -39,9 +39,11 @@ registerCleanupFunction(() => {
let tmp = {};
Cu.import("resource://gre/modules/Promise.jsm", tmp);
Cu.import("resource://gre/modules/Task.jsm", tmp);
Cu.import("resource:///modules/sessionstore/SessionStore.jsm", tmp);
Cu.import("resource:///modules/sessionstore/SessionSaver.jsm", tmp);
let {Promise, SessionStore, SessionSaver} = tmp;
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", tmp);
let {Promise, Task, SessionStore, SessionSaver, SessionFile} = tmp;
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
@ -282,13 +284,26 @@ function forceSaveState() {
return SessionSaver.run();
}
function promiseSaveFileContents() {
function promiseRecoveryFileContents() {
let promise = forceSaveState();
return promise.then(function() {
return OS.File.read(OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"), { encoding: "utf-8" });
return OS.File.read(SessionFile.Paths.recovery, { encoding: "utf-8" });
});
}
let promiseForEachSessionRestoreFile = Task.async(function*(cb) {
for (let key of SessionFile.Paths.loadOrder) {
let data = "";
try {
data = yield OS.File.read(SessionFile.Paths[key], { encoding: "utf-8" });
} catch (ex if ex instanceof OS.File.Error
&& ex.becauseNoSuchFile) {
// Ignore missing files
}
cb(data, key);
}
});
function whenBrowserLoaded(aBrowser, aCallback = next, ignoreSubFrames = true) {
aBrowser.addEventListener("load", function onLoad(event) {
if (!ignoreSubFrames || event.target == aBrowser.contentDocument) {

View File

@ -5,8 +5,7 @@ let Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/Services.jsm");
// Call a function once initialization of SessionStartup is complete
let afterSessionStartupInitialization =
function afterSessionStartupInitialization(cb) {
function afterSessionStartupInitialization(cb) {
do_print("Waiting for session startup initialization");
let observer = function() {
try {

View File

@ -1,42 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let toplevel = this;
Cu.import("resource://gre/modules/osfile.jsm");
function run_test() {
do_get_profile();
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", toplevel);
pathStore = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
run_next_test();
}
let pathStore;
function pathBackup(ext) {
return OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak" + ext);
}
// Ensure that things proceed smoothly if there is no file to back up
add_task(function test_nothing_to_backup() {
yield SessionFile.createBackupCopy("");
});
// Create a file, back it up, remove it
add_task(function test_do_backup() {
let content = "test_1";
let ext = ".upgrade_test_1";
yield OS.File.writeAtomic(pathStore, content, {tmpPath: pathStore + ".tmp"});
do_print("Ensuring that the backup is created");
yield SessionFile.createBackupCopy(ext);
do_check_true((yield OS.File.exists(pathBackup(ext))));
let data = yield OS.File.read(pathBackup(ext));
do_check_eq((new TextDecoder()).decode(data), content);
do_print("Ensuring that we can remove the backup");
yield SessionFile.removeBackupCopy(ext);
do_check_false((yield OS.File.exists(pathBackup(ext))));
});

View File

@ -1,48 +1,151 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let toplevel = this;
Cu.import("resource://gre/modules/osfile.jsm");
"use strict";
let {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
let {SessionWorker} = Cu.import("resource:///modules/sessionstore/SessionWorker.jsm", {});
let File = OS.File;
let Paths;
let SessionFile;
// We need a XULAppInfo to initialize SessionFile
let (XULAppInfo = {
vendor: "Mozilla",
name: "SessionRestoreTest",
ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}",
version: "1",
appBuildID: "2007010101",
platformVersion: "",
platformBuildID: "2007010101",
inSafeMode: false,
logConsoleErrors: true,
OS: "XPCShell",
XPCOMABI: "noarch-spidermonkey",
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIXULAppInfo,
Ci.nsIXULRuntime,
])
}) {
let XULAppInfoFactory = {
createInstance: function (outer, iid) {
if (outer != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return XULAppInfo.QueryInterface(iid);
}
};
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"),
"XULAppInfo", "@mozilla.org/xre/app-info;1",
XULAppInfoFactory);
};
function run_test() {
let profd = do_get_profile();
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", toplevel);
decoder = new TextDecoder();
pathStore = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
pathBackup = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak");
let source = do_get_file("data/sessionstore_valid.js");
source.copyTo(profd, "sessionstore.js");
run_next_test();
}
add_task(function* init() {
// Make sure that we have a profile before initializing SessionFile
let profd = do_get_profile();
SessionFile = Cu.import("resource:///modules/sessionstore/SessionFile.jsm", {}).SessionFile;
Paths = SessionFile.Paths;
let source = do_get_file("data/sessionstore_valid.js");
source.copyTo(profd, "sessionstore.js");
// Finish initialization of SessionFile
yield SessionFile.read();
});
let pathStore;
let pathBackup;
let decoder;
// Write to the store, and check that a backup is created first
add_task(function test_first_write_backup() {
let content = "test_1";
let initial_content = decoder.decode(yield OS.File.read(pathStore));
function promise_check_exist(path, shouldExist) {
return Task.spawn(function*() {
do_print("Ensuring that " + path + (shouldExist?" exists":" does not exist"));
if ((yield OS.File.exists(path)) != shouldExist) {
throw new Error("File " + path + " should " + (shouldExist?"exist":"not exist"));
}
});
}
do_check_true(!(yield OS.File.exists(pathBackup)));
yield SessionFile.write(content);
do_check_true(yield OS.File.exists(pathBackup));
function promise_check_contents(path, expect) {
return Task.spawn(function*() {
do_print("Checking whether " + path + " has the right contents");
let actual = yield OS.File.read(path, { encoding: "utf-8"});
if (actual != expect) {
throw new Error("File " + path + " should contain\n\t" + expect + "\nbut contains " + actual);
}
});
}
let backup_content = decoder.decode(yield OS.File.read(pathBackup));
do_check_eq(initial_content, backup_content);
// Write to the store, and check that it creates:
// - $Path.recovery with the new data
// - $Path.nextUpgradeBackup with the old data
add_task(function* test_first_write_backup() {
let initial_content = "initial content " + Math.random();
let new_content = "test_1 " + Math.random();
do_print("Before the first write, none of the files should exist");
yield promise_check_exist(Paths.backups, false);
yield File.makeDir(Paths.backups);
yield File.writeAtomic(Paths.clean, initial_content, { encoding: "utf-8" });
yield SessionFile.write(new_content);
do_print("After first write, a few files should have been created");
yield promise_check_exist(Paths.backups, true);
yield promise_check_exist(Paths.clean, false);
yield promise_check_exist(Paths.cleanBackup, true);
yield promise_check_exist(Paths.recovery, true);
yield promise_check_exist(Paths.recoveryBackup, false);
yield promise_check_exist(Paths.nextUpgradeBackup, true);
yield promise_check_contents(Paths.recovery, new_content);
yield promise_check_contents(Paths.nextUpgradeBackup, initial_content);
});
// Write to the store again, and check that the backup is not updated
add_task(function test_second_write_no_backup() {
let content = "test_2";
let initial_content = decoder.decode(yield OS.File.read(pathStore));
let initial_backup_content = decoder.decode(yield OS.File.read(pathBackup));
// Write to the store again, and check that
// - $Path.clean is not written
// - $Path.recovery contains the new data
// - $Path.recoveryBackup contains the previous data
add_task(function* test_second_write_no_backup() {
let new_content = "test_2 " + Math.random();
let previous_backup_content = yield File.read(Paths.recovery, { encoding: "utf-8" });
yield SessionFile.write(content);
yield OS.File.remove(Paths.cleanBackup);
let written_content = decoder.decode(yield OS.File.read(pathStore));
do_check_eq(content, written_content);
yield SessionFile.write(new_content);
yield promise_check_exist(Paths.backups, true);
yield promise_check_exist(Paths.clean, false);
yield promise_check_exist(Paths.cleanBackup, false);
yield promise_check_exist(Paths.recovery, true);
yield promise_check_exist(Paths.nextUpgradeBackup, true);
yield promise_check_contents(Paths.recovery, new_content);
yield promise_check_contents(Paths.recoveryBackup, previous_backup_content);
});
// Make sure that we create $Paths.clean and remove $Paths.recovery*
// upon shutdown
add_task(function* test_shutdown() {
let output = "test_3 " + Math.random();
yield File.writeAtomic(Paths.recovery, "I should disappear");
yield File.writeAtomic(Paths.recoveryBackup, "I should also disappear");
yield SessionWorker.post("write", [output, { isFinalWrite: true, performShutdownCleanup: true}]);
do_check_false((yield File.exists(Paths.recovery)));
do_check_false((yield File.exists(Paths.recoveryBackup)));
let input = yield File.read(Paths.clean, { encoding: "utf-8"});
do_check_eq(input, output);
let backup_content = decoder.decode(yield OS.File.read(pathBackup));
do_check_eq(initial_backup_content, backup_content);
});

View File

@ -7,7 +7,6 @@ support-files =
data/sessionstore_invalid.js
data/sessionstore_valid.js
[test_backup.js]
[test_backup_once.js]
[test_startup_nosession_async.js]
[test_startup_session_async.js]

View File

@ -124,6 +124,16 @@ TranslationUI.prototype = {
return;
}
if (this.state == Translation.STATE_OFFER) {
if (this.detectedLanguage != aFrom)
TranslationHealthReport.recordDetectedLanguageChange(true);
} else {
if (this.translatedFrom != aFrom)
TranslationHealthReport.recordDetectedLanguageChange(false);
if (this.translatedTo != aTo)
TranslationHealthReport.recordTargetLanguageChange();
}
this.state = Translation.STATE_TRANSLATING;
this.translatedFrom = aFrom;
this.translatedTo = aTo;
@ -184,6 +194,7 @@ TranslationUI.prototype = {
this.originalShown = true;
this.showURLBarIcon();
this.browser.messageManager.sendAsyncMessage("Translation:ShowOriginal");
TranslationHealthReport.recordShowOriginalContent();
},
showTranslatedContent: function() {
@ -255,6 +266,11 @@ TranslationUI.prototype = {
}
break;
}
},
infobarClosed: function() {
if (this.state == Translation.STATE_OFFER)
TranslationHealthReport.recordDeniedTranslationOffer();
}
};
@ -297,7 +313,7 @@ let TranslationHealthReport = {
/**
* Record a change of the detected language in the health report. This should
* only be called when actually executing a translation not every time the
* only be called when actually executing a translation, not every time the
* user changes in the language in the UI.
*
* @param beforeFirstTranslation
@ -307,8 +323,17 @@ let TranslationHealthReport = {
* the user has manually adjusted the detected language false should
* be passed.
*/
recordLanguageChange: function (beforeFirstTranslation) {
this._withProvider(provider => provider.recordLanguageChange(beforeFirstTranslation));
recordDetectedLanguageChange: function (beforeFirstTranslation) {
this._withProvider(provider => provider.recordDetectedLanguageChange(beforeFirstTranslation));
},
/**
* Record a change of the target language in the health report. This should
* only be called when actually executing a translation, not every time the
* user changes in the language in the UI.
*/
recordTargetLanguageChange: function () {
this._withProvider(provider => provider.recordTargetLanguageChange());
},
/**
@ -318,6 +343,13 @@ let TranslationHealthReport = {
this._withProvider(provider => provider.recordDeniedTranslationOffer());
},
/**
* Record a "Show Original" command use.
*/
recordShowOriginalContent: function () {
this._withProvider(provider => provider.recordShowOriginalContent());
},
/**
* Retrieve the translation provider and pass it to the given function.
*
@ -376,7 +408,9 @@ TranslationMeasurement1.prototype = Object.freeze({
pageTranslatedCountsByLanguage: DAILY_LAST_TEXT_FIELD,
detectedLanguageChangedBefore: DAILY_COUNTER_FIELD,
detectedLanguageChangedAfter: DAILY_COUNTER_FIELD,
targetLanguageChanged: DAILY_COUNTER_FIELD,
deniedTranslationOffer: DAILY_COUNTER_FIELD,
showOriginalContent: DAILY_COUNTER_FIELD,
detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD,
showTranslationUI: DAILY_LAST_NUMERIC_FIELD,
},
@ -500,7 +534,7 @@ TranslationProvider.prototype = Object.freeze({
}.bind(this));
},
recordLanguageChange: function (beforeFirstTranslation) {
recordDetectedLanguageChange: function (beforeFirstTranslation) {
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
TranslationMeasurement1.prototype.version);
@ -513,6 +547,15 @@ TranslationProvider.prototype = Object.freeze({
}.bind(this));
},
recordTargetLanguageChange: function () {
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
TranslationMeasurement1.prototype.version);
return this._enqueueTelemetryStorageTask(function* recordTask() {
yield m.incrementDailyCounter("targetLanguageChanged");
}.bind(this));
},
recordDeniedTranslationOffer: function () {
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
TranslationMeasurement1.prototype.version);
@ -522,6 +565,15 @@ TranslationProvider.prototype = Object.freeze({
}.bind(this));
},
recordShowOriginalContent: function () {
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
TranslationMeasurement1.prototype.version);
return this._enqueueTelemetryStorageTask(function* recordTask() {
yield m.incrementDailyCounter("showOriginalContent");
}.bind(this));
},
collectDailyData: function () {
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
TranslationMeasurement1.prototype.version);

View File

@ -85,8 +85,15 @@ TranslationContentHandler.prototype = {
return;
LanguageDetector.detectLanguage(string).then(result => {
if (!result.confident)
// Bail if we're not confident.
if (!result.confident) {
return;
}
// The window might be gone by now.
if (Cu.isDeadWrapper(content)) {
return;
}
content.detectedLanguage = result.language;

View File

@ -7,6 +7,62 @@ let tmp = {};
Cu.import("resource:///modules/translation/Translation.jsm", tmp);
let {Translation} = tmp;
let MetricsChecker = {
_metricsTime: new Date(),
_midnightError: new Error("Getting metrics around midnight may fail sometimes"),
updateMetrics: Task.async(function* () {
let svc = Cc["@mozilla.org/datareporting/service;1"].getService();
let reporter = svc.wrappedJSObject.healthReporter;
yield reporter.onInit();
// Get the provider.
let provider = reporter.getProvider("org.mozilla.translation");
let measurement = provider.getMeasurement("translation", 1);
let values = yield measurement.getValues();
let metricsTime = new Date();
let day = values.days.getDay(metricsTime);
if (!day) {
// This should never happen except when the test runs at midnight.
throw this._midnightError;
}
// .get() may return `undefined`, which we can't compute.
this._metrics = {
pageCount: day.get("pageTranslatedCount") || 0,
charCount: day.get("charactersTranslatedCount") || 0,
deniedOffers: day.get("deniedTranslationOffer") || 0,
showOriginal: day.get("showOriginalContent") || 0,
detectedLanguageChangedBefore: day.get("detectedLanguageChangedBefore") || 0,
detectedLanguageChangeAfter: day.get("detectedLanguageChangedAfter") || 0,
targetLanguageChanged: day.get("targetLanguageChanged") || 0
};
this._metricsTime = metricsTime;
}),
checkAdditions: Task.async(function* (additions) {
let prevMetrics = this._metrics, prevMetricsTime = this._metricsTime;
try {
yield this.updateMetrics();
} catch(ex if ex == this._midnightError) {
return;
}
// Check that it's still the same day of the month as when we started. This
// prevents intermittent failures when the test starts before and ends after
// midnight.
if (this._metricsTime.getDate() != prevMetricsTime.getDate()) {
for (let metric of Object.keys(prevMetrics)) {
prevMetrics[metric] = 0;
}
}
for (let metric of Object.keys(additions)) {
Assert.equal(prevMetrics[metric] + additions[metric], this._metrics[metric]);
}
})
};
add_task(function* setup() {
Services.prefs.setBoolPref("toolkit.telemetry.enabled", true);
Services.prefs.setBoolPref("browser.translation.detectLanguage", true);
@ -17,53 +73,100 @@ add_task(function* setup() {
Services.prefs.clearUserPref("browser.translation.detectLanguage");
Services.prefs.clearUserPref("browser.translation.ui.show");
});
// Make sure there are some initial metrics in place when the test starts.
yield translate("<h1>Hallo Welt!</h1>", "de");
yield MetricsChecker.updateMetrics();
});
add_task(function* test_fhr() {
let start = new Date();
// Translate a page.
yield translate("<h1>Hallo Welt!</h1>", "de", "en");
let [pageCount, charCount] = yield retrieveTranslationCounts();
yield translate("<h1>Hallo Welt!</h1>", "de");
// Translate another page.
yield translate("<h1>Hallo Welt!</h1><h1>Bratwurst!</h1>", "de", "en");
let [pageCount2, charCount2] = yield retrieveTranslationCounts();
// Check that it's still the same day of the month as when we started. This
// prevents intermittent failures when the test starts before and ends after
// midnight.
if (start.getDate() == new Date().getDate()) {
Assert.equal(pageCount2, pageCount + 1);
Assert.equal(charCount2, charCount + 21);
}
yield translate("<h1>Hallo Welt!</h1><h1>Bratwurst!</h1>", "de");
yield MetricsChecker.checkAdditions({ pageCount: 1, charCount: 21, deniedOffers: 0});
});
function retrieveTranslationCounts() {
return Task.spawn(function* task_retrieve_counts() {
let svc = Cc["@mozilla.org/datareporting/service;1"].getService();
let reporter = svc.wrappedJSObject.healthReporter;
yield reporter.onInit();
// Get the provider.
let provider = reporter.getProvider("org.mozilla.translation");
let measurement = provider.getMeasurement("translation", 1);
let values = yield measurement.getValues();
let day = values.days.getDay(new Date());
if (!day) {
// This should never happen except when the test runs at midnight.
return [0, 0];
add_task(function* test_deny_translation_metric() {
function* offerAndDeny(elementAnonid) {
let tab = yield offerTranslatationFor("<h1>Hallo Welt!</h1>", "de", "en");
getInfobarElement(tab.linkedBrowser, elementAnonid).doCommand();
yield MetricsChecker.checkAdditions({ deniedOffers: 1 });
gBrowser.removeTab(tab);
}
// .get() may return `undefined`, which we can't compute.
return [day.get("pageTranslatedCount") || 0, day.get("charactersTranslatedCount") || 0];
yield offerAndDeny("notNow");
yield offerAndDeny("neverForSite");
yield offerAndDeny("neverForLanguage");
yield offerAndDeny("closeButton");
// Test that the close button doesn't record a denied translation if
// the infobar is not in its "offer" state.
let tab = yield translate("<h1>Hallo Welt!</h1>", "de", false);
yield MetricsChecker.checkAdditions({ deniedOffers: 0 });
gBrowser.removeTab(tab);
});
add_task(function* test_show_original() {
let tab =
yield translate("<h1>Hallo Welt!</h1><h1>Bratwurst!</h1>", "de", false);
yield MetricsChecker.checkAdditions({ pageCount: 1, showOriginal: 0 });
getInfobarElement(tab.linkedBrowser, "showOriginal").doCommand();
yield MetricsChecker.checkAdditions({ pageCount: 0, showOriginal: 1 });
gBrowser.removeTab(tab);
});
add_task(function* test_language_change() {
for (let i of Array(4)) {
let tab = yield offerTranslatationFor("<h1>Hallo Welt!</h1>", "fr");
let browser = tab.linkedBrowser;
// In the offer state, translation is executed by the Translate button,
// so we expect just a single recoding.
let detectedLangMenulist = getInfobarElement(browser, "detectedLanguage");
simulateUserSelectInMenulist(detectedLangMenulist, "de");
simulateUserSelectInMenulist(detectedLangMenulist, "it");
simulateUserSelectInMenulist(detectedLangMenulist, "de");
yield acceptTranslationOffer(tab);
// In the translated state, a change in the form or to menulists
// triggers re-translation right away.
let fromLangMenulist = getInfobarElement(browser, "fromLanguage");
simulateUserSelectInMenulist(fromLangMenulist, "it");
simulateUserSelectInMenulist(fromLangMenulist, "de");
// Selecting the same item shouldn't count.
simulateUserSelectInMenulist(fromLangMenulist, "de");
let toLangMenulist = getInfobarElement(browser, "toLanguage");
simulateUserSelectInMenulist(toLangMenulist, "fr");
simulateUserSelectInMenulist(toLangMenulist, "en");
simulateUserSelectInMenulist(toLangMenulist, "it");
// Selecting the same item shouldn't count.
simulateUserSelectInMenulist(toLangMenulist, "it");
// Setting the target language to the source language is a no-op,
// so it shouldn't count.
simulateUserSelectInMenulist(toLangMenulist, "de");
gBrowser.removeTab(tab);
}
yield MetricsChecker.checkAdditions({
detectedLanguageChangedBefore: 4,
detectedLanguageChangeAfter: 8,
targetLanguageChanged: 12
});
});
function getInfobarElement(browser, anonid) {
let notif = browser.translationUI
.notificationBox.getNotificationWithValue("translation");
return notif._getAnonElt(anonid);
}
function translate(text, from, to) {
return Task.spawn(function* task_translate() {
function offerTranslatationFor(text, from) {
return Task.spawn(function* task_offer_translation() {
// Create some content to translate.
let tab = gBrowser.selectedTab =
gBrowser.addTab("data:text/html;charset=utf-8," + text);
@ -77,12 +180,27 @@ function translate(text, from, to) {
originalShown: true,
detectedLanguage: from});
// Translate the page.
browser.translationUI.translate(from, to);
yield waitForMessage(browser, "Translation:Finished");
return tab;
});
}
// Cleanup.
function acceptTranslationOffer(tab) {
return Task.spawn(function* task_accept_translation_offer() {
let browser = tab.linkedBrowser;
getInfobarElement(browser, "translate").doCommand();
yield waitForMessage(browser, "Translation:Finished");
});
}
function translate(text, from, closeTab = true) {
return Task.spawn(function* task_translate() {
let tab = yield offerTranslatationFor(text, from);
yield acceptTranslationOffer(tab);
if (closeTab) {
gBrowser.removeTab(tab);
} else {
return tab;
}
});
}
@ -105,3 +223,8 @@ function promiseBrowserLoaded(browser) {
}, true);
});
}
function simulateUserSelectInMenulist(menulist, value) {
menulist.value = value;
menulist.doCommand();
}

View File

@ -173,13 +173,16 @@ add_task(function* test_record_translation() {
yield provider.init(storage);
let now = new Date();
// Record a language change before translation.
yield provider.recordLanguageChange(true);
// Record a change to the source language changes before translation.
yield provider.recordDetectedLanguageChange(true);
// Record two language changes after translation.
yield provider.recordLanguageChange(false);
yield provider.recordLanguageChange(false);
// Record two changes to the source language changes after translation.
yield provider.recordDetectedLanguageChange(false);
yield provider.recordDetectedLanguageChange(false);
// Record two changes to the target language.
yield provider.recordTargetLanguageChange();
yield provider.recordTargetLanguageChange();
let m = provider.getMeasurement("translation", 1);
let values = yield m.getValues();
@ -189,21 +192,24 @@ add_task(function* test_record_translation() {
Assert.ok(day.has("detectedLanguageChangedBefore"));
Assert.equal(day.get("detectedLanguageChangedBefore"), 1);
Assert.ok(day.has("detectedLanguageChangedAfter"));
Assert.equal(day.get("detectedLanguageChangedAfter"), 2);
Assert.ok(day.has("targetLanguageChanged"));
Assert.equal(day.get("targetLanguageChanged"), 2);
yield provider.shutdown();
yield storage.close();
});
add_task(function* test_denied_translation_offer() {
function* test_simple_counter(aProviderFuncName, aCounterName) {
let storage = yield Metrics.Storage("translation");
let provider = new TranslationProvider();
yield provider.init(storage);
let now = new Date();
yield provider.recordDeniedTranslationOffer();
yield provider.recordDeniedTranslationOffer();
yield provider[aProviderFuncName]();
yield provider[aProviderFuncName]();
let m = provider.getMeasurement("translation", 1);
let values = yield m.getValues();
@ -211,11 +217,19 @@ add_task(function* test_denied_translation_offer() {
Assert.ok(values.days.hasDay(now));
let day = values.days.getDay(now);
Assert.ok(day.has("deniedTranslationOffer"));
Assert.equal(day.get("deniedTranslationOffer"), 2);
Assert.ok(day.has(aCounterName));
Assert.equal(day.get(aCounterName), 2);
yield provider.shutdown();
yield storage.close();
}
add_task(function* test_denied_translation_offer() {
yield test_simple_counter("recordDeniedTranslationOffer", "deniedTranslationOffer");
});
add_task(function* test_show_original() {
yield test_simple_counter("recordShowOriginalContent", "showOriginalContent");
});
add_task(function* test_collect_daily() {
@ -268,15 +282,17 @@ add_task(function* test_healthreporter_json() {
yield reporter._providerManager.registerProvider(provider);
yield provider.recordTranslationOpportunity("fr", now);
yield provider.recordLanguageChange(true);
yield provider.recordDetectedLanguageChange(true);
yield provider.recordTranslation("fr", "en", 1000, now);
yield provider.recordLanguageChange(false);
yield provider.recordDetectedLanguageChange(false);
yield provider.recordTranslationOpportunity("es", now);
yield provider.recordTranslation("es", "en", 1000, now);
yield provider.recordDeniedTranslationOffer();
yield provider.recordShowOriginalContent();
yield reporter.collectMeasurements();
let payload = yield reporter.getJSONPayload(true);
let today = reporter._formatDate(now);
@ -312,6 +328,9 @@ add_task(function* test_healthreporter_json() {
Assert.ok("deniedTranslationOffer" in translations);
Assert.equal(translations["deniedTranslationOffer"], 1);
Assert.ok("showOriginalContent" in translations);
Assert.equal(translations["showOriginalContent"], 1);
} finally {
reporter._shutdown();
}
@ -329,15 +348,17 @@ add_task(function* test_healthreporter_json2() {
yield reporter._providerManager.registerProvider(provider);
yield provider.recordTranslationOpportunity("fr", now);
yield provider.recordLanguageChange(true);
yield provider.recordDetectedLanguageChange(true);
yield provider.recordTranslation("fr", "en", 1000, now);
yield provider.recordLanguageChange(false);
yield provider.recordDetectedLanguageChange(false);
yield provider.recordTranslationOpportunity("es", now);
yield provider.recordTranslation("es", "en", 1000, now);
yield provider.recordDeniedTranslationOffer();
yield provider.recordShowOriginalContent();
yield reporter.collectMeasurements();
let payload = yield reporter.getJSONPayload(true);
let today = reporter._formatDate(now);
@ -357,6 +378,7 @@ add_task(function* test_healthreporter_json2() {
Assert.ok(!("detectedLanguageChangedBefore" in translations));
Assert.ok(!("detectedLanguageChangedAfter" in translations));
Assert.ok(!("deniedTranslationOffer" in translations));
Assert.ok(!("showOriginalContent" in translations));
} finally {
reporter._shutdown();
}

View File

@ -38,7 +38,7 @@
oncommand="document.getBindingParent(this).translate();"/>
<xul:button class="translate-infobar-element"
label="&translation.notNow.button;" anonid="notNow"
oncommand="document.getBindingParent(this).close();"/>
oncommand="document.getBindingParent(this).closeCommand();"/>
</xul:hbox>
<!-- translating -->
@ -122,10 +122,11 @@
</xul:hbox>
<xul:toolbarbutton ondblclick="event.stopPropagation();"
anonid="closeButton"
class="messageCloseButton close-icon tabbable"
xbl:inherits="hidden=hideclose"
tooltiptext="&closeNotification.tooltip;"
oncommand="document.getBindingParent(this).close();"/>
oncommand="document.getBindingParent(this).closeCommand();"/>
</xul:hbox>
</content>
<implementation>
@ -216,6 +217,16 @@
</body>
</method>
<!-- To be called when the infobar should be closed per user's wish (e.g.
by clicking the notification's close button -->
<method name="closeCommand">
<body>
<![CDATA[
this.close();
this.translation.infobarClosed();
]]>
</body>
</method>
<method name="_handleButtonHiding">
<body>
<![CDATA[
@ -301,7 +312,7 @@
Services.prefs.setCharPref(kPrefName, val);
this.close();
this.closeCommand();
]]>
</body>
</method>
@ -313,7 +324,7 @@
let perms = Services.perms;
perms.add(uri, "translate", perms.DENY_ACTION);
this.close();
this.closeCommand();
]]>
</body>
</method>

View File

@ -210,7 +210,7 @@ exports.getHighlighterUtils = function(toolbox) {
yield toolbox.walker.highlight(nodeFront);
}
toolbox.emit("node-highlight", nodeFront);
toolbox.emit("node-highlight", nodeFront, options.toSource());
});
/**

View File

@ -7,6 +7,9 @@ support-files =
head.js
[browser_layoutview.js]
[browser_layoutview_guides.js]
# browser_layoutview_guides.js should be re-enabled when bug 1029451 is fixed.
skip-if = true
[browser_layoutview_rotate-labels-on-sides.js]
[browser_layoutview_update-after-navigation.js]
[browser_layoutview_update-after-reload.js]

View File

@ -0,0 +1,82 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let test = asyncTest(function*() {
let style = "div { position: absolute; top: 50px; left: 50px; height: 10px; " +
"width: 10px; border: 10px solid black; padding: 10px; margin: 10px;}";
let html = "<style>" + style + "</style><div></div>";
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(html));
let {inspector, view} = yield openLayoutView();
yield selectNode("div", inspector);
yield runTests(inspector, view);
});
addTest("Test that the initial values of the box model are correct",
function*(inspector, view) {
yield testGuideOnLayoutHover("margins", {
top: { x1: "0", x2: "100%", y1: "119.5", y2: "119.5" },
right: { x1: "50.5", x2: "50.5", y1: "0", y2: "100%" },
bottom: { x1: "0", x2: "100%", y1: "50.5", y2: "50.5" },
left: { x1: "119.5", x2: "119.5", y1: "0", y2: "100%" }
}, inspector, view);
yield testGuideOnLayoutHover("borders", {
top: { x1: "0", x2: "100%", y1: "109.5", y2: "109.5" },
right: { x1: "60.5", x2: "60.5", y1: "0", y2: "100%" },
bottom: { x1: "0", x2: "100%", y1: "60.5", y2: "60.5" },
left: { x1: "109.5", x2: "109.5", y1: "0", y2: "100%" }
}, inspector, view);
yield testGuideOnLayoutHover("padding", {
top: { x1: "0", x2: "100%", y1: "99.5", y2: "99.5" },
right: { x1: "70.5", x2: "70.5", y1: "0", y2: "100%" },
bottom: { x1: "0", x2: "100%", y1: "70.5", y2: "70.5" },
left: { x1: "99.5", x2: "99.5", y1: "0", y2: "100%" }
}, inspector, view);
yield testGuideOnLayoutHover("content", {
top: { x1: "0", x2: "100%", y1: "79.5", y2: "79.5" },
right: { x1: "90.5", x2: "90.5", y1: "0", y2: "100%" },
bottom: { x1: "0", x2: "100%", y1: "90.5", y2: "90.5" },
left: { x1: "79.5", x2: "79.5", y1: "0", y2: "100%" }
}, inspector, view);
gBrowser.removeCurrentTab();
});
function* testGuideOnLayoutHover(id, expected, inspector, view) {
info("Checking " + id);
let elt = view.doc.getElementById(id);
EventUtils.synthesizeMouse(elt, 2, 2, {type:'mouseover'}, view.doc.defaultView);
yield inspector.toolbox.once("highlighter-ready");
let guideTop = getGuideStatus("top");
let guideRight = getGuideStatus("right");
let guideBottom = getGuideStatus("bottom");
let guideLeft = getGuideStatus("left");
is(guideTop.x1, expected.top.x1, "top x1 is correct");
is(guideTop.x2, expected.top.x2, "top x2 is correct");
is(guideTop.y1, expected.top.y1, "top y1 is correct");
is(guideTop.y2, expected.top.y2, "top y2 is correct");
is(guideRight.x1, expected.right.x1, "right x1 is correct");
is(guideRight.x2, expected.right.x2, "right x2 is correct");
is(guideRight.y1, expected.right.y1, "right y1 is correct");
is(guideRight.y2, expected.right.y2, "right y2 is correct");
is(guideBottom.x1, expected.bottom.x1, "bottom x1 is correct");
is(guideBottom.x2, expected.bottom.x2, "bottom x2 is correct");
is(guideBottom.y1, expected.bottom.y1, "bottom y1 is correct");
is(guideBottom.y2, expected.bottom.y2, "bottom y2 is correct");
is(guideLeft.x1, expected.left.x1, "left x1 is correct");
is(guideLeft.x2, expected.left.x2, "left x2 is correct");
is(guideLeft.y1, expected.left.y1, "left y1 is correct");
is(guideLeft.y2, expected.left.y2, "left y2 is correct");
}

View File

@ -216,6 +216,28 @@ function waitForUpdate(inspector) {
return inspector.once("layoutview-updated");
}
function getHighlighter() {
return gBrowser.selectedBrowser.parentNode.querySelector(".highlighter-container");
}
function getBoxModelRoot() {
let highlighter = getHighlighter();
return highlighter.querySelector(".box-model-root");
}
function getGuideStatus(location) {
let root = getBoxModelRoot();
let guide = root.querySelector(".box-model-guide-" + location);
return {
visible: !guide.hasAttribute("hidden"),
x1: guide.getAttribute("x1"),
y1: guide.getAttribute("y1"),
x2: guide.getAttribute("x2"),
y2: guide.getAttribute("y2")
};
}
/**
* The addTest/runTests function couple provides a simple way to define
* subsequent test cases in a test file. Example:

View File

@ -84,6 +84,22 @@ function getDOMWindow(aChannel) {
return win;
}
function makeContentReadable(obj, window) {
if (Cu.cloneInto) {
return Cu.cloneInto(obj, window);
}
// TODO remove for Firefox 32+
if (typeof obj !== 'object' || obj === null) {
return obj;
}
var expose = {};
for (let k in obj) {
expose[k] = "rw";
}
obj.__exposedProps__ = expose;
return obj;
}
function parseQueryString(qs) {
if (!qs)
return {};
@ -537,7 +553,6 @@ RequestListener.prototype.receive = function(event) {
if (sync) {
var response = actions[action].call(this.actions, data);
var detail = event.detail;
detail.__exposedProps__ = {response: 'r'};
detail.response = response;
} else {
var response;
@ -548,9 +563,10 @@ RequestListener.prototype.receive = function(event) {
try {
var listener = doc.createEvent('CustomEvent');
listener.initCustomEvent('shumway.response', true, false,
{response: response,
cookie: cookie,
__exposedProps__: {response: 'r', cookie: 'r'}});
makeContentReadable({
response: response,
cookie: cookie
}, doc.defaultView));
return message.dispatchEvent(listener);
} catch (e) {
@ -752,11 +768,11 @@ function initExternalCom(wrappedWindow, wrappedObject, targetDocument) {
var args = Array.prototype.slice.call(arguments, 0);
wrappedWindow.console.log('__flash__callIn: ' + functionName);
var e = targetDocument.createEvent('CustomEvent');
e.initCustomEvent('shumway.remote', true, false, {
e.initCustomEvent('shumway.remote', true, false, makeContentReadable({
functionName: functionName,
args: args,
__exposedProps__: {args: 'r', functionName: 'r', result: 'rw'}
});
result: undefined
}, targetDocument.defaultView));
targetDocument.dispatchEvent(e);
return e.detail.result;
};

View File

@ -455,7 +455,7 @@
@BINPATH@/components/FormHistoryStartup.js
@BINPATH@/components/nsInputListAutoComplete.js
@BINPATH@/components/formautofill.manifest
@BINPATH@/components/AutofillController.js
@BINPATH@/components/FormAutofillContentService.js
@BINPATH@/components/contentSecurityPolicy.manifest
@BINPATH@/components/contentSecurityPolicy.js
@BINPATH@/components/contentAreaDropListener.manifest

View File

@ -4,6 +4,8 @@
<!ENTITY prefWindow.titleWin "Options">
<!ENTITY prefWindow.title "Preferences">
<!-- LOCALIZATION NOTE (prefWindow.titleGNOME): This is not used for in-content preferences -->
<!ENTITY prefWindow.titleGNOME "&brandShortName; Preferences">
<!-- When making changes to prefWindow.styleWin test both Windows Classic and
Luna since widget heights are different based on the OS theme -->

View File

@ -72,9 +72,6 @@ leak:AllocateArrayBufferContents
# Bug 1022010 - Small leak under _render_glyph_outline. bc1
leak:_render_glyph_outline
# Bug 1022042 - compareVariants in the test plugin leaks identifiers. oth
leak:compareVariants(_NPP*,
# Bug 1022954 - ScriptSource leaks sourceMapURL_ sometimes. dt
leak:ScriptSource::setSourceMapURL

View File

@ -71,7 +71,7 @@ GCONF_VERSION=1.2.1
GIO_VERSION=2.20
STARTUP_NOTIFICATION_VERSION=0.8
DBUS_VERSION=0.60
SQLITE_VERSION=3.8.4.2
SQLITE_VERSION=3.8.5
MSMANIFEST_TOOL=

View File

@ -2172,22 +2172,6 @@ WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
return ErrorInvalidOperation("readPixels: Invalid type floatness");
// Check the format and type params to assure they are an acceptable pair (as per spec)
switch (format) {
case LOCAL_GL_RGBA: {
switch (type) {
case LOCAL_GL_UNSIGNED_BYTE:
break;
case LOCAL_GL_FLOAT:
break;
default:
return ErrorInvalidOperation("readPixels: Invalid format/type pair");
}
break;
}
default:
return ErrorInvalidOperation("readPixels: Invalid format/type pair");
}
MakeContextCurrent();
if (mBoundFramebuffer) {
@ -2203,6 +2187,41 @@ WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
} else {
ClearBackbufferIfNeeded();
}
bool isFormatAndTypeValid = false;
// OpenGL ES 2.0 $4.3.1 - IMPLEMENTATION_COLOR_READ_{TYPE/FORMAT} is a valid
// combination for glReadPixels().
if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) {
GLenum implType = 0;
GLenum implFormat = 0;
gl->fGetIntegerv(LOCAL_GL_IMPLEMENTATION_COLOR_READ_TYPE,
reinterpret_cast<GLint*>(&implType));
gl->fGetIntegerv(LOCAL_GL_IMPLEMENTATION_COLOR_READ_FORMAT,
reinterpret_cast<GLint*>(&implFormat));
if (type == implType && format == implFormat) {
isFormatAndTypeValid = true;
}
}
switch (format) {
case LOCAL_GL_RGBA: {
switch (type) {
case LOCAL_GL_UNSIGNED_BYTE:
case LOCAL_GL_FLOAT:
isFormatAndTypeValid = true;
break;
}
break;
}
}
if (!isFormatAndTypeValid) {
return ErrorInvalidOperation("readPixels: Invalid format/type pair");
}
// Now that the errors are out of the way, on to actually reading
// If we won't be reading any pixels anyways, just skip the actual reading

View File

@ -222,6 +222,24 @@ WebGLContext::GetParameter(JSContext* cx, GLenum pname, ErrorResult& rv)
gl->fGetIntegerv(pname, &i);
return JS::NumberValue(uint32_t(i));
}
case LOCAL_GL_IMPLEMENTATION_COLOR_READ_TYPE: {
GLint i = 0;
if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) {
gl->fGetIntegerv(pname, &i);
} else {
i = LOCAL_GL_UNSIGNED_BYTE;
}
return JS::NumberValue(uint32_t(i));
}
case LOCAL_GL_IMPLEMENTATION_COLOR_READ_FORMAT: {
GLint i = 0;
if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) {
gl->fGetIntegerv(pname, &i);
} else {
i = LOCAL_GL_RGBA;
}
return JS::NumberValue(uint32_t(i));
}
// int
case LOCAL_GL_STENCIL_CLEAR_VALUE:
case LOCAL_GL_STENCIL_REF:

View File

@ -74,9 +74,9 @@ shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilFuncSeparate(gl.FRONT, gl.ALWA
shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawArrays(gl.TRIANGLES, 0, 0)");
debug("");
debug("Verify that IMPLEMENTATION_COLOR_READ_FORMAT and IMPLEMENTATION_COLOR_READ_TYPE are undefined");
shouldBeUndefined(gl.IMPLEMENTATION_COLOR_READ_FORMAT);
shouldBeUndefined(gl.IMPLEMENTATION_COLOR_READ_TYPE);
debug("Verify that IMPLEMENTATION_COLOR_READ_FORMAT and IMPLEMENTATION_COLOR_READ_TYPE are defined");
shouldBe(gl.IMPLEMENTATION_COLOR_READ_FORMAT, 0x8B9B);
shouldBe(gl.IMPLEMENTATION_COLOR_READ_TYPE, 0x8B9A);
debug("");
debug("Verify that *LENGTH are undefined");

View File

@ -29,7 +29,7 @@
#include "nsAutoPtr.h"
#include "nsTArray.h"
#include "nsIMutableArray.h"
#include "nsIAutofillController.h"
#include "nsIFormAutofillContentService.h"
// form submission
#include "nsIFormSubmitObserver.h"
@ -306,10 +306,12 @@ void
HTMLFormElement::RequestAutocomplete()
{
bool dummy;
nsCOMPtr<nsIDOMWindow> win = do_QueryInterface(OwnerDoc()->GetScriptHandlingObject(dummy));
nsCOMPtr<nsIAutofillController> controller(do_GetService("@mozilla.org/autofill-controller;1"));
nsCOMPtr<nsIDOMWindow> window =
do_QueryInterface(OwnerDoc()->GetScriptHandlingObject(dummy));
nsCOMPtr<nsIFormAutofillContentService> formAutofillContentService =
do_GetService("@mozilla.org/formautofill/content-service;1");
if (!controller || !win) {
if (!formAutofillContentService || !window) {
AutocompleteErrorEventInit init;
init.mBubbles = true;
init.mCancelable = false;
@ -317,11 +319,12 @@ HTMLFormElement::RequestAutocomplete()
nsRefPtr<AutocompleteErrorEvent> event =
AutocompleteErrorEvent::Constructor(this, NS_LITERAL_STRING("autocompleteerror"), init);
(new AsyncEventDispatcher(this, event))->PostDOMEvent();
return;
}
controller->RequestAutocomplete(this, win);
formAutofillContentService->RequestAutocomplete(this, window);
}
bool

View File

@ -23,7 +23,6 @@ public:
// This class holds a weak pointer to MediaResource. It's the responsibility
// of the caller to manage the memory of the MediaResource object.
explicit BufferDecoder(MediaResource* aResource);
virtual ~BufferDecoder();
NS_DECL_THREADSAFE_ISUPPORTS
@ -75,6 +74,8 @@ public:
virtual void NotifyWaitingForResourcesStatusChanged() MOZ_OVERRIDE;
protected:
virtual ~BufferDecoder();
// This monitor object is not really used to synchronize access to anything.
// It's just there in order for us to be able to override
// GetReentrantMonitor correctly.

View File

@ -420,6 +420,10 @@ DOMAudioNodeMediaStream::DOMAudioNodeMediaStream(AudioNode* aNode)
{
}
DOMAudioNodeMediaStream::~DOMAudioNodeMediaStream()
{
}
already_AddRefed<DOMAudioNodeMediaStream>
DOMAudioNodeMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow,
AudioNode* aNode,

View File

@ -56,7 +56,6 @@ public:
typedef uint8_t TrackTypeHints;
DOMMediaStream();
virtual ~DOMMediaStream();
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMMediaStream)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@ -209,6 +208,8 @@ public:
}
protected:
virtual ~DOMMediaStream();
void Destroy();
void InitSourceStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents);
void InitTrackUnionStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents);
@ -286,6 +287,7 @@ class DOMAudioNodeMediaStream : public DOMMediaStream
typedef dom::AudioNode AudioNode;
public:
DOMAudioNodeMediaStream(AudioNode* aNode);
~DOMAudioNodeMediaStream();
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)

View File

@ -61,13 +61,14 @@ PRLogModuleInfo* gMediaDecoderLog;
class MediaMemoryTracker : public nsIMemoryReporter
{
virtual ~MediaMemoryTracker();
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIMEMORYREPORTER
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);
MediaMemoryTracker();
virtual ~MediaMemoryTracker();
void InitMemoryReporter();
static StaticRefPtr<MediaMemoryTracker> sUniqueInstance;

View File

@ -275,7 +275,6 @@ public:
};
MediaDecoder();
virtual ~MediaDecoder();
// Reset the decoder and notify the media element that
// server connection is closed.
@ -999,6 +998,8 @@ public:
}
protected:
virtual ~MediaDecoder();
/******
* The following members should be accessed with the decoder lock held.
******/

View File

@ -3267,6 +3267,8 @@ public:
return mStateMachine->TimeoutExpired(mTimerId);
}
private:
~TimerEvent() {}
const nsRefPtr<MediaDecoderStateMachine> mStateMachine;
int mTimerId;
};

View File

@ -238,13 +238,6 @@ public:
mLastBlobTimeStamp = TimeStamp::Now();
}
// Only DestroyRunnable is allowed to delete Session object.
virtual ~Session()
{
LOG(PR_LOG_DEBUG, ("Session.~Session (%p)", this));
CleanupStreams();
}
void Start()
{
LOG(PR_LOG_DEBUG, ("Session.Start %p", this));
@ -299,7 +292,12 @@ public:
}
private:
// Only DestroyRunnable is allowed to delete Session object.
virtual ~Session()
{
LOG(PR_LOG_DEBUG, ("Session.~Session (%p)", this));
CleanupStreams();
}
// Pull encoded media data from MediaEncoder and put into EncodedBufferCache.
// Destroy this session object in the end of this function.
// If the bool aForceFlush is true, we will force to dispatch a

View File

@ -608,9 +608,9 @@ public:
public nsIInterfaceRequestor,
public nsIChannelEventSink
{
~Listener() {}
public:
Listener(ChannelMediaResource* aResource) : mResource(aResource) {}
~Listener() {}
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER

View File

@ -1591,6 +1591,7 @@ public:
class MediaStreamGraphShutdownObserver MOZ_FINAL : public nsIObserver
{
~MediaStreamGraphShutdownObserver() {}
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER

View File

@ -204,9 +204,9 @@ public:
class Listener MOZ_FINAL : public nsIInterfaceRequestor,
public nsIStreamingProtocolListener
{
~Listener() {}
public:
Listener(RtspMediaResource* aResource) : mResource(aResource) {}
~Listener() {}
NS_DECL_ISUPPORTS
NS_DECL_NSIINTERFACEREQUESTOR

View File

@ -38,6 +38,9 @@ TextTrackCueList::TextTrackCueList(nsISupports* aParent) : mParent(aParent)
SetIsDOMBinding();
}
TextTrackCueList::~TextTrackCueList()
{}
JSObject*
TextTrackCueList::WrapObject(JSContext* aCx)
{

View File

@ -55,6 +55,8 @@ public:
void GetArray(nsTArray<nsRefPtr<TextTrackCue> >& aCues);
private:
~TextTrackCueList();
nsCOMPtr<nsISupports> mParent;
// A sorted list of TextTrackCues sorted by earliest start time. If the start

View File

@ -35,6 +35,10 @@ TextTrackList::TextTrackList(nsPIDOMWindow* aOwnerWindow,
{
}
TextTrackList::~TextTrackList()
{
}
void
TextTrackList::UpdateAndGetShowingCues(nsTArray<nsRefPtr<TextTrackCue> >& aCues)
{

View File

@ -28,6 +28,7 @@ public:
TextTrackList(nsPIDOMWindow* aOwnerWindow);
TextTrackList(nsPIDOMWindow* aOwnerWindow, TextTrackManager* aTextTrackManager);
~TextTrackList();
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;

View File

@ -148,6 +148,8 @@ public:
}
private:
~TextTrackRegion() {}
nsCOMPtr<nsISupports> mParent;
double mWidth;
long mLines;

View File

@ -50,6 +50,8 @@ public:
}
private:
~VideoPlaybackQuality() {}
nsRefPtr<HTMLMediaElement> mElement;
DOMHighResTimeStamp mCreationTime;
uint64_t mTotalFrames;

View File

@ -37,7 +37,6 @@ class WebVTTListener MOZ_FINAL : public nsIWebVTTListener,
public:
WebVTTListener(HTMLTrackElement* aElement);
~WebVTTListener();
/**
* Loads the WebVTTListener. Must call this in order for the listener to be
@ -46,6 +45,8 @@ public:
nsresult LoadResource();
private:
~WebVTTListener();
// List of error codes returned from the WebVTT parser that we care about.
enum ErrorCodes {
BadSignature = 0

View File

@ -36,14 +36,14 @@ typedef uint32_t PromiseId;
class MediaKeys MOZ_FINAL : public nsISupports,
public nsWrapperCache
{
~MediaKeys();
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeys)
MediaKeys(nsPIDOMWindow* aParentWindow, const nsAString& aKeySystem);
~MediaKeys();
nsPIDOMWindow* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;

View File

@ -109,6 +109,8 @@ public:
void UnregisterPannerNode(PannerNode* aPannerNode);
private:
~AudioListener() {}
void SendDoubleParameterToStream(uint32_t aIndex, double aValue);
void SendThreeDPointParameterToStream(uint32_t aIndex, const ThreeDPoint& aValue);
void UpdatePannersVelocity();

View File

@ -23,13 +23,14 @@ namespace dom {
class AudioParam MOZ_FINAL : public nsWrapperCache,
public AudioParamTimeline
{
virtual ~AudioParam();
public:
typedef void (*CallbackType)(AudioNode*);
AudioParam(AudioNode* aNode,
CallbackType aCallback,
float aDefaultValue);
virtual ~AudioParam();
NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
NS_IMETHOD_(MozExternalRefCountType) Release(void);

View File

@ -32,7 +32,6 @@ struct WebAudioDecodeJob MOZ_FINAL
dom::AudioContext* aContext,
dom::DecodeSuccessCallback* aSuccessCallback = nullptr,
dom::DecodeErrorCallback* aFailureCallback = nullptr);
~WebAudioDecodeJob();
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebAudioDecodeJob)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebAudioDecodeJob)
@ -63,6 +62,9 @@ struct WebAudioDecodeJob MOZ_FINAL
nsRefPtr<dom::DecodeErrorCallback> mFailureCallback; // can be null
nsRefPtr<dom::AudioBuffer> mOutput;
FallibleTArray<ChannelBuffer> mChannelBuffers;
private:
~WebAudioDecodeJob();
};
/**

View File

@ -51,6 +51,8 @@ public:
size_t SizeOfIncludingThisIfNotShared(MallocSizeOf aMallocSizeOf) const;
private:
~PeriodicWave() {}
nsRefPtr<AudioContext> mContext;
nsRefPtr<ThreadSharedFloatArrayBufferList> mCoefficients;
uint32_t mLength;

View File

@ -32,7 +32,6 @@ public:
NS_DECL_NSIOBSERVER
LoadMonitor(int aLoadUpdateInterval);
~LoadMonitor();
nsresult Init(nsRefPtr<LoadMonitor> &self);
void SetLoadChangeCallback(LoadNotificationCallback* aCallback);
@ -43,6 +42,7 @@ public:
friend class LoadInfoCollectRunner;
private:
~LoadMonitor();
void SetProcessLoad(float load);
void SetSystemLoad(float load);

View File

@ -36,7 +36,6 @@ class MediaEngineDefaultVideoSource : public nsITimerCallback,
{
public:
MediaEngineDefaultVideoSource();
~MediaEngineDefaultVideoSource();
virtual void GetName(nsAString&);
virtual void GetUUID(nsAString&);
@ -65,6 +64,8 @@ public:
NS_DECL_NSITIMERCALLBACK
protected:
~MediaEngineDefaultVideoSource();
friend class MediaEngineDefault;
TrackID mTrackID;
@ -90,7 +91,6 @@ class MediaEngineDefaultAudioSource : public nsITimerCallback,
{
public:
MediaEngineDefaultAudioSource();
~MediaEngineDefaultAudioSource();
virtual void GetName(nsAString&);
virtual void GetUUID(nsAString&);
@ -119,6 +119,8 @@ public:
NS_DECL_NSITIMERCALLBACK
protected:
~MediaEngineDefaultAudioSource();
TrackID mTrackID;
nsCOMPtr<nsITimer> mTimer;

View File

@ -52,6 +52,9 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
nsRefPtr<MediaEngineTabVideoSource> mVideoSource;
};
protected:
~MediaEngineTabVideoSource() {}
private:
int mBufW;
int mBufH;

View File

@ -140,8 +140,6 @@ public:
}
#endif
~MediaEngineWebRTCVideoSource() { Shutdown(); }
virtual void GetName(nsAString&);
virtual void GetUUID(nsAString&);
virtual nsresult Allocate(const VideoTrackConstraintsN &aConstraints,
@ -205,6 +203,9 @@ public:
return NS_OK;
}
protected:
~MediaEngineWebRTCVideoSource() { Shutdown(); }
private:
static const unsigned int KMaxDeviceNameLength = 128;
static const unsigned int KMaxUniqueIdLength = 256;
@ -286,7 +287,6 @@ public:
mDeviceUUID.Assign(NS_ConvertUTF8toUTF16(uuid));
Init();
}
~MediaEngineWebRTCAudioSource() { Shutdown(); }
virtual void GetName(nsAString&);
virtual void GetUUID(nsAString&);
@ -320,6 +320,8 @@ public:
NS_DECL_THREADSAFE_ISUPPORTS
protected:
~MediaEngineWebRTCAudioSource() { Shutdown(); }
// mSamples is an int to avoid conversions when comparing/etc to
// samplingFreq & length. Making mSamples protected instead of private is a
// silly way to avoid -Wunused-private-field warnings when PR_LOGGING is not

View File

@ -26,7 +26,6 @@ class SpeechGrammar MOZ_FINAL : public nsISupports,
{
public:
SpeechGrammar(nsISupports* aParent);
~SpeechGrammar();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(SpeechGrammar)
@ -47,6 +46,8 @@ public:
void SetWeight(float aArg, ErrorResult& aRv);
private:
~SpeechGrammar();
nsCOMPtr<nsISupports> mParent;
};

View File

@ -29,7 +29,6 @@ class SpeechGrammarList MOZ_FINAL : public nsISupports,
{
public:
SpeechGrammarList(nsISupports* aParent);
~SpeechGrammarList();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(SpeechGrammarList)
@ -52,6 +51,8 @@ public:
already_AddRefed<SpeechGrammar> IndexedGetter(uint32_t aIndex, bool& aPresent, ErrorResult& aRv);
private:
~SpeechGrammarList();
nsCOMPtr<nsISupports> mParent;
};

View File

@ -185,9 +185,9 @@ private:
: mRecognition(aRecognition)
{}
private:
virtual ~GetUserMediaSuccessCallback() {}
private:
nsRefPtr<SpeechRecognition> mRecognition;
};
@ -201,9 +201,9 @@ private:
: mRecognition(aRecognition)
{}
private:
virtual ~GetUserMediaErrorCallback() {}
private:
nsRefPtr<SpeechRecognition> mRecognition;
};

View File

@ -25,7 +25,6 @@ class SpeechRecognitionAlternative MOZ_FINAL : public nsISupports,
{
public:
SpeechRecognitionAlternative(SpeechRecognition* aParent);
~SpeechRecognitionAlternative();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(SpeechRecognitionAlternative)
@ -41,6 +40,8 @@ public:
nsString mTranscript;
float mConfidence;
private:
~SpeechRecognitionAlternative();
nsRefPtr<SpeechRecognition> mParent;
};

View File

@ -26,7 +26,6 @@ class SpeechRecognitionResult MOZ_FINAL : public nsISupports,
{
public:
SpeechRecognitionResult(SpeechRecognition* aParent);
~SpeechRecognitionResult();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(SpeechRecognitionResult)
@ -44,7 +43,10 @@ public:
already_AddRefed<SpeechRecognitionAlternative> IndexedGetter(uint32_t aIndex, bool& aPresent);
nsTArray<nsRefPtr<SpeechRecognitionAlternative> > mItems;
private:
~SpeechRecognitionResult();
nsRefPtr<SpeechRecognition> mParent;
};

View File

@ -27,7 +27,6 @@ class SpeechRecognitionResultList MOZ_FINAL : public nsISupports,
{
public:
SpeechRecognitionResultList(SpeechRecognition* aParent);
~SpeechRecognitionResultList();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(SpeechRecognitionResultList)
@ -44,6 +43,8 @@ public:
nsTArray<nsRefPtr<SpeechRecognitionResult> > mItems;
private:
~SpeechRecognitionResultList();
nsRefPtr<SpeechRecognition> mParent;
};

View File

@ -28,7 +28,6 @@ class SpeechSynthesis MOZ_FINAL : public nsISupports,
{
public:
SpeechSynthesis(nsPIDOMWindow* aParent);
virtual ~SpeechSynthesis();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(SpeechSynthesis)
@ -56,6 +55,7 @@ public:
void GetVoices(nsTArray< nsRefPtr<SpeechSynthesisVoice> >& aResult);
private:
virtual ~SpeechSynthesis();
void AdvanceQueue();

View File

@ -29,8 +29,6 @@ class SpeechSynthesisVoice MOZ_FINAL : public nsISupports,
public:
SpeechSynthesisVoice(nsISupports* aParent, const nsAString& aUri);
virtual ~SpeechSynthesisVoice();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(SpeechSynthesisVoice)
@ -49,6 +47,7 @@ public:
bool Default() const;
private:
virtual ~SpeechSynthesisVoice();
nsCOMPtr<nsISupports> mParent;

View File

@ -31,8 +31,6 @@ public:
nsSpeechTask(SpeechSynthesisUtterance* aUtterance);
nsSpeechTask(float aVolume, const nsAString& aText);
virtual ~nsSpeechTask();
virtual void Pause();
virtual void Resume();
@ -48,6 +46,8 @@ public:
void SetIndirectAudio(bool aIndirectAudio) { mIndirectAudio = aIndirectAudio; }
protected:
virtual ~nsSpeechTask();
virtual nsresult DispatchStartImpl();
virtual nsresult DispatchEndImpl(float aElapsedTime, uint32_t aCharIndex);

View File

@ -30,7 +30,6 @@ public:
NS_DECL_NSISYNTHVOICEREGISTRY
nsSynthVoiceRegistry();
virtual ~nsSynthVoiceRegistry();
already_AddRefed<nsSpeechTask> SpeakUtterance(SpeechSynthesisUtterance& aUtterance,
const nsAString& aDocLang);
@ -55,6 +54,8 @@ public:
static void Shutdown();
private:
virtual ~nsSynthVoiceRegistry();
VoiceData* FindBestMatch(const nsAString& aUri, const nsAString& lang);
bool FindVoiceByLang(const nsAString& aLang, VoiceData** aRetval);

View File

@ -79,6 +79,8 @@ class DOMSVGLength MOZ_FINAL : public nsIDOMSVGLength,
*/
DOMSVGLength(nsSVGLength2* aVal, nsSVGElement* aSVGElement, bool aAnimVal);
~DOMSVGLength();
public:
NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOMSVGLENGTH_IID)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@ -99,8 +101,6 @@ public:
*/
DOMSVGLength();
~DOMSVGLength();
static already_AddRefed<DOMSVGLength> GetTearOff(nsSVGLength2* aVal,
nsSVGElement* aSVGElement,
bool aAnimVal);

View File

@ -44,6 +44,15 @@ class DOMSVGLengthList MOZ_FINAL : public nsISupports,
friend class AutoChangeLengthListNotifier;
friend class DOMSVGLength;
~DOMSVGLengthList() {
// Our mAList's weak ref to us must be nulled out when we die. If GC has
// unlinked us using the cycle collector code, then that has already
// happened, and mAList is null.
if (mAList) {
( IsAnimValList() ? mAList->mAnimVal : mAList->mBaseVal ) = nullptr;
}
}
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMSVGLengthList)
@ -62,15 +71,6 @@ public:
InternalListLengthWillChange(aInternalList.Length()); // Sync mItems
}
~DOMSVGLengthList() {
// Our mAList's weak ref to us must be nulled out when we die. If GC has
// unlinked us using the cycle collector code, then that has already
// happened, and mAList is null.
if (mAList) {
( IsAnimValList() ? mAList->mAnimVal : mAList->mBaseVal ) = nullptr;
}
}
virtual JSObject* WrapObject(JSContext *cx) MOZ_OVERRIDE;
nsISupports* GetParentObject()

View File

@ -38,6 +38,15 @@ class DOMSVGNumber MOZ_FINAL : public nsISupports
{
friend class AutoChangeNumberNotifier;
~DOMSVGNumber() {
// Our mList's weak ref to us must be nulled out when we die. If GC has
// unlinked us using the cycle collector code, then that has already
// happened, and mList is null.
if (mList) {
mList->mItems[mListIndex] = nullptr;
}
}
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMSVGNumber)
@ -56,15 +65,6 @@ public:
*/
explicit DOMSVGNumber(nsISupports* aParent);
~DOMSVGNumber() {
// Our mList's weak ref to us must be nulled out when we die. If GC has
// unlinked us using the cycle collector code, then that has already
// happened, and mList is null.
if (mList) {
mList->mItems[mListIndex] = nullptr;
}
}
/**
* Create an unowned copy. The caller is responsible for the first AddRef().
*/

View File

@ -44,6 +44,15 @@ class DOMSVGNumberList MOZ_FINAL : public nsISupports,
friend class AutoChangeNumberListNotifier;
friend class DOMSVGNumber;
~DOMSVGNumberList() {
// Our mAList's weak ref to us must be nulled out when we die. If GC has
// unlinked us using the cycle collector code, then that has already
// happened, and mAList is null.
if (mAList) {
( IsAnimValList() ? mAList->mAnimVal : mAList->mBaseVal ) = nullptr;
}
}
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMSVGNumberList)
@ -62,15 +71,6 @@ public:
InternalListLengthWillChange(aInternalList.Length()); // Sync mItems
}
~DOMSVGNumberList() {
// Our mAList's weak ref to us must be nulled out when we die. If GC has
// unlinked us using the cycle collector code, then that has already
// happened, and mAList is null.
if (mAList) {
( IsAnimValList() ? mAList->mAnimVal : mAList->mBaseVal ) = nullptr;
}
}
virtual JSObject* WrapObject(JSContext *cx) MOZ_OVERRIDE;
nsISupports* GetParentObject()

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