mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-16 23:05:42 +00:00
Merge m-c to b2g-inbound a=merge
This commit is contained in:
commit
c0dc2887cb
1
addon-sdk/source/app-extension/bootstrap.js
vendored
1
addon-sdk/source/app-extension/bootstrap.js
vendored
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -56,8 +56,6 @@ eventTarget.addEventListener("DOMContentLoaded", function handler(event) {
|
||||
resolve();
|
||||
}, false);
|
||||
|
||||
|
||||
|
||||
exports.ready = promise;
|
||||
exports.window = window;
|
||||
|
||||
|
@ -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,19 +145,21 @@ 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,
|
||||
testFunction: suiteModule[name],
|
||||
name: suite + "." + name
|
||||
});
|
||||
setup: suiteModule.setup,
|
||||
teardown: suiteModule.teardown,
|
||||
testFunction: suiteModule[name],
|
||||
name: suite + "." + name
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
cb(tests);
|
||||
return tests;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -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,
|
||||
stopOnError: options.stopOnError,
|
||||
onDone: options.onDone});
|
||||
finder.findTests().then(tests => {
|
||||
runner.startMany({
|
||||
tests: tests,
|
||||
stopOnError: options.stopOnError,
|
||||
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 = {
|
||||
@ -252,9 +255,9 @@ TestRunner.prototype = {
|
||||
assertArray: function(a, message) {
|
||||
this.assertStrictEqual('[object Array]', Object.prototype.toString.apply(a), message);
|
||||
},
|
||||
|
||||
|
||||
assertNumber: function(a, message) {
|
||||
this.assertStrictEqual('[object Number]', Object.prototype.toString.apply(a), message);
|
||||
this.assertStrictEqual('[object Number]', Object.prototype.toString.apply(a), message);
|
||||
},
|
||||
|
||||
done: function done() {
|
||||
@ -321,36 +324,37 @@ TestRunner.prototype = {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Set of assertion functions to wait for an assertion to become true
|
||||
// These functions take the same arguments as the TestRunner.assert* methods.
|
||||
waitUntil: function waitUntil() {
|
||||
return this._waitUntil(this.assert, arguments);
|
||||
},
|
||||
|
||||
|
||||
waitUntilNotEqual: function waitUntilNotEqual() {
|
||||
return this._waitUntil(this.assertNotEqual, arguments);
|
||||
},
|
||||
|
||||
|
||||
waitUntilEqual: function waitUntilEqual() {
|
||||
return this._waitUntil(this.assertEqual, arguments);
|
||||
},
|
||||
|
||||
|
||||
waitUntilMatches: function waitUntilMatches() {
|
||||
return this._waitUntil(this.assertMatches, arguments);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Internal function that waits for an assertion to become true.
|
||||
* @param {Function} assertionMethod
|
||||
* Reference to a TestRunner assertion method like test.assert,
|
||||
* Reference to a TestRunner assertion method like test.assert,
|
||||
* test.assertEqual, ...
|
||||
* @param {Array} args
|
||||
* List of arguments to give to the previous assertion method.
|
||||
* List of arguments to give to the previous assertion method.
|
||||
* All functions in this list are going to be called to retrieve current
|
||||
* 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
|
||||
@ -398,8 +399,8 @@ TestRunner.prototype = {
|
||||
timeout = timer.setTimeout(loop, test.PAUSE_DELAY);
|
||||
}
|
||||
};
|
||||
|
||||
// Automatically call args closures in order to build arguments for
|
||||
|
||||
// Automatically call args closures in order to build arguments for
|
||||
// assertion function
|
||||
let appliedArgs = [];
|
||||
for (let i = 0, l = args.length; i < l; i++) {
|
||||
@ -411,33 +412,21 @@ TestRunner.prototype = {
|
||||
catch(e) {
|
||||
test.fail("Exception when calling asynchronous assertion: " + e +
|
||||
"\n" + e.stack);
|
||||
finished = true;
|
||||
return;
|
||||
return resolve();
|
||||
}
|
||||
}
|
||||
appliedArgs.push(a);
|
||||
}
|
||||
|
||||
|
||||
// Finally call assertion function with current assertion values
|
||||
assertionMethod.apply(mock, appliedArgs);
|
||||
}
|
||||
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) {
|
||||
if (ms === undefined)
|
||||
ms = this.DEFAULT_PAUSE_TIMEOUT;
|
||||
@ -514,3 +503,4 @@ TestRunner.prototype = {
|
||||
this.done();
|
||||
}
|
||||
};
|
||||
exports.TestRunner = TestRunner;
|
||||
|
@ -621,7 +621,4 @@ var runTests = exports.runTests = function runTests(options) {
|
||||
}
|
||||
};
|
||||
|
||||
unload(function() {
|
||||
cService.unregisterListener(consoleListener);
|
||||
});
|
||||
|
||||
unload(_ => cService.unregisterListener(consoleListener));
|
||||
|
@ -33,24 +33,22 @@ function runTests(findAndRunTests) {
|
||||
// are not correctly updated.
|
||||
// For ex: nsIFocusManager.getFocusedElementForWindow may throw
|
||||
// NS_ERROR_ILLEGAL_VALUE exception.
|
||||
require("../timers").setTimeout(function () {
|
||||
harness.runTests({
|
||||
findAndRunTests: findAndRunTests,
|
||||
iterations: cfxArgs.iterations || 1,
|
||||
filter: cfxArgs.filter,
|
||||
profileMemory: cfxArgs.profileMemory,
|
||||
stopOnError: cfxArgs.stopOnError,
|
||||
verbose: cfxArgs.verbose,
|
||||
parseable: cfxArgs.parseable,
|
||||
print: stdout.write,
|
||||
onDone: onDone
|
||||
});
|
||||
}, 0);
|
||||
require("../timers").setTimeout(_ => harness.runTests({
|
||||
findAndRunTests: findAndRunTests,
|
||||
iterations: cfxArgs.iterations || 1,
|
||||
filter: cfxArgs.filter,
|
||||
profileMemory: cfxArgs.profileMemory,
|
||||
stopOnError: cfxArgs.stopOnError,
|
||||
verbose: cfxArgs.verbose,
|
||||
parseable: cfxArgs.parseable,
|
||||
print: stdout.write,
|
||||
onDone: onDone
|
||||
}));
|
||||
}
|
||||
|
||||
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({
|
||||
|
22
addon-sdk/source/lib/sdk/zip/utils.js
Normal file
22
addon-sdk/source/lib/sdk/zip/utils.js
Normal 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;
|
@ -6,5 +6,6 @@
|
||||
"xulrunner", "firefox", "browser"
|
||||
],
|
||||
"loader": "lib/sdk/loader/cuddlefish.js",
|
||||
"license": "MPL 2.0"
|
||||
"license": "MPL 2.0",
|
||||
"unpack": true
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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():
|
||||
|
@ -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):
|
||||
os.remove(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}
|
||||
|
@ -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)
|
||||
|
@ -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']:
|
||||
|
@ -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:
|
||||
|
@ -21,7 +21,6 @@ exports.testNoTabCloseOnStartup = function(assert, done) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
exports.main = function() {
|
||||
tabs.on('close', closeEventDetector);
|
||||
|
||||
|
@ -8,7 +8,7 @@ const { Loader } = require("sdk/test/loader");
|
||||
var setupCalled = false, teardownCalled = false;
|
||||
|
||||
exports.setup = function() {
|
||||
setupCalled = true;
|
||||
setupCalled = true;
|
||||
};
|
||||
|
||||
exports.teardown = function() {
|
||||
@ -16,10 +16,10 @@ exports.teardown = function() {
|
||||
setupCalled = false;
|
||||
};
|
||||
|
||||
// Important note - unit tests are run in alphabetical order. The following
|
||||
// unit tests for setup/teardown are order dependent, sometimes the result of
|
||||
// one test is checked in the next test (testing for teardown does this). When
|
||||
// tests are cohesively a single unit, they are named <test_name> - partN where
|
||||
// Important note - unit tests are run in alphabetical order. The following
|
||||
// unit tests for setup/teardown are order dependent, sometimes the result of
|
||||
// one test is checked in the next test (testing for teardown does this). When
|
||||
// tests are cohesively a single unit, they are named <test_name> - partN where
|
||||
// N is their order in the sequence. Secondly, because these tests should be
|
||||
// run before all others, they start with an A.
|
||||
exports.testASetupTeardownSyncTestPart1 = function(test) {
|
||||
@ -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();
|
||||
};
|
||||
|
||||
@ -48,7 +47,7 @@ exports.testATeardownAsyncTestPart2 = function(test) {
|
||||
|
||||
exports.testWaitUntilInstant = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
|
||||
test.waitUntil(function () true, "waitUntil with instant true pass")
|
||||
.then(function () test.done());
|
||||
}
|
||||
@ -56,60 +55,63 @@ exports.testWaitUntilInstant = function(test) {
|
||||
exports.testWaitUntil = function(test) {
|
||||
test.waitUntilDone();
|
||||
let succeed = false;
|
||||
|
||||
test.waitUntil(function () succeed, "waitUntil pass")
|
||||
.then(function () test.done());
|
||||
|
||||
timer.setTimeout(function () {
|
||||
|
||||
test.waitUntil(_ => succeed, "waitUntil pass")
|
||||
.then(test.done);
|
||||
|
||||
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());
|
||||
|
||||
timer.setTimeout(function () {
|
||||
.then(test.done);
|
||||
|
||||
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());
|
||||
|
||||
timer.setTimeout(function () {
|
||||
.then(test.done);
|
||||
|
||||
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());
|
||||
|
||||
timer.setTimeout(function () {
|
||||
.then(test.done);
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
@ -195,26 +197,26 @@ exports.testAssertFunction = function(test) {
|
||||
test.assertFunction(function() {}, 'assertFunction with function');
|
||||
test.expectFail(function() {
|
||||
test.assertFunction(null, 'assertFunction with non-function');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.testAssertUndefined = function(test) {
|
||||
test.assertUndefined(undefined, 'assertUndefined with undefined');
|
||||
test.expectFail(function() {
|
||||
test.assertUndefined(null, 'assertUndefined with null');
|
||||
});
|
||||
});
|
||||
test.expectFail(function() {
|
||||
test.assertUndefined(false, 'assertUndefined with false');
|
||||
});
|
||||
});
|
||||
test.expectFail(function() {
|
||||
test.assertUndefined(0, 'assertUndefined with 0');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.testAssertNotUndefined = function(test) {
|
||||
test.expectFail(function() {
|
||||
test.assertNotUndefined(undefined, 'assertNotUndefined with undefined');
|
||||
});
|
||||
});
|
||||
test.assertNotUndefined(null, 'assertNotUndefined with null');
|
||||
test.assertNotUndefined(false, 'assertNotUndefined with false');
|
||||
test.assertNotUndefined(0, 'assertNotUndefined with 0');
|
||||
@ -224,7 +226,7 @@ exports.testAssertNull = function(test) {
|
||||
test.assertNull(null, 'assertNull with null');
|
||||
test.expectFail(function() {
|
||||
test.assertNull(undefined, 'assertNull with undefined');
|
||||
});
|
||||
});
|
||||
test.expectFail(function() {
|
||||
test.assertNull(false, 'assertNull with false');
|
||||
});
|
||||
@ -240,7 +242,7 @@ exports.testAssertNotNull = function(test) {
|
||||
|
||||
test.expectFail(function() {
|
||||
test.assertNotNull(null, 'testAssertNotNull with null');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.testAssertObject = function(test) {
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 SessionFileInternal = {
|
||||
/**
|
||||
* The path to sessionstore.js
|
||||
*/
|
||||
path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"),
|
||||
let Path = OS.Path;
|
||||
let profileDir = OS.Constants.Path.profileDir;
|
||||
|
||||
/**
|
||||
* The path to sessionstore.bak
|
||||
*/
|
||||
backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
|
||||
let SessionFileInternal = {
|
||||
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 order in which to search for a valid sessionstore file.
|
||||
*/
|
||||
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]) {
|
||||
try {
|
||||
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;
|
||||
} catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
|
||||
// Ignore exceptions about non-existent files.
|
||||
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 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) {
|
||||
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(
|
||||
|
@ -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.");
|
||||
|
||||
|
@ -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) {
|
||||
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.
|
||||
} 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;
|
||||
let data = Encoder.encode(stateString);
|
||||
let startWriteMs, stopWriteMs;
|
||||
|
||||
try {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
this.hasWrittenState = true;
|
||||
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) {
|
||||
// Don't throw immediately
|
||||
exn = exn || ex;
|
||||
}
|
||||
|
||||
let ret = this._write(stateString, telemetry);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
43
browser/components/sessionstore/src/SessionWorker.jsm
Normal file
43
browser/components/sessionstore/src/SessionWorker.jsm
Normal file
@ -0,0 +1,43 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"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;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
})();
|
@ -27,6 +27,7 @@ EXTRA_JS_MODULES = [
|
||||
'SessionMigration.jsm',
|
||||
'SessionStorage.jsm',
|
||||
'SessionWorker.js',
|
||||
'SessionWorker.jsm',
|
||||
'TabAttributes.jsm',
|
||||
'TabState.jsm',
|
||||
'TabStateCache.jsm',
|
||||
|
@ -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;
|
||||
|
||||
// No valid session found.
|
||||
if (!stateString) {
|
||||
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.
|
||||
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
|
||||
*/
|
||||
|
@ -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]
|
||||
|
@ -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 = [
|
||||
{ url: "about:config",
|
||||
key: "bug 394759 Non-PB",
|
||||
value: "uniq" + (++now) },
|
||||
{ url: "about:mozilla",
|
||||
key: "bug 394759 PB",
|
||||
value: "uniq" + (++now) },
|
||||
];
|
||||
let closedWindowCount = 0;
|
||||
// Prevent VM timers issues, cache now and increment it manually.
|
||||
let now = Date.now();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("browser.sessionstore.interval");
|
||||
windowsToClose.forEach(function(win) {
|
||||
win.close();
|
||||
});
|
||||
const TESTS = [
|
||||
{ url: "about:config",
|
||||
key: "bug 394759 Non-PB",
|
||||
value: "uniq" + (++now) },
|
||||
{ url: "about:mozilla",
|
||||
key: "bug 394759 PB",
|
||||
value: "uniq" + (++now) },
|
||||
];
|
||||
|
||||
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.
|
||||
yield promiseWindowClosed(win);
|
||||
});
|
||||
}
|
||||
|
||||
function testOpenCloseWindow(aIsPrivate, aTest, aCallback) {
|
||||
whenNewWindowLoaded({ private: aIsPrivate }, function(win) {
|
||||
whenBrowserLoaded(win.gBrowser.selectedBrowser, function() {
|
||||
executeSoon(function() {
|
||||
// Mark the window with some unique data to be restored later on.
|
||||
ss.setWindowValue(win, aTest.key, aTest.value);
|
||||
// Close.
|
||||
win.close();
|
||||
aCallback();
|
||||
});
|
||||
});
|
||||
win.gBrowser.selectedBrowser.loadURI(aTest.url);
|
||||
});
|
||||
}
|
||||
function promiseTestOnWindow(aIsPrivate, aValue) {
|
||||
return Task.spawn(function*() {
|
||||
let win = yield promiseNewWindowLoaded({ "private": aIsPrivate });
|
||||
yield promiseCheckClosedWindows(aIsPrivate, aValue);
|
||||
registerCleanupFunction(() => promiseWindowClosed(win));
|
||||
});
|
||||
}
|
||||
|
||||
function testOnWindow(aIsPrivate, aValue, aCallback) {
|
||||
whenNewWindowLoaded({ private: aIsPrivate }, function(win) {
|
||||
windowsToClose.push(win);
|
||||
executeSoon(function() checkClosedWindows(aIsPrivate, aValue, aCallback));
|
||||
});
|
||||
}
|
||||
|
||||
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");
|
||||
closedWindowCount = ss.getClosedWindowCount();
|
||||
is(closedWindowCount, 0, "Correctly set window count");
|
||||
|
||||
executeSoon(aCallback);
|
||||
});
|
||||
yield forceSaveState();
|
||||
closedWindowCount = ss.getClosedWindowCount();
|
||||
is(closedWindowCount, 0, "Correctly set window count");
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
132
browser/components/sessionstore/test/browser_backup_recovery.js
Normal file
132
browser/components/sessionstore/test/browser_backup_recovery.js
Normal 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();
|
||||
});
|
||||
|
@ -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");
|
||||
|
@ -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 {
|
||||
// Wait until initialization is complete
|
||||
yield SessionStore.promiseInitialized;
|
||||
add_task(function* init() {
|
||||
// Wait until initialization is complete
|
||||
yield SessionStore.promiseInitialized;
|
||||
yield SessionFile.wipe();
|
||||
});
|
||||
|
||||
const PREF_UPGRADE = "browser.sessionstore.upgradeBackup.latestBuildID";
|
||||
let buildID = Services.appinfo.platformBuildID;
|
||||
add_task(function* test_upgrade_backup() {
|
||||
const PREF_UPGRADE = "browser.sessionstore.upgradeBackup.latestBuildID";
|
||||
let buildID = Services.appinfo.platformBuildID;
|
||||
info("Let's check if we create an upgrade backup");
|
||||
Services.prefs.setCharPref(PREF_UPGRADE, "");
|
||||
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
|
||||
|
||||
// Write state once before starting the test to
|
||||
// ensure sessionstore.js writes won't happen in between.
|
||||
yield forceSaveState();
|
||||
is(Services.prefs.getCharPref(PREF_UPGRADE), buildID, "upgrade backup should be set");
|
||||
|
||||
// Force backup to take place with a file decided by us
|
||||
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)");
|
||||
is((yield OS.File.exists(Paths.upgradeBackup)), true, "upgrade backup file has been created");
|
||||
|
||||
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");
|
||||
let data = yield OS.File.read(Paths.upgradeBackup);
|
||||
is(contents, (new TextDecoder()).decode(data), "upgrade backup contains the expected contents");
|
||||
|
||||
let data = yield OS.File.read(pathBackup);
|
||||
is(new TextDecoder().decode(data), contents, "upgrade backup contains the expected contents");
|
||||
|
||||
// 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");
|
||||
|
||||
} 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");
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -5,29 +5,28 @@ 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) {
|
||||
do_print("Waiting for session startup initialization");
|
||||
let observer = function() {
|
||||
try {
|
||||
do_print("Session startup initialization observed");
|
||||
Services.obs.removeObserver(observer, "sessionstore-state-finalized");
|
||||
cb();
|
||||
} catch (ex) {
|
||||
do_throw(ex);
|
||||
}
|
||||
};
|
||||
function afterSessionStartupInitialization(cb) {
|
||||
do_print("Waiting for session startup initialization");
|
||||
let observer = function() {
|
||||
try {
|
||||
do_print("Session startup initialization observed");
|
||||
Services.obs.removeObserver(observer, "sessionstore-state-finalized");
|
||||
cb();
|
||||
} catch (ex) {
|
||||
do_throw(ex);
|
||||
}
|
||||
};
|
||||
|
||||
// We need the Crash Monitor initialized for sessionstartup to run
|
||||
// successfully.
|
||||
Components.utils.import("resource://gre/modules/CrashMonitor.jsm");
|
||||
CrashMonitor.init();
|
||||
// We need the Crash Monitor initialized for sessionstartup to run
|
||||
// successfully.
|
||||
Components.utils.import("resource://gre/modules/CrashMonitor.jsm");
|
||||
CrashMonitor.init();
|
||||
|
||||
// Start sessionstartup initialization.
|
||||
let startup = Cc["@mozilla.org/browser/sessionstartup;1"].
|
||||
getService(Ci.nsIObserver);
|
||||
Services.obs.addObserver(startup, "final-ui-startup", false);
|
||||
Services.obs.addObserver(startup, "quit-application", false);
|
||||
Services.obs.notifyObservers(null, "final-ui-startup", "");
|
||||
Services.obs.addObserver(observer, "sessionstore-state-finalized", false);
|
||||
// Start sessionstartup initialization.
|
||||
let startup = Cc["@mozilla.org/browser/sessionstartup;1"].
|
||||
getService(Ci.nsIObserver);
|
||||
Services.obs.addObserver(startup, "final-ui-startup", false);
|
||||
Services.obs.addObserver(startup, "quit-application", false);
|
||||
Services.obs.notifyObservers(null, "final-ui-startup", "");
|
||||
Services.obs.addObserver(observer, "sessionstore-state-finalized", false);
|
||||
};
|
||||
|
@ -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))));
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
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 the provider.
|
||||
let provider = reporter.getProvider("org.mozilla.translation");
|
||||
let measurement = provider.getMeasurement("translation", 1);
|
||||
let values = yield measurement.getValues();
|
||||
yield offerAndDeny("notNow");
|
||||
yield offerAndDeny("neverForSite");
|
||||
yield offerAndDeny("neverForLanguage");
|
||||
yield offerAndDeny("closeButton");
|
||||
|
||||
let day = values.days.getDay(new Date());
|
||||
if (!day) {
|
||||
// This should never happen except when the test runs at midnight.
|
||||
return [0, 0];
|
||||
}
|
||||
// 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);
|
||||
});
|
||||
|
||||
// .get() may return `undefined`, which we can't compute.
|
||||
return [day.get("pageTranslatedCount") || 0, day.get("charactersTranslatedCount") || 0];
|
||||
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.
|
||||
gBrowser.removeTab(tab);
|
||||
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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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());
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -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]
|
||||
|
@ -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");
|
||||
}
|
@ -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:
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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 -->
|
||||
|
@ -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
|
||||
|
||||
|
@ -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=
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -420,6 +420,10 @@ DOMAudioNodeMediaStream::DOMAudioNodeMediaStream(AudioNode* aNode)
|
||||
{
|
||||
}
|
||||
|
||||
DOMAudioNodeMediaStream::~DOMAudioNodeMediaStream()
|
||||
{
|
||||
}
|
||||
|
||||
already_AddRefed<DOMAudioNodeMediaStream>
|
||||
DOMAudioNodeMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow,
|
||||
AudioNode* aNode,
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
******/
|
||||
|
@ -3267,6 +3267,8 @@ public:
|
||||
return mStateMachine->TimeoutExpired(mTimerId);
|
||||
}
|
||||
private:
|
||||
~TimerEvent() {}
|
||||
|
||||
const nsRefPtr<MediaDecoderStateMachine> mStateMachine;
|
||||
int mTimerId;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -608,9 +608,9 @@ public:
|
||||
public nsIInterfaceRequestor,
|
||||
public nsIChannelEventSink
|
||||
{
|
||||
~Listener() {}
|
||||
public:
|
||||
Listener(ChannelMediaResource* aResource) : mResource(aResource) {}
|
||||
~Listener() {}
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
|
@ -1591,6 +1591,7 @@ public:
|
||||
|
||||
class MediaStreamGraphShutdownObserver MOZ_FINAL : public nsIObserver
|
||||
{
|
||||
~MediaStreamGraphShutdownObserver() {}
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
@ -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
|
||||
|
@ -38,6 +38,9 @@ TextTrackCueList::TextTrackCueList(nsISupports* aParent) : mParent(aParent)
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
|
||||
TextTrackCueList::~TextTrackCueList()
|
||||
{}
|
||||
|
||||
JSObject*
|
||||
TextTrackCueList::WrapObject(JSContext* aCx)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -35,6 +35,10 @@ TextTrackList::TextTrackList(nsPIDOMWindow* aOwnerWindow,
|
||||
{
|
||||
}
|
||||
|
||||
TextTrackList::~TextTrackList()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
TextTrackList::UpdateAndGetShowingCues(nsTArray<nsRefPtr<TextTrackCue> >& aCues)
|
||||
{
|
||||
|
@ -28,6 +28,7 @@ public:
|
||||
|
||||
TextTrackList(nsPIDOMWindow* aOwnerWindow);
|
||||
TextTrackList(nsPIDOMWindow* aOwnerWindow, TextTrackManager* aTextTrackManager);
|
||||
~TextTrackList();
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
|
||||
|
||||
|
@ -148,6 +148,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
~TextTrackRegion() {}
|
||||
|
||||
nsCOMPtr<nsISupports> mParent;
|
||||
double mWidth;
|
||||
long mLines;
|
||||
|
@ -50,6 +50,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
~VideoPlaybackQuality() {}
|
||||
|
||||
nsRefPtr<HTMLMediaElement> mElement;
|
||||
DOMHighResTimeStamp mCreationTime;
|
||||
uint64_t mTotalFrames;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -51,6 +51,8 @@ public:
|
||||
size_t SizeOfIncludingThisIfNotShared(MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
private:
|
||||
~PeriodicWave() {}
|
||||
|
||||
nsRefPtr<AudioContext> mContext;
|
||||
nsRefPtr<ThreadSharedFloatArrayBufferList> mCoefficients;
|
||||
uint32_t mLength;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -52,6 +52,9 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
|
||||
nsRefPtr<MediaEngineTabVideoSource> mVideoSource;
|
||||
};
|
||||
|
||||
protected:
|
||||
~MediaEngineTabVideoSource() {}
|
||||
|
||||
private:
|
||||
int mBufW;
|
||||
int mBufH;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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().
|
||||
*/
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user