This commit is contained in:
Wes Kocher 2013-12-19 19:51:23 -08:00
Родитель 5893105125 56d9fc8c65
Коммит b27e63c236
130 изменённых файлов: 6926 добавлений и 1068 удалений

Просмотреть файл

@ -24,6 +24,7 @@ const { Worker } = require("./content/worker");
const { EventTarget } = require("./event/target");
const { emit } = require('./event/core');
const { when } = require('./system/unload');
const selection = require('./selection');
// All user items we add have this class.
const ITEM_CLASS = "addon-context-menu-item";
@ -204,6 +205,66 @@ let URLContext = Class({
});
exports.URLContext = URLContext;
// Matches when the user-supplied predicate returns true
let PredicateContext = Class({
extends: Context,
initialize: function initialize(predicate) {
let options = validateOptions({ predicate: predicate }, {
predicate: {
is: ["function"],
msg: "predicate must be a function."
}
});
internal(this).predicate = options.predicate;
},
isCurrent: function isCurrent(popupNode) {
return internal(this).predicate(populateCallbackNodeData(popupNode));
}
});
exports.PredicateContext = PredicateContext;
// List all editable types of inputs. Or is it better to have a list
// of non-editable inputs?
let editableInputs = {
email: true,
number: true,
password: true,
search: true,
tel: true,
text: true,
textarea: true,
url: true
};
function populateCallbackNodeData(node) {
let window = node.ownerDocument.defaultView;
let data = {};
data.documentType = node.ownerDocument.contentType;
data.documentURL = node.ownerDocument.location.href;
data.targetName = node.nodeName.toLowerCase();
data.targetID = node.id || null ;
if ((data.targetName === 'input' && editableInputs[node.type]) ||
data.targetName === 'textarea') {
data.isEditable = !node.readOnly && !node.disabled;
}
else {
data.isEditable = node.isContentEditable;
}
data.selectionText = selection.text;
data.srcURL = node.src || null;
data.linkURL = node.href || null;
data.value = node.value || null;
return data;
}
function removeItemFromArray(array, item) {
return array.filter(function(i) i !== item);
}

Просмотреть файл

@ -14,6 +14,7 @@ const suites = require('@test/options').allTestModules;
const { Loader } = require("sdk/test/loader");
const cuddlefish = require("sdk/loader/cuddlefish");
let loader = Loader(module);
const NOT_TESTS = ['setup', 'teardown'];
var TestFinder = exports.TestFinder = function TestFinder(options) {
@ -51,11 +52,10 @@ TestFinder.prototype = {
} else
filter = function() {return true};
suites.forEach(
function(suite) {
suites.forEach(function(suite) {
// Load each test file as a main module in its own loader instance
// `suite` is defined by cuddlefish/manifest.py:ManifestBuilder.build
let loader = Loader(module);
let suiteModule;
try {

Просмотреть файл

@ -129,6 +129,12 @@ function isArguments(value) {
}
exports.isArguments = isArguments;
let isMap = value => Object.prototype.toString.call(value) === "[object Map]"
exports.isMap = isMap;
let isSet = value => Object.prototype.toString.call(value) === "[object Set]"
exports.isSet = isSet;
/**
* Returns true if it is a primitive `value`. (null, undefined, number,
* boolean, string)

Просмотреть файл

@ -315,14 +315,24 @@ function getElementWithSelection() {
if (!element)
return null;
let { value, selectionStart, selectionEnd } = element;
try {
// Accessing selectionStart and selectionEnd on e.g. a button
// results in an exception thrown as per the HTML5 spec. See
// http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection
let hasSelection = typeof value === "string" &&
let { value, selectionStart, selectionEnd } = element;
let hasSelection = typeof value === "string" &&
!isNaN(selectionStart) &&
!isNaN(selectionEnd) &&
selectionStart !== selectionEnd;
return hasSelection ? element : null;
return hasSelection ? element : null;
}
catch (err) {
return null;
}
}
/**

Просмотреть файл

@ -0,0 +1,576 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
"use strict";
module.metadata = {
"stability": "experimental"
};
// Disclamer:
// In this module we'll have some common argument / variable names
// to hint their type or behavior.
//
// - `f` stands for "function" that is intended to be side effect
// free.
// - `p` stands for "predicate" that is function which returns logical
// true or false and is intended to be side effect free.
// - `x` / `y` single item of the sequence.
// - `xs` / `ys` sequence of `x` / `y` items where `x` / `y` signifies
// type of the items in sequence, so sequence is not of the same item.
// - `_` used for argument(s) or variable(s) who's values are ignored.
const { complement, flip, identity } = require("../lang/functional");
const { iteratorSymbol } = require("../util/iteration");
const { isArray, isArguments, isMap, isSet,
isString, isBoolean, isNumber } = require("../lang/type");
const Sequence = function Sequence(iterator) {
if (iterator.isGenerator && iterator.isGenerator())
this[iteratorSymbol] = iterator;
else
throw TypeError("Expected generator argument");
};
exports.Sequence = Sequence;
const polymorphic = dispatch => x =>
x === null ? dispatch.null(null) :
x === void(0) ? dispatch.void(void(0)) :
isArray(x) ? (dispatch.array || dispatch.indexed)(x) :
isString(x) ? (dispatch.string || dispatch.indexed)(x) :
isArguments(x) ? (dispatch.arguments || dispatch.indexed)(x) :
isMap(x) ? dispatch.map(x) :
isSet(x) ? dispatch.set(x) :
isNumber(x) ? dispatch.number(x) :
isBoolean(x) ? dispatch.boolean(x) :
dispatch.default(x);
const nogen = function*() {};
const empty = () => new Sequence(nogen);
exports.empty = empty;
const seq = polymorphic({
null: empty,
void: empty,
array: identity,
string: identity,
arguments: identity,
map: identity,
set: identity,
default: x => x instanceof Sequence ? x : new Sequence(x)
});
exports.seq = seq;
// Function to cast seq to string.
const string = (...etc) => "".concat(...etc);
exports.string = string;
// Function for casting seq to plain object.
const object = (...pairs) => {
let result = {};
for (let [key, value] of pairs)
result[key] = value;
return result;
};
exports.object = object;
// Takes `getEnumerator` function that returns `nsISimpleEnumerator`
// and creates lazy sequence of it's items. Note that function does
// not take `nsISimpleEnumerator` itslef because that would allow
// single iteration, which would not be consistent with rest of the
// lazy sequences.
const fromEnumerator = getEnumerator => seq(function* () {
const enumerator = getEnumerator();
while (enumerator.hasMoreElements())
yield enumerator.getNext();
});
exports.fromEnumerator = fromEnumerator;
// Takes `object` and returns lazy sequence of own `[key, value]`
// pairs (does not include inherited and non enumerable keys).
const pairs = polymorphic({
null: empty,
void: empty,
map: identity,
indexed: indexed => seq(function* () {
const count = indexed.length;
let index = 0;
while (index < count) {
yield [index, indexed[index]];
index = index + 1;
}
}),
default: object => seq(function* () {
for (let key of Object.keys(object))
yield [key, object[key]];
})
});
exports.pairs = pairs;
const keys = polymorphic({
null: empty,
void: empty,
indexed: indexed => seq(function* () {
const count = indexed.length;
let index = 0;
while (index < count) {
yield index;
index = index + 1;
}
}),
map: map => seq(function* () {
for (let [key, _] of map)
yield key;
}),
default: object => seq(function* () {
for (let key of Object.keys(object))
yield key;
})
});
exports.keys = keys;
const values = polymorphic({
null: empty,
void: empty,
set: identity,
indexed: indexed => seq(function* () {
const count = indexed.length;
let index = 0;
while (index < count) {
yield indexed[index];
index = index + 1;
}
}),
map: map => seq(function* () {
for (let [_, value] of map) yield value;
}),
default: object => seq(function* () {
for (let key of Object.keys(object)) yield object[key];
})
});
exports.values = values;
// Returns a lazy sequence of `x`, `f(x)`, `f(f(x))` etc.
// `f` must be free of side-effects. Note that returned
// sequence is infinite so it must be consumed partially.
//
// Implements clojure iterate:
// http://clojuredocs.org/clojure_core/clojure.core/iterate
const iterate = (f, x) => seq(function* () {
let state = x;
while (true) {
yield state;
state = f(state);
}
});
exports.iterate = iterate;
// Returns a lazy sequence of the items in sequence for which `p(item)`
// returns `true`. `p` must be free of side-effects.
//
// Implements clojure filter:
// http://clojuredocs.org/clojure_core/clojure.core/filter
const filter = (p, sequence) => seq(function* () {
if (sequence !== null && sequence !== void(0)) {
for (let item of sequence) {
if (p(item))
yield item;
}
}
});
exports.filter = filter;
// Returns a lazy sequence consisting of the result of applying `f` to the
// set of first items of each sequence, followed by applying f to the set
// of second items in each sequence, until any one of the sequences is
// exhausted. Any remaining items in other sequences are ignored. Function
// `f` should accept number-of-sequences arguments.
//
// Implements clojure map:
// http://clojuredocs.org/clojure_core/clojure.core/map
const map = (f, ...sequences) => seq(function* () {
const count = sequences.length;
// Optimize a single sequence case
if (count === 1) {
let [sequence] = sequences;
if (sequence !== null && sequence !== void(0)) {
for (let item of sequence)
yield f(item);
}
}
else {
// define args array that will be recycled on each
// step to aggregate arguments to be passed to `f`.
let args = [];
// define inputs to contain started generators.
let inputs = [];
let index = 0;
while (index < count) {
inputs[index] = sequences[index][iteratorSymbol]();
index = index + 1;
}
// Run loop yielding of applying `f` to the set of
// items at each step until one of the `inputs` is
// exhausted.
let done = false;
while (!done) {
let index = 0;
let value = void(0);
while (index < count && !done) {
({ done, value }) = inputs[index].next();
// If input is not exhausted yet store value in args.
if (!done) {
args[index] = value;
index = index + 1;
}
}
// If none of the inputs is exhasted yet, `args` contain items
// from each input so we yield application of `f` over them.
if (!done)
yield f(...args);
}
}
});
exports.map = map;
// Returns a lazy sequence of the intermediate values of the reduction (as
// per reduce) of sequence by `f`, starting with `initial` value if provided.
//
// Implements clojure reductions:
// http://clojuredocs.org/clojure_core/clojure.core/reductions
const reductions = (...params) => {
const count = params.length;
let hasInitial = false;
let f, initial, source;
if (count === 2) {
([f, source]) = params;
}
else if (count === 3) {
([f, initial, source]) = params;
hasInitial = true;
}
else {
throw Error("Invoked with wrong number of arguments: " + count);
}
const sequence = seq(source);
return seq(function* () {
let started = hasInitial;
let result = void(0);
// If initial is present yield it.
if (hasInitial)
yield (result = initial);
// For each item of the sequence accumulate new result.
for (let item of sequence) {
// If nothing has being yield yet set result to first
// item and yield it.
if (!started) {
started = true;
yield (result = item);
}
// Otherwise accumulate new result and yield it.
else {
yield (result = f(result, item));
}
}
// If nothing has being yield yet it's empty sequence and no
// `initial` was provided in which case we need to yield `f()`.
if (!started)
yield f();
});
};
exports.reductions = reductions;
// `f` should be a function of 2 arguments. If `initial` is not supplied,
// returns the result of applying `f` to the first 2 items in sequence, then
// applying `f` to that result and the 3rd item, etc. If sequence contains no
// items, `f` must accept no arguments as well, and reduce returns the
// result of calling f with no arguments. If sequence has only 1 item, it
// is returned and `f` is not called. If `initial` is supplied, returns the
// result of applying `f` to `initial` and the first item in sequence, then
// applying `f` to that result and the 2nd item, etc. If sequence contains no
// items, returns `initial` and `f` is not called.
//
// Implements clojure reduce:
// http://clojuredocs.org/clojure_core/clojure.core/reduce
const reduce = (...args) => {
const xs = reductions(...args);
let x;
for (x of xs) void(0);
return x;
};
exports.reduce = reduce;
const each = (f, sequence) => {
for (let x of seq(sequence)) void(f(x));
};
exports.each = each;
const inc = x => x + 1;
// Returns the number of items in the sequence. `count(null)` && `count()`
// returns `0`. Also works on strings, arrays, Maps & Sets.
// Implements clojure count:
// http://clojuredocs.org/clojure_core/clojure.core/count
const count = polymorphic({
null: _ => 0,
void: _ => 0,
indexed: indexed => indexed.length,
map: map => map.size,
set: set => set.size,
default: xs => reduce(inc, 0, xs)
});
exports.count = count;
// Returns `true` if sequence has no items.
// Implements clojure empty?:
// http://clojuredocs.org/clojure_core/clojure.core/empty_q
const isEmpty = sequence => {
// Treat `null` and `undefined` as empty sequences.
if (sequence === null || sequence === void(0))
return true;
// If contains any item non empty so return `false`.
for (let _ of sequence)
return false;
// If has not returned yet, there was nothing to iterate
// so it's empty.
return true;
};
exports.isEmpty = isEmpty;
const and = (a, b) => a && b;
// Returns true if `p(x)` is logical `true` for every `x` in sequence, else
// `false`.
//
// Implements clojure every?:
// http://clojuredocs.org/clojure_core/clojure.core/every_q
const isEvery = (p, sequence) => {
if (sequence !== null && sequence !== void(0)) {
for (let item of sequence) {
if (!p(item))
return false;
}
}
return true;
};
exports.isEvery = isEvery;
// Returns the first logical true value of (p x) for any x in sequence,
// else `null`.
//
// Implements clojure some:
// http://clojuredocs.org/clojure_core/clojure.core/some
const some = (p, sequence) => {
if (sequence !== null && sequence !== void(0)) {
for (let item of sequence) {
if (p(item))
return true;
}
}
return null;
};
exports.some = some;
// Returns a lazy sequence of the first `n` items in sequence, or all items if
// there are fewer than `n`.
//
// Implements clojure take:
// http://clojuredocs.org/clojure_core/clojure.core/take
const take = (n, sequence) => n <= 0 ? empty() : seq(function* () {
let count = n;
for (let item of sequence) {
yield item;
count = count - 1;
if (count === 0) break;
}
});
exports.take = take;
// Returns a lazy sequence of successive items from sequence while
// `p(item)` returns `true`. `p` must be free of side-effects.
//
// Implements clojure take-while:
// http://clojuredocs.org/clojure_core/clojure.core/take-while
const takeWhile = (p, sequence) => seq(function* () {
for (let item of sequence) {
if (!p(item))
break;
yield item;
}
});
exports.takeWhile = takeWhile;
// Returns a lazy sequence of all but the first `n` items in
// sequence.
//
// Implements clojure drop:
// http://clojuredocs.org/clojure_core/clojure.core/drop
const drop = (n, sequence) => seq(function* () {
if (sequence !== null && sequence !== void(0)) {
let count = n;
for (let item of sequence) {
if (count > 0)
count = count - 1;
else
yield item;
}
}
});
exports.drop = drop;
// Returns a lazy sequence of the items in sequence starting from the
// first item for which `p(item)` returns falsy value.
//
// Implements clojure drop-while:
// http://clojuredocs.org/clojure_core/clojure.core/drop-while
const dropWhile = (p, sequence) => seq(function* () {
let keep = false;
for (let item of sequence) {
keep = keep || !p(item);
if (keep) yield item;
}
});
exports.dropWhile = dropWhile;
// Returns a lazy sequence representing the concatenation of the
// suplied sequences.
//
// Implements clojure conact:
// http://clojuredocs.org/clojure_core/clojure.core/concat
const concat = (...sequences) => seq(function* () {
for (let sequence of sequences)
for (let item of sequence)
yield item;
});
exports.concat = concat;
// Returns the first item in the sequence.
//
// Implements clojure first:
// http://clojuredocs.org/clojure_core/clojure.core/first
const first = sequence => {
if (sequence !== null && sequence !== void(0)) {
for (let item of sequence)
return item;
}
return null;
};
exports.first = first;
// Returns a possibly empty sequence of the items after the first.
//
// Implements clojure rest:
// http://clojuredocs.org/clojure_core/clojure.core/rest
const rest = sequence => drop(1, sequence);
exports.rest = rest;
// Returns the value at the index. Returns `notFound` or `undefined`
// if index is out of bounds.
const nth = (xs, n, notFound) => {
if (n >= 0) {
if (isArray(xs) || isArguments(xs) || isString(xs)) {
return n < xs.length ? xs[n] : notFound;
}
else if (xs !== null && xs !== void(0)) {
let count = n;
for (let x of xs) {
if (count <= 0)
return x;
count = count - 1;
}
}
}
return notFound;
};
exports.nth = nth;
// Return the last item in sequence, in linear time.
// If `sequence` is an array or string or arguments
// returns in constant time.
// Implements clojure last:
// http://clojuredocs.org/clojure_core/clojure.core/last
const last = polymorphic({
null: _ => null,
void: _ => null,
indexed: indexed => indexed[indexed.length - 1],
map: xs => reduce((_, x) => x, xs),
set: xs => reduce((_, x) => x, xs),
default: xs => reduce((_, x) => x, xs)
});
exports.last = last;
// Return a lazy sequence of all but the last `n` (default 1) items
// from the give `xs`.
//
// Implements clojure drop-last:
// http://clojuredocs.org/clojure_core/clojure.core/drop-last
const dropLast = flip((xs, n=1) => seq(function* () {
let ys = [];
for (let x of xs) {
ys.push(x);
if (ys.length > n)
yield ys.shift();
}
}));
exports.dropLast = dropLast;
// Returns a lazy sequence of the elements of `xs` with duplicates
// removed
//
// Implements clojure distinct
// http://clojuredocs.org/clojure_core/clojure.core/distinct
const distinct = sequence => seq(function* () {
let items = new Set();
for (let item of sequence) {
if (!items.has(item)) {
items.add(item);
yield item;
}
}
});
exports.distinct = distinct;
// Returns a lazy sequence of the items in `xs` for which
// `p(x)` returns false. `p` must be free of side-effects.
//
// Implements clojure remove
// http://clojuredocs.org/clojure_core/clojure.core/remove
const remove = (p, xs) => filter(complement(p), xs);
exports.remove = remove;
// Returns the result of applying concat to the result of
// `map(f, xs)`. Thus function `f` should return a sequence.
//
// Implements clojure mapcat
// http://clojuredocs.org/clojure_core/clojure.core/mapcat
const mapcat = (f, sequence) => seq(function* () {
const sequences = map(f, sequence);
for (let sequence of sequences)
for (let item of sequence)
yield item;
});
exports.mapcat = mapcat;

Просмотреть файл

@ -26,14 +26,12 @@ usage = """
%prog [options] command [command-specific options]
Supported Commands:
docs - view web-based documentation
init - create a sample addon in an empty directory
test - run tests
run - run program
xpi - generate an xpi
Internal Commands:
sdocs - export static documentation
testcfx - test the cfx tool
testex - test all example code
testpkgs - test all installed packages
@ -223,11 +221,6 @@ parser_groups = (
metavar=None,
default=False,
cmds=['test', 'testex', 'testpkgs'])),
(("", "--override-version",), dict(dest="override_version",
help="Pass in a version string to use in generated docs",
metavar=None,
default=False,
cmds=['sdocs'])),
(("", "--check-memory",), dict(dest="check_memory",
help="attempts to detect leaked compartments after a test run",
action="store_true",
@ -238,6 +231,10 @@ parser_groups = (
help="Where to put the finished .xpi",
default=None,
cmds=['xpi'])),
(("", "--manifest-overload",), dict(dest="manifest_overload",
help="JSON file to overload package.json properties",
default=None,
cmds=['xpi'])),
]
),
@ -249,12 +246,6 @@ parser_groups = (
default=None,
cmds=['test', 'run', 'testex', 'testpkgs',
'testall'])),
(("", "--baseurl",), dict(dest="baseurl",
help=("root of static docs tree: "
"for example: 'http://me.com/the_docs/'"),
metavar=None,
default='',
cmds=['sdocs'])),
(("", "--test-runner-pkg",), dict(dest="test_runner_pkg",
help=("name of package "
"containing test runner "
@ -626,23 +617,6 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
return
test_cfx(env_root, options.verbose)
return
elif command == "docs":
from cuddlefish.docs import generate
if len(args) > 1:
docs_home = generate.generate_named_file(env_root, filename_and_path=args[1])
else:
docs_home = generate.generate_local_docs(env_root)
webbrowser.open(docs_home)
return
elif command == "sdocs":
from cuddlefish.docs import generate
filename=""
if options.override_version:
filename = generate.generate_static_docs(env_root, override_version=options.override_version)
else:
filename = generate.generate_static_docs(env_root)
print >>stdout, "Wrote %s." % filename
return
elif command not in ["xpi", "test", "run"]:
print >>sys.stderr, "Unknown command: %s" % command
print >>sys.stderr, "Try using '--help' for assistance."
@ -666,6 +640,10 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
target_cfg_json = os.path.join(options.pkgdir, 'package.json')
target_cfg = packaging.get_config_in_dir(options.pkgdir)
if options.manifest_overload:
for k, v in packaging.load_json_file(options.manifest_overload).items():
target_cfg[k] = v
# At this point, we're either building an XPI or running Jetpack code in
# a Mozilla application (which includes running tests).

Просмотреть файл

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

Просмотреть файл

@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os, unittest, shutil
import zipfile
from StringIO import StringIO
from cuddlefish import initializer
from cuddlefish.templates import TEST_MAIN_JS, PACKAGE_JSON
@ -196,6 +197,21 @@ class TestCfxQuits(unittest.TestCase):
self.assertIn("1 of 1 tests passed.", err)
self.assertIn("Program terminated successfully.", err)
def test_cfx_xpi(self):
addon_path = os.path.join(tests_path,
"addons", "simplest-test")
rc, out, err = self.run_cfx(addon_path, \
["xpi", "--manifest-overload", "manifest-overload.json"])
self.assertEqual(rc, 0)
# Ensure that the addon version from our manifest overload is used
# in install.rdf
xpi_path = os.path.join(addon_path, "simplest-test.xpi")
xpi = zipfile.ZipFile(xpi_path, "r")
manifest = xpi.read("install.rdf")
self.assertIn("<em:version>1.0-nightly</em:version>", manifest)
xpi.close()
os.remove(xpi_path)
def test_cfx_init(self):
# Create an empty test directory
addon_path = os.path.abspath(os.path.join(".test_tmp", "test-cfx-init"))

Просмотреть файл

@ -0,0 +1,24 @@
/* 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, Cm, components } = require('chrome');
const self = require('sdk/self');
const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
exports.testTranslators = function(assert, done) {
AddonManager.getAddonByID(self.id, function(addon) {
let count = 0;
addon.translators.forEach(function({ name }) {
count++;
assert.equal(name, 'Erik Vold', 'The translator keys are correct');
});
assert.equal(count, 1, 'The translator key count is correct');
assert.equal(addon.translators.length, 1, 'The translator key length is correct');
done();
});
}
require('sdk/test/runner').runTestsFromModule(module);

Просмотреть файл

@ -0,0 +1,6 @@
{
"id": "test-translators",
"translators": [
"Erik Vold"
]
}

Просмотреть файл

@ -6,6 +6,9 @@
<head>
<meta charset="UTF-8">
<title>Context menu test</title>
<style>
p { display: inline-block; }
</style>
</head>
<body>
<p>
@ -52,5 +55,27 @@
<p>
<input type="submit" id="button">
</p>
<p>
<a class="predicate-test-a" href="#test">
A link with no ID and an anchor, used by PredicateContext tests.
</a>
</p>
<p>
<input type="text" id="textbox" value="test value">
</p>
<p>
<input type="text" id="readonly-textbox" readonly="true" value="readonly value">
</p>
<p>
<input type="text" id="disabled-textbox" disabled="true" value="disabled value">
</p>
<p>
<p contenteditable="true" id="editable">This content is editable.</p>
</p>
</body>
</html>

Просмотреть файл

@ -3135,6 +3135,488 @@ exports.testSelectionInOuterFrameNoMatch = function (assert, done) {
});
};
// Test that the return value of the predicate function determines if
// item is shown
exports.testPredicateContextControl = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let itemTrue = loader.cm.Item({
label: "visible",
context: loader.cm.PredicateContext(function () { return true; })
});
let itemFalse = loader.cm.Item({
label: "hidden",
context: loader.cm.PredicateContext(function () { return false; })
});
test.showMenu(null, function (popup) {
test.checkMenu([itemTrue, itemFalse], [itemFalse], []);
test.done();
});
};
// Test that the data object has the correct document type
exports.testPredicateContextDocumentType = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.equal(data.documentType, 'text/html');
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(null, function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the correct document URL
exports.testPredicateContextDocumentURL = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.equal(data.documentURL, TEST_DOC_URL);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(null, function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the correct element name
exports.testPredicateContextTargetName = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.targetName, "input");
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("button"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the correct ID
exports.testPredicateContextTargetIDSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.targetID, "button");
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("button"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the correct ID
exports.testPredicateContextTargetIDNotSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.targetID, null);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object is showing editable correctly for regular text inputs
exports.testPredicateContextTextBoxIsEditable = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, true);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("textbox"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object is showing editable correctly for readonly text inputs
exports.testPredicateContextReadonlyTextBoxIsNotEditable = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, false);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("readonly-textbox"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object is showing editable correctly for disabled text inputs
exports.testPredicateContextDisabledTextBoxIsNotEditable = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, false);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("disabled-textbox"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object is showing editable correctly for text areas
exports.testPredicateContextTextAreaIsEditable = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, true);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("textfield"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that non-text inputs are not considered editable
exports.testPredicateContextButtonIsNotEditable = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, false);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("button"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object is showing editable correctly
exports.testPredicateContextNonInputIsNotEditable = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, false);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("image"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object is showing editable correctly for HTML contenteditable elements
exports.testPredicateContextEditableElement = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.isEditable, true);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("editable"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object does not have a selection when there is none
exports.testPredicateContextNoSelectionInPage = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.selectionText, null);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(null, function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object includes the selected page text
exports.testPredicateContextSelectionInPage = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
// since we might get whitespace
assert.ok(data.selectionText && data.selectionText.search(/^\s*Some text.\s*$/) != -1,
'Expected "Some text.", got "' + data.selectionText + '"');
return true;
})
})];
test.withTestDoc(function (window, doc) {
window.getSelection().selectAllChildren(doc.getElementById("text"));
test.showMenu(null, function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object includes the selected input text
exports.testPredicateContextSelectionInTextBox = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
// since we might get whitespace
assert.strictEqual(data.selectionText, "t v");
return true;
})
})];
test.withTestDoc(function (window, doc) {
let textbox = doc.getElementById("textbox");
textbox.focus();
textbox.setSelectionRange(3, 6);
test.showMenu(textbox, function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the correct src for an image
exports.testPredicateContextTargetSrcSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let image;
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.srcURL, image.src);
return true;
})
})];
test.withTestDoc(function (window, doc) {
image = doc.getElementById("image");
test.showMenu(image, function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has no src for a link
exports.testPredicateContextTargetSrcNotSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.srcURL, null);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("link"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the correct link set
exports.testPredicateContextTargetLinkSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let image;
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.linkURL, TEST_DOC_URL + "#test");
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has no link for an image
exports.testPredicateContextTargetLinkNotSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.linkURL, null);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("image"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has the value for an input textbox
exports.testPredicateContextTargetValueSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let image;
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.value, "test value");
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("textbox"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// Test that the data object has no value for an image
exports.testPredicateContextTargetValueNotSet = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let items = [loader.cm.Item({
label: "item",
context: loader.cm.PredicateContext(function (data) {
assert.strictEqual(data.value, null);
return true;
})
})];
test.withTestDoc(function (window, doc) {
test.showMenu(doc.getElementById("image"), function (popup) {
test.checkMenu(items, [], []);
test.done();
});
});
};
// NO TESTS BELOW THIS LINE! ///////////////////////////////////////////////////
// This makes it easier to run tests by handling things like opening the menu,

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -9,6 +9,7 @@ const { getTabForWindow } = require('sdk/tabs/helpers');
const app = require("sdk/system/xul-app");
const { viewFor } = require("sdk/view/core");
const { getTabId } = require("sdk/tabs/utils");
const { defer } = require("sdk/lang/functional");
// The primary test tab
var primaryTab;
@ -139,14 +140,16 @@ exports["test behavior on close"] = function(assert, done) {
};
exports["test viewFor(tab)"] = (assert, done) => {
tabs.once("open", tab => {
// Note we defer handlers as length collection is updated after
// handler is invoked, so if test is finished before counnts are
// updated wrong length will show up in followup tests.
tabs.once("open", defer(tab => {
const view = viewFor(tab);
assert.ok(view, "view is returned");
assert.equal(getTabId(view), tab.id, "tab has a same id");
tab.close();
done();
});
tab.close(defer(done));
}));
tabs.open({ url: "about:mozilla" });
}

Просмотреть файл

@ -8,6 +8,8 @@ const { browserWindows } = require('sdk/windows');
const { viewFor } = require('sdk/view/core');
const { Ci } = require("chrome");
const { isBrowser, getWindowTitle } = require("sdk/window/utils");
const { defer } = require("sdk/lang/functional");
// TEST: browserWindows Iterator
exports.testBrowserWindowsIterator = function(assert) {
@ -30,6 +32,7 @@ exports.testBrowserWindowsIterator = function(assert) {
}
};
exports.testWindowTabsObject_alt = function(assert, done) {
let window = browserWindows.activeWindow;
window.tabs.open({
@ -58,6 +61,7 @@ exports.testWindowActivateMethod_simple = function(assert) {
'Active tab is active after window.activate() call');
};
exports["test getView(window)"] = function(assert, done) {
browserWindows.once("open", window => {
const view = viewFor(window);
@ -68,9 +72,11 @@ exports["test getView(window)"] = function(assert, done) {
"window has a right title");
window.close();
window.destroy();
assert.equal(viewFor(window), null, "window view is gone");
done();
// Defer handler cause window is destroyed after event is dispatched.
browserWindows.once("close", defer(_ => {
assert.equal(viewFor(window), null, "window view is gone");
done();
}));
});

Просмотреть файл

@ -51,7 +51,14 @@ XPCOMUtils.defineLazyServiceGetter(this, "smsService",
"@mozilla.org/sms/smsservice;1",
"nsISmsService");
XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
"@mozilla.org/settingsService;1",
"nsISettingsService");
const kSilentSmsReceivedTopic = "silent-sms-received";
const kMozSettingsChangedObserverTopic = "mozsettings-changed";
const kRilDefaultDataServiceId = "ril.data.defaultServiceId";
const MOBILEMESSAGECALLBACK_CID =
Components.ID("{b484d8c9-6be4-4f94-ab60-c9c7ebcc853d}");
@ -61,6 +68,7 @@ const MOBILEMESSAGECALLBACK_CID =
// from JS.
function SilentSmsRequest() {
}
SilentSmsRequest.prototype = {
__exposedProps__: {
onsuccess: 'rw',
@ -80,19 +88,70 @@ SilentSmsRequest.prototype = {
},
notifyMessageSent: function notifyMessageSent(aMessage) {
if (_DEBUG) {
_debug("Silent message successfully sent");
if (_debug) {
LOG("Silent message successfully sent");
}
this._onsuccess(aMessage);
},
notifySendMessageFailed: function notifySendMessageFailed(aError) {
if (_DEBUG) {
_debug("Error sending silent message " + aError);
if (_debug) {
LOG("Error sending silent message " + aError);
}
this._onerror(aError);
}
};
function PaymentSettings() {
this.dataServiceId = 0;
Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
gSettingsService.createLock().get(kRilDefaultDataServiceId, this);
}
PaymentSettings.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsISettingsServiceCallback,
Ci.nsIObserver]),
handle: function(aName, aValue) {
if (aName != kRilDefaultDataServiceId) {
return;
}
this.dataServiceId = aValue;
if (_debug) {
LOG("dataServiceId " + this.dataServiceId);
}
},
observe: function(aSubject, aTopic, aData) {
if (aTopic != kMozSettingsChangedObserverTopic) {
return;
}
try {
let setting = JSON.parse(aData);
if (!setting.key || setting.key !== kRilDefaultDataServiceId) {
return;
}
this.dataServiceId = setting.value;
if (_debug) {
LOG("dataServiceId " + setting.value);
}
} catch (e) {
if (_debug) {
LOG(e);
}
}
},
cleanup: function() {
Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
}
};
#endif
const kClosePaymentFlowEvent = "close-payment-flow-dialog";
@ -120,6 +179,12 @@ let PaymentProvider = {
},
#endif
_init: function _init() {
#ifdef MOZ_B2G_RIL
this._settings = new PaymentSettings();
#endif
},
_closePaymentFlowDialog: function _closePaymentFlowDialog(aCallback) {
// After receiving the payment provider confirmation about the
// successful or failed payment flow, we notify the UI to close the
@ -194,7 +259,7 @@ let PaymentProvider = {
// Bug 938993. Support Multi-SIM for Payments.
get iccInfo() {
delete this.iccInfo;
return this.iccInfo = iccProvider.getIccInfo(0);
return this.iccInfo = iccProvider.getIccInfo(this._settings.dataServiceId);
},
get iccIds() {
@ -304,6 +369,7 @@ let PaymentProvider = {
}
this._silentNumbers = null;
this._silentSmsObservers = null;
this._settings.cleanup();
Services.obs.removeObserver(this._onSilentSms, kSilentSmsReceivedTopic);
}
#endif
@ -313,6 +379,7 @@ let PaymentProvider = {
// of the payment flow to the appropriate content process.
addMessageListener("Payment:LoadShim", function receiveMessage(aMessage) {
gRequestId = aMessage.json.requestId;
PaymentProvider._init();
});
addEventListener("DOMWindowCreated", function(e) {

Просмотреть файл

@ -1067,6 +1067,7 @@ let RemoteDebugger = {
}
DebuggerServer.registerModule("devtools/server/actors/inspector");
DebuggerServer.registerModule("devtools/server/actors/styleeditor");
DebuggerServer.registerModule("devtools/server/actors/stylesheets");
DebuggerServer.enableWebappsContentActor = true;
}
DebuggerServer.addActors('chrome://browser/content/dbg-browser-actors.js');

Просмотреть файл

@ -1,4 +1,4 @@
{
"revision": "c6aeaa41977d9410b9baac23ccea6909f796fd1f",
"revision": "c8f56fb9f9888d4f4e7f55a52a639b9271b8d207",
"repo_path": "/integration/gaia-central"
}

Просмотреть файл

@ -6463,9 +6463,6 @@ var gIdentityHandler = {
if (PopupNotifications.getNotification("mixed-content-blocked", gBrowser.selectedBrowser))
return;
let helplink = document.getElementById("mixed-content-blocked-helplink");
helplink.setAttribute("onclick", "openHelpLink('mixed-content');");
let brandBundle = document.getElementById("bundle_brand");
let brandShortName = brandBundle.getString("brandShortName");
let messageString = gNavigatorBundle.getFormattedString("mixedContentBlocked.message", [brandShortName]);

Просмотреть файл

@ -62,6 +62,7 @@
<description id="mixed-content-blocked-moreinfo">&mixedContentBlocked.moreinfo;</description>
<separator/>
<label id="mixed-content-blocked-helplink" class="text-link"
onclick="openHelpLink('mixed-content');"
value="&mixedContentBlocked.helplink;"/>
</popupnotificationcontent>
</popupnotification>

Просмотреть файл

@ -146,6 +146,9 @@ function basicNotification() {
case "removed":
self.removedCallbackTriggered = true;
break;
case "swapping":
self.swappingCallbackTriggered = true;
break;
}
}
};
@ -978,6 +981,49 @@ var tests = [
triggerMainCommand(popup);
},
onHidden: function() { }
},
{ // Test #31 - Moving a tab to a new window should remove non-swappable
// notifications.
run: function() {
gBrowser.selectedTab = gBrowser.addTab("about:blank");
let notifyObj = new basicNotification();
showNotification(notifyObj);
let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
whenDelayedStartupFinished(win, function() {
let [tab] = win.gBrowser.tabs;
let anchor = win.document.getElementById("default-notification-icon");
win.PopupNotifications._reshowNotifications(anchor);
ok(win.PopupNotifications.panel.childNodes.length == 0,
"no notification displayed in new window");
ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
ok(notifyObj.removedCallbackTriggered, "the removed callback was triggered");
win.close();
goNext();
});
}
},
{ // Test #32 - Moving a tab to a new window should preserve swappable notifications.
run: function() {
gBrowser.selectedTab = gBrowser.addTab("about:blank");
let notifyObj = new basicNotification();
let originalCallback = notifyObj.options.eventCallback;
notifyObj.options.eventCallback = function (eventName) {
originalCallback(eventName);
return eventName == "swapping";
};
showNotification(notifyObj);
let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
whenDelayedStartupFinished(win, function() {
let [tab] = win.gBrowser.tabs;
let anchor = win.document.getElementById("default-notification-icon");
win.PopupNotifications._reshowNotifications(anchor);
checkPopup(win.PopupNotifications.panel, notifyObj);
ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
win.close();
goNext();
});
}
}
];

Просмотреть файл

@ -150,7 +150,8 @@ let CustomizableUIInternal = {
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"
"add-ons-button",
"developer-button",
];
if (gPalette.has("switch-to-metro-button")) {

Просмотреть файл

@ -27,7 +27,7 @@ add_task(function testWrapUnwrap() {
// Creating and destroying a widget should correctly deal with panel placeholders
add_task(function testPanelPlaceholders() {
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
is(panel.querySelectorAll(".panel-customization-placeholder").length, isInWin8() ? 2 : 3, "The number of placeholders should be correct.");
is(panel.querySelectorAll(".panel-customization-placeholder").length, isInWin8() ? 1 : 2, "The number of placeholders should be correct.");
CustomizableUI.createWidget({id: kTestWidget2, label: 'Pretty label', tooltiptext: 'Pretty tooltip', defaultArea: CustomizableUI.AREA_PANEL});
let elem = document.getElementById(kTestWidget2);
let wrapper = document.getElementById("wrapper-" + kTestWidget2);
@ -35,7 +35,7 @@ add_task(function testPanelPlaceholders() {
ok(wrapper, "There should be a wrapper");
is(wrapper.firstChild.id, kTestWidget2, "Wrapper should have test widget");
is(wrapper.parentNode, panel, "Wrapper should be in panel");
is(panel.querySelectorAll(".panel-customization-placeholder").length, isInWin8() ? 1 : 2, "The number of placeholders should be correct.");
is(panel.querySelectorAll(".panel-customization-placeholder").length, isInWin8() ? 3 : 1, "The number of placeholders should be correct.");
CustomizableUI.destroyWidget(kTestWidget2);
wrapper = document.getElementById("wrapper-" + kTestWidget2);
ok(!wrapper, "There should be a wrapper");

Просмотреть файл

@ -9,13 +9,13 @@ Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
// Dragging an item from the palette to another button in the panel should work.
add_task(function() {
yield startCustomizing();
let btn = document.getElementById("developer-button");
let btn = document.getElementById("feed-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let lastButtonIndex = placements.length - 1;
let lastButton = placements[lastButtonIndex];
let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["developer-button", lastButton]);
let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["feed-button", lastButton]);
let lastButtonNode = document.getElementById(lastButton);
simulateItemDrag(btn, lastButtonNode);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
@ -28,11 +28,11 @@ add_task(function() {
// Dragging an item from the palette to the panel itself should also work.
add_task(function() {
yield startCustomizing();
let btn = document.getElementById("developer-button");
let btn = document.getElementById("feed-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let placementsAfterAppend = placements.concat(["developer-button"]);
let placementsAfterAppend = placements.concat(["feed-button"]);
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
@ -48,12 +48,12 @@ add_task(function() {
CustomizableUI.removeWidgetFromArea(widgetIds.shift());
}
yield startCustomizing();
let btn = document.getElementById("developer-button");
let btn = document.getElementById("feed-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
assertAreaPlacements(panel.id, []);
let placementsAfterAppend = ["developer-button"];
let placementsAfterAppend = ["feed-button"];
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");

Просмотреть файл

@ -22,7 +22,8 @@ add_task(function() {
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
"add-ons-button",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
simulateItemDrag(zoomControls, printButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
@ -47,7 +48,8 @@ add_task(function() {
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
"add-ons-button",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
simulateItemDrag(zoomControls, savePageButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
@ -70,7 +72,8 @@ add_task(function() {
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
"add-ons-button",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
simulateItemDrag(zoomControls, newWindowButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
@ -92,7 +95,8 @@ add_task(function() {
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
"add-ons-button",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
simulateItemDrag(zoomControls, historyPanelMenu);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
@ -118,7 +122,8 @@ add_task(function() {
"zoom-controls",
"find-button",
"preferences-button",
"add-ons-button"];
"add-ons-button",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
simulateItemDrag(zoomControls, preferencesButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
@ -131,10 +136,10 @@ add_task(function() {
// Dragging an item from the palette to before the zoom-controls should move it and two other buttons before the zoom controls.
add_task(function() {
yield startCustomizing();
let developerButton = document.getElementById("developer-button");
let openFileButton = document.getElementById("open-file-button");
let zoomControls = document.getElementById("zoom-controls");
let placementsAfterInsert = ["edit-controls",
"developer-button",
"open-file-button",
"new-window-button",
"privatebrowsing-button",
"zoom-controls",
@ -144,9 +149,10 @@ add_task(function() {
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
"add-ons-button",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterInsert);
simulateItemDrag(developerButton, zoomControls);
simulateItemDrag(openFileButton, zoomControls);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
@ -157,9 +163,9 @@ add_task(function() {
"feed-button should be a child of wrapper-feed-button");
is(feedWrapper.getAttribute("place"), "palette",
"The feed-button wrapper should have it's place set to 'palette'");
simulateItemDrag(developerButton, palette);
is(developerButton.parentNode.tagName, "toolbarpaletteitem",
"The developer-button should be wrapped by a toolbarpaletteitem");
simulateItemDrag(openFileButton, palette);
is(openFileButton.parentNode.tagName, "toolbarpaletteitem",
"The open-file-button should be wrapped by a toolbarpaletteitem");
let newWindowButton = document.getElementById("new-window-button");
simulateItemDrag(zoomControls, newWindowButton);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
@ -169,9 +175,9 @@ add_task(function() {
// should move it and two other buttons before the edit and zoom controls.
add_task(function() {
yield startCustomizing();
let developerButton = document.getElementById("developer-button");
let openFileButton = document.getElementById("open-file-button");
let editControls = document.getElementById("edit-controls");
let placementsAfterInsert = ["developer-button",
let placementsAfterInsert = ["open-file-button",
"new-window-button",
"privatebrowsing-button",
"edit-controls",
@ -182,9 +188,10 @@ add_task(function() {
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
"add-ons-button",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterInsert);
simulateItemDrag(developerButton, editControls);
simulateItemDrag(openFileButton, editControls);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
@ -195,9 +202,9 @@ add_task(function() {
"feed-button should be a child of wrapper-feed-button");
is(feedWrapper.getAttribute("place"), "palette",
"The feed-button wrapper should have it's place set to 'palette'");
simulateItemDrag(developerButton, palette);
is(developerButton.parentNode.tagName, "toolbarpaletteitem",
"The developer-button should be wrapped by a toolbarpaletteitem");
simulateItemDrag(openFileButton, palette);
is(openFileButton.parentNode.tagName, "toolbarpaletteitem",
"The open-file-button should be wrapped by a toolbarpaletteitem");
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
@ -217,7 +224,8 @@ add_task(function() {
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
"add-ons-button",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
simulateItemDrag(editControls, zoomControls);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
@ -240,7 +248,8 @@ add_task(function() {
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
"add-ons-button",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
simulateItemDrag(editControls, newWindowButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
@ -266,7 +275,8 @@ add_task(function() {
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
"add-ons-button",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
simulateItemDrag(editControls, privateBrowsingButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
@ -292,7 +302,8 @@ add_task(function() {
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
"add-ons-button",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
simulateItemDrag(editControls, savePageButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
@ -317,7 +328,8 @@ add_task(function() {
"find-button",
"preferences-button",
"add-ons-button",
"edit-controls"];
"edit-controls",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
simulateItemDrag(editControls, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
@ -341,7 +353,8 @@ add_task(function() {
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button"];
"add-ons-button",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
let paletteChildElementCount = palette.childElementCount;
simulateItemDrag(editControls, palette);
@ -365,7 +378,7 @@ add_task(function() {
yield startCustomizing();
let editControls = document.getElementById("edit-controls");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let numPlaceholders = isInWin8() ? 2 : 3;
let numPlaceholders = isInWin8() ? 1 : 2;
for (let i = 0; i < numPlaceholders; i++) {
// NB: We can't just iterate over all of the placeholders
// because each drag-drop action recreates them.
@ -380,7 +393,8 @@ add_task(function() {
"find-button",
"preferences-button",
"add-ons-button",
"edit-controls"];
"edit-controls",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
simulateItemDrag(editControls, placeholder);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
@ -390,15 +404,15 @@ add_task(function() {
}
});
// Dragging the developer-button back on to itself should work.
// Dragging the open-file-button back on to itself should work.
add_task(function() {
yield startCustomizing();
let developerButton = document.getElementById("developer-button");
is(developerButton.parentNode.tagName, "toolbarpaletteitem",
"developer-button should be wrapped by a toolbarpaletteitem");
simulateItemDrag(developerButton, developerButton);
is(developerButton.parentNode.tagName, "toolbarpaletteitem",
"developer-button should be wrapped by a toolbarpaletteitem");
let openFileButton = document.getElementById("open-file-button");
is(openFileButton.parentNode.tagName, "toolbarpaletteitem",
"open-file-button should be wrapped by a toolbarpaletteitem");
simulateItemDrag(openFileButton, openFileButton);
is(openFileButton.parentNode.tagName, "toolbarpaletteitem",
"open-file-button should be wrapped by a toolbarpaletteitem");
let editControls = document.getElementById("edit-controls");
is(editControls.parentNode.tagName, "toolbarpaletteitem",
"edit-controls should be wrapped by a toolbarpaletteitem");
@ -421,16 +435,19 @@ add_task(function() {
"find-button",
"preferences-button",
"add-ons-button",
"edit-controls"];
"edit-controls",
"developer-button"];
addSwitchToMetroButtonInWindows8(placementsAfterMove);
simulateItemDrag(editControls, target);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
let itemToDrag = "sync-button";
let button = document.getElementById(itemToDrag);
if (!isInWin8()) {
placementsAfterMove.push(itemToDrag);
} else {
placementsAfterMove.splice(11, 0, itemToDrag);
placementsAfterMove.splice(11, 0, itemToDrag);
if (isInWin8()) {
placementsAfterMove[10] = placementsAfterMove[11];
placementsAfterMove[11] = placementsAfterMove[12];
placementsAfterMove[12] = placementsAfterMove[13];
placementsAfterMove[13] = "edit-controls";
}
simulateItemDrag(button, editControls);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);

Просмотреть файл

@ -9,13 +9,13 @@ Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
// One orphaned item should have two placeholders next to it.
add_task(function() {
yield startCustomizing();
let btn = document.getElementById("developer-button");
let btn = document.getElementById("open-file-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
if (!isInWin8()) {
placements = placements.concat(["developer-button"]);
simulateItemDrag(btn, panel);
if (isInWin8()) {
CustomizableUI.removeWidgetFromArea("switch-to-metro-button");
placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
} else {
ok(CustomizableUI.inDefaultState, "Should be in default state.");
@ -28,9 +28,8 @@ add_task(function() {
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders after re-entering");
if (!isInWin8()) {
let palette = document.getElementById("customization-palette");
simulateItemDrag(btn, palette);
if (isInWin8()) {
CustomizableUI.addWidgetToArea("switch-to-metro-button", CustomizableUI.AREA_PANEL);
}
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
@ -38,32 +37,30 @@ add_task(function() {
// Two orphaned items should have one placeholder next to them (case 1).
add_task(function() {
yield startCustomizing();
let btn = document.getElementById("developer-button");
let btn = document.getElementById("open-file-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let placementsAfterAppend = placements.concat(["developer-button"]);
simulateItemDrag(btn, panel);
let placementsAfterAppend = placements;
if (!isInWin8()) {
placementsAfterAppend = placementsAfterAppend.concat(["sync-button"]);
btn = document.getElementById("sync-button");
placementsAfterAppend = placements.concat(["open-file-button"]);
simulateItemDrag(btn, panel);
}
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders before exiting");
is(CustomizableUI.inDefaultState, isInWin8(), "Should only be in default state if on Win8");
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder before exiting");
yield endCustomizing();
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders after re-entering");
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder after re-entering");
let palette = document.getElementById("customization-palette");
simulateItemDrag(btn, palette);
if (!isInWin8()) {
btn = document.getElementById("developer-button");
btn = document.getElementById("open-file-button");
simulateItemDrag(btn, palette);
}
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
@ -73,31 +70,34 @@ add_task(function() {
add_task(function() {
yield startCustomizing();
let btn = document.getElementById("add-ons-button");
let btn2 = document.getElementById("switch-to-metro-button");
let btn2 = document.getElementById("developer-button");
let btn3 = document.getElementById("switch-to-metro-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let palette = document.getElementById("customization-palette");
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let placementsAfterAppend = placements.filter(p => p != btn.id);
let placementsAfterAppend = placements.filter(p => p != btn.id && p != btn2.id);
simulateItemDrag(btn, palette);
simulateItemDrag(btn2, palette);
if (isInWin8()) {
placementsAfterAppend = placementsAfterAppend.filter(p => p != btn2.id);
simulateItemDrag(btn2, palette);
placementsAfterAppend = placementsAfterAppend.filter(p => p != btn3.id);
simulateItemDrag(btn3, palette);
}
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders before exiting");
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder before exiting");
yield endCustomizing();
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders after re-entering");
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder after re-entering");
simulateItemDrag(btn, panel);
simulateItemDrag(btn2, panel);
if (isInWin8()) {
simulateItemDrag(btn2, panel);
simulateItemDrag(btn3, panel);
}
assertAreaPlacements(CustomizableUI.AREA_PANEL, placements);
@ -108,11 +108,14 @@ add_task(function() {
add_task(function() {
yield startCustomizing();
let btn = document.getElementById("edit-controls");
let developerButton = document.getElementById("developer-button");
let metroBtn = document.getElementById("switch-to-metro-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let palette = document.getElementById("customization-palette");
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
placements.pop();
simulateItemDrag(developerButton, palette);
if (isInWin8()) {
// Remove switch-to-metro-button
placements.pop();
@ -129,6 +132,7 @@ add_task(function() {
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders after re-entering");
simulateItemDrag(developerButton, panel);
if (isInWin8()) {
simulateItemDrag(metroBtn, panel);
}
@ -137,10 +141,10 @@ add_task(function() {
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
// The default placements should have three placeholders at the bottom (or 2 in win8).
// The default placements should have two placeholders at the bottom (or 1 in win8).
add_task(function() {
yield startCustomizing();
let numPlaceholders = isInWin8() ? 2 : 3;
let numPlaceholders = isInWin8() ? 1 : 2;
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
ok(CustomizableUI.inDefaultState, "Should be in default state.");
is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders before exiting");

Просмотреть файл

@ -3,4 +3,5 @@ support-files = head.js
[browser_basic_functionality.js]
[browser_first_download_panel.js]
skip-if = os == "linux" # Bug 949434
[browser_overflow_anchor.js]

Просмотреть файл

@ -244,20 +244,34 @@ let ScrollPositionListener = {
* currently applied style to the chrome process.
*
* Causes a SessionStore:update message to be sent that contains the currently
* selected pageStyle, if any. The pageStyle is represented by a string.
* selected pageStyle for all reachable frames.
*
* Example:
* {pageStyle: "Dusk", children: [null, {pageStyle: "Mozilla"}]}
*/
let PageStyleListener = {
init: function () {
Services.obs.addObserver(this, "author-style-disabled-changed", true);
Services.obs.addObserver(this, "style-sheet-applicable-state-changed", true);
gFrameTree.addObserver(this);
},
observe: function (subject, topic) {
if (subject.defaultView && subject.defaultView.top == content) {
MessageQueue.push("pageStyle", () => PageStyle.collect(docShell) || null);
let frame = subject.defaultView;
if (frame && gFrameTree.contains(frame)) {
MessageQueue.push("pageStyle", () => this.collect());
}
},
collect: function () {
return PageStyle.collect(docShell, gFrameTree);
},
onFrameTreeReset: function () {
MessageQueue.push("pageStyle", () => null);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference])
};

Просмотреть файл

@ -12,13 +12,17 @@ const Ci = Components.interfaces;
* The external API exported by this module.
*/
this.PageStyle = Object.freeze({
collect: function (docShell) {
return PageStyleInternal.collect(docShell);
collect: function (docShell, frameTree) {
return PageStyleInternal.collect(docShell, frameTree);
},
restore: function (docShell, frameList, pageStyle) {
PageStyleInternal.restore(docShell, frameList, pageStyle);
},
restoreTree: function (docShell, data) {
PageStyleInternal.restoreTree(docShell, data);
}
});
// Signifies that author style level is disabled for the page.
@ -26,47 +30,29 @@ const NO_STYLE = "_nostyle";
let PageStyleInternal = {
/**
* Find out the title of the style sheet selected for the given
* docshell. Recurse into frames if needed.
* Collects the selected style sheet sets for all reachable frames.
*/
collect: function (docShell) {
collect: function (docShell, frameTree) {
let result = frameTree.map(({document: doc}) => {
let style;
if (doc) {
// http://dev.w3.org/csswg/cssom/#persisting-the-selected-css-style-sheet-set
style = doc.selectedStyleSheetSet || doc.lastStyleSheetSet;
}
return style ? {pageStyle: style} : null;
});
let markupDocumentViewer =
docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
if (markupDocumentViewer.authorStyleDisabled) {
return NO_STYLE;
result = result || {};
result.disabled = true;
}
let content = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
return this.collectFrame(content);
},
/**
* Determine the title of the currently enabled style sheet (if any);
* recurse through the frameset if necessary.
* @param content is a frame reference
* @returns the title style sheet determined to be enabled (empty string if none)
*/
collectFrame: function (content) {
const forScreen = /(?:^|,)\s*(?:all|screen)\s*(?:,|$)/i;
let sheets = content.document.styleSheets;
for (let i = 0; i < sheets.length; i++) {
let ss = sheets[i];
let media = ss.media.mediaText;
if (!ss.disabled && ss.title && (!media || forScreen.test(media))) {
return ss.title;
}
}
for (let i = 0; i < content.frames.length; i++) {
let selectedPageStyle = this.collectFrame(content.frames[i]);
if (selectedPageStyle) {
return selectedPageStyle;
}
}
return "";
return result && Object.keys(result).length ? result : null;
},
/**
@ -91,4 +77,51 @@ let PageStyleInternal = {
});
}
},
/**
* Restores pageStyle data for the current frame hierarchy starting at the
* |docShell's| current DOMWindow using the given pageStyle |data|.
*
* Warning: If the current frame hierarchy doesn't match that of the given
* |data| object we will silently discard data for unreachable frames. We may
* as well assign page styles to the wrong frames if some were reordered or
* removed.
*
* @param docShell (nsIDocShell)
* @param data (object)
* {
* disabled: true, // when true, author styles will be disabled
* pageStyle: "Dusk",
* children: [
* null,
* {pageStyle: "Mozilla", children: [ ... ]}
* ]
* }
*/
restoreTree: function (docShell, data) {
let disabled = data.disabled || false;
let markupDocumentViewer =
docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer);
markupDocumentViewer.authorStyleDisabled = disabled;
function restoreFrame(root, data) {
if (data.hasOwnProperty("pageStyle")) {
root.document.selectedStyleSheetSet = data.pageStyle;
}
if (!data.hasOwnProperty("children")) {
return;
}
let frames = root.frames;
data.children.forEach((child, index) => {
if (child && index < frames.length) {
restoreFrame(frames[index], child);
}
});
}
let ifreq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
restoreFrame(ifreq.getInterface(Ci.nsIDOMWindow), data);
}
};

Просмотреть файл

@ -2976,11 +2976,17 @@ let SessionStoreInternal = {
}
let frameList = this.getFramesToRestore(aBrowser);
let pageStyle = RestoreData.get(aBrowser, "pageStyle") || "";
let pageStyle = RestoreData.get(aBrowser, "pageStyle") || {};
let scrollPositions = RestoreData.get(aBrowser, "scroll") || {};
PageStyle.restore(aBrowser.docShell, frameList, pageStyle);
ScrollPosition.restoreTree(aBrowser.contentWindow, scrollPositions);
// Support the old pageStyle format.
if (typeof(pageStyle) === "string") {
PageStyle.restore(aBrowser.docShell, frameList, pageStyle);
} else {
ScrollPosition.restoreTree(aBrowser.contentWindow, scrollPositions);
}
PageStyle.restoreTree(aBrowser.docShell, pageStyle);
TextAndScrollData.restore(frameList);
let tab = aBrowser.__SS_restore_tab;

Просмотреть файл

@ -61,7 +61,8 @@ add_task(function nested_page_style() {
gBrowser.removeTab(tab);
let [{state: {pageStyle}}] = JSON.parse(ss.getClosedTabData(window));
is(pageStyle, "alternate", "correct pageStyle persisted");
let expected = JSON.stringify({children: [{pageStyle: "alternate"}]});
is(JSON.stringify(pageStyle), expected, "correct pageStyle persisted");
});
function getStyleSheets(browser) {

Просмотреть файл

@ -11,9 +11,6 @@
<link href="404.css" title="media_ALL" rel="alternate stylesheet" media=" ALL ">
<link href="404.css" title="media_screen" rel="alternate stylesheet" media="screen">
<link href="404.css" title="media_print_screen" rel="alternate stylesheet" media="print,screen">
<link href="404.css" title="fail_media_print" rel="alternate stylesheet" media="print">
<link href="404.css" title="fail_media_projection" rel="stylesheet" media="projection">
<link href="404.css" title="fail_media_invalid" rel="alternate stylesheet" media="hallo">
</head>
<body></body>
</html>

Просмотреть файл

@ -537,7 +537,7 @@ let DebuggerView = {
animated: true,
delayed: true,
callback: aCallback
});
}, 0);
},
/**

Просмотреть файл

@ -80,6 +80,7 @@ support-files =
[browser_dbg_break-on-dom-05.js]
[browser_dbg_break-on-dom-06.js]
[browser_dbg_break-on-dom-07.js]
[browser_dbg_break-on-dom-08.js]
[browser_dbg_breakpoints-actual-location.js]
[browser_dbg_breakpoints-button-01.js]
[browser_dbg_breakpoints-button-02.js]

Просмотреть файл

@ -0,0 +1,57 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that breaking on an event selects the variables view tab.
*/
const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
let gDebugger = aPanel.panelWin;
let gView = gDebugger.DebuggerView;
let gEvents = gView.EventListeners;
Task.spawn(function() {
yield waitForSourceShown(aPanel, ".html");
aDebuggee.addBodyClickEventListener();
let fetched = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
yield fetched;
yield ensureThreadClientState(aPanel, "resumed");
is(gView.instrumentsPaneHidden, false,
"The instruments pane should be visible.");
is(gView.instrumentsPaneTab, "events-tab",
"The events tab should be selected.");
let updated = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
yield updated;
yield ensureThreadClientState(aPanel, "resumed");
let paused = waitForCaretAndScopes(aPanel, 48);
// Spin the event loop before causing the debuggee to pause, to allow
// this function to yield first.
executeSoon(() => {
EventUtils.sendMouseEvent({ type: "click" }, aDebuggee.document.body, aDebuggee);
});
yield paused;
yield ensureThreadClientState(aPanel, "paused");
is(gView.instrumentsPaneHidden, false,
"The instruments pane should be visible.");
is(gView.instrumentsPaneTab, "variables-tab",
"The variables tab should be selected.");
yield resumeDebuggerThenCloseAndFinish(aPanel);
});
function getItemCheckboxNode(index) {
return gEvents.items[index].target.parentNode
.querySelector(".side-menu-widget-item-checkbox");
}
});
}

Просмотреть файл

@ -43,6 +43,10 @@
window.changeHandler = changeHandler;
});
function addBodyClickEventListener() {
document.body.addEventListener("click", function() { debugger; });
}
</script>
</body>

Просмотреть файл

@ -8,8 +8,6 @@ const { ViewHelpers } = Cu.import("resource:///modules/devtools/ViewHelpers.jsm"
* Note: this widget should be used in tandem with the WidgetMethods in
* ViewHelpers.jsm.
*
* Note: this widget also reuses SideMenuWidget CSS class names.
*
* @param nsIDOMNode aNode
* The element associated with the widget.
*/
@ -21,14 +19,12 @@ const FastListWidget = module.exports = function FastListWidget(aNode) {
// This is a prototype element that each item added to the list clones.
this._templateElement = this.document.createElement("hbox");
this._templateElement.className = "side-menu-widget-item side-menu-widget-item-contents";
// Create an internal scrollbox container.
this._list = this.document.createElement("scrollbox");
this._list.className = "side-menu-widget-container";
this._list.className = "fast-list-widget-container theme-body";
this._list.setAttribute("flex", "1");
this._list.setAttribute("orient", "vertical");
this._list.setAttribute("theme", "dark");
this._list.setAttribute("tabindex", "0");
this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false);

Просмотреть файл

@ -907,7 +907,6 @@ VariablesView.prototype = {
label.className = "variables-view-empty-notice";
label.setAttribute("value", this._emptyTextValue);
this._parent.setAttribute("empty", "");
this._parent.appendChild(label);
this._emptyTextNode = label;
},
@ -920,7 +919,6 @@ VariablesView.prototype = {
return;
}
this._parent.removeAttribute("empty");
this._parent.removeChild(this._emptyTextNode);
this._emptyTextNode = null;
},

Просмотреть файл

@ -16,7 +16,10 @@ Cu.import("resource:///modules/devtools/StyleEditorUI.jsm");
Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
loader.lazyGetter(this, "StyleSheetsFront",
() => require("devtools/server/actors/styleeditor").StyleSheetsFront);
() => require("devtools/server/actors/stylesheets").StyleSheetsFront);
loader.lazyGetter(this, "StyleEditorFront",
() => require("devtools/server/actors/styleeditor").StyleEditorFront);
this.StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
EventEmitter.decorate(this);
@ -54,14 +57,20 @@ StyleEditorPanel.prototype = {
targetPromise.then(() => {
this.target.on("close", this.destroy);
this._debuggee = StyleSheetsFront(this.target.client, this.target.form);
if (this.target.form.styleSheetsActor) {
this._debuggee = StyleSheetsFront(this.target.client, this.target.form);
}
else {
/* We're talking to a pre-Firefox 29 server-side */
this._debuggee = StyleEditorFront(this.target.client, this.target.form);
}
this.UI = new StyleEditorUI(this._debuggee, this.target, this._panelDoc);
this.UI.on("error", this._showError);
this.isReady = true;
deferred.resolve(this);
})
}, console.error);
return deferred.promise;
},

Просмотреть файл

@ -183,6 +183,9 @@
@BINPATH@/components/content_canvas.xpt
@BINPATH@/components/content_htmldoc.xpt
@BINPATH@/components/content_html.xpt
#ifdef MOZ_WEBRTC
@BINPATH@/components/content_webrtc.xpt
#endif
@BINPATH@/components/content_xslt.xpt
@BINPATH@/components/cookie.xpt
@BINPATH@/components/directory.xpt

Просмотреть файл

@ -8,63 +8,63 @@
<!ENTITY index.help "Help">
<!ENTITY device.screenshot "Screenshot">
<!ENTITY device.screenshotTooltip "Open a screenshot of the current state of the device in a new tab.">
<!ENTITY device.screenshotTooltip "Open a screenshot of the current state of the device in a new tab">
<!ENTITY device.title "Device Control Center">
<!ENTITY device.notConnected "Not connected. Please connect your device below.">
<!ENTITY device.startApp "Start">
<!ENTITY device.startAppTooltip "Start this app on the device.">
<!ENTITY device.startAppTooltip "Start this app on the device">
<!ENTITY device.stopApp "Stop">
<!ENTITY device.stopAppTooltip "Stop this app on the device.">
<!ENTITY device.stopAppTooltip "Stop this app on the device">
<!ENTITY device.debugApp "Debug">
<!ENTITY device.debugAppTooltip "Open the Developer Tools connected to this app on the device.">
<!ENTITY device.debugAppTooltip "Open the Developer Tools connected to this app on the device">
<!ENTITY device.name "Name">
<!ENTITY device.plain "Plain (default)">
<!ENTITY device.privileged "Privileged">
<!ENTITY device.certified "Certified">
<!ENTITY device.allow "Allow">
<!ENTITY device.allowTooltip "This permission is allowed for apps of this type.">
<!ENTITY device.allowTooltip "This permission is allowed for apps of this type">
<!ENTITY device.prompt "Prompt">
<!ENTITY device.promptTooltip "This permission requires a user prompt for apps of this type.">
<!ENTITY device.promptTooltip "This permission requires a user prompt for apps of this type">
<!ENTITY device.deny "Deny">
<!ENTITY device.denyTooltip "This permission is denied for apps of this type.">
<!ENTITY device.denyTooltip "This permission is denied for apps of this type">
<!ENTITY device.installedApps "Installed Apps">
<!ENTITY device.installedAppsTooltip "View a list of apps installed on the device. Some apps, such as certified apps, may be excluded from this view.">
<!ENTITY device.permissions "Permissions">
<!ENTITY device.permissionsTooltip "View a table of the permissions accessible to the different types of apps.">
<!ENTITY device.permissionsTooltip "View a table of the permissions accessible to the different types of apps">
<!ENTITY device.permissionsHelpLink "https://developer.mozilla.org/docs/Web/Apps/App_permissions">
<!ENTITY device.help "Help">
<!ENTITY connection.connectTooltip "Connect to the device.">
<!ENTITY connection.connectTooltip "Connect to the device">
<!ENTITY connection.disconnect "Disconnect">
<!ENTITY connection.disconnectTooltip "Disconnect from the current device or simulator.">
<!ENTITY connection.disconnectTooltip "Disconnect from the current device or simulator">
<!ENTITY connection.notConnected2 "Not Connected.">
<!ENTITY connection.connectTo "Connect to:">
<!ENTITY connection.noDeviceFound "No device found. Plug a device">
<!ENTITY connection.changeHostAndPort "Change">
<!ENTITY connection.changeHostAndPortTooltip "Change the host and port used to connect to the device. (Defaults to localhost:6000)">
<!ENTITY connection.changeHostAndPortTooltip "Change the host and port used to connect to the device (defaults to localhost:6000)">
<!ENTITY connection.startSimulator "Start Simulator">
<!ENTITY connection.startSimulatorTooltip "Start an instance of the Simulator and connect to it.">
<!ENTITY connection.startSimulatorTooltip "Start an instance of the Simulator and connect to it">
<!ENTITY connection.saveConnectionInfo "Save">
<!ENTITY connection.saveConnectionInfoTooltip "Save the host and port.">
<!ENTITY connection.saveConnectionInfoTooltip "Save the host and port">
<!ENTITY connection.connecting "Connecting…">
<!ENTITY connection.disconnecting "Disconnecting…">
<!ENTITY connection.cancel "Cancel">
<!ENTITY connection.cancelConnectTooltip "Cancel the connection in progress.">
<!ENTITY connection.cancelShowSimulatorTooltip "Exit the Simulator connection mode and return to the initial prompt.">
<!ENTITY connection.cancelConnectTooltip "Cancel the connection in progress">
<!ENTITY connection.cancelShowSimulatorTooltip "Exit the Simulator connection mode and return to the initial prompt">
<!ENTITY connection.or "or">
<!ENTITY connection.noSimulatorInstalled "No simulator installed.">
<!ENTITY connection.installOneSimulator "Install Simulator">
<!ENTITY connection.installOneSimulatorTooltip "Install a version of the Simulator by downloading the relevant add-on.">
<!ENTITY connection.installOneSimulatorTooltip "Install a version of the Simulator by downloading the relevant add-on">
<!ENTITY connection.installAnotherSimulator "Add">
<!ENTITY connection.installAnotherSimulatorTooltip "Install an additional version of the Simulator by downloading the relevant add-on.">
<!ENTITY connection.installAnotherSimulatorTooltip "Install an additional version of the Simulator by downloading the relevant add-on">
<!ENTITY connection.startRegisteredSimulator "Start:">
<!ENTITY projects.localApps "Local Apps">
<!ENTITY projects.addApp "Add">
<!ENTITY projects.addPackaged "Add Packaged App">
<!ENTITY projects.addPackagedTooltip "Add a new packaged app (a directory) from your computer.">
<!ENTITY projects.addPackagedTooltip "Add a new packaged app (a directory) from your computer">
<!ENTITY projects.addHosted "Add Hosted App">
<!ENTITY projects.addHostedTooltip "Add a new hosted app (link to a manifest.webapp file) from a remote website.">
<!ENTITY projects.addHostedTooltip "Add a new hosted app (link to a manifest.webapp file) from a remote website">
<!ENTITY projects.title "Local Apps">
<!ENTITY projects.appDetails "App Details">
<!ENTITY projects.removeAppFromList "Remove this app from the list of apps you are working on. This will not remove it from a device or a simulator.">
@ -73,13 +73,13 @@
<!ENTITY projects.debugApp "Debug">
<!ENTITY projects.debugAppTooltip "Open Developer Tools connected to this app">
<!ENTITY projects.saveManifest "Save">
<!ENTITY projects.saveManifestTooltip "Save the contents of the Manifest Editor below.">
<!ENTITY projects.saveManifestTooltip "Save the contents of the Manifest Editor below">
<!ENTITY projects.hostedManifestPlaceHolder2 "http://example.com/app/manifest.webapp">
<!ENTITY projects.noProjects "No projects. Add a new packaged app below (local directory) or a hosted app (link to a manifest file).">
<!ENTITY projects.manifestEditor "Manifest Editor">
<!ENTITY projects.manifestEditorTooltip "Edit your app's manifest in the panel below. The Update button will save your changes and update the app.">
<!ENTITY projects.manifestViewer "Manifest Viewer">
<!ENTITY projects.manifestViewerTooltip "Examine your app's manifest in the panel below.">
<!ENTITY projects.manifestViewerTooltip "Examine your app's manifest in the panel below">
<!ENTITY projects.valid "Valid">
<!ENTITY projects.error "Error">
<!ENTITY projects.warning "Warning">

Просмотреть файл

@ -62,24 +62,64 @@ SessionStore.prototype = {
this._loadState = STATE_STOPPED;
try {
let shutdownWasUnclean = false;
if (this._sessionFileBackup.exists()) {
this._shouldRestore = true;
this._sessionFileBackup.remove(false);
shutdownWasUnclean = true;
}
if (this._sessionFile.exists()) {
// Disable crash recovery if we have exceeded the timeout
this._lastSessionTime = this._sessionFile.lastModifiedTime;
let delta = Date.now() - this._lastSessionTime;
let timeout = Services.prefs.getIntPref("browser.sessionstore.resume_from_crash_timeout");
if (delta > (timeout * 60000))
this._shouldRestore = false;
this._sessionFile.copyTo(null, this._sessionFileBackup.leafName);
switch(Services.metro.previousExecutionState) {
// 0 == NotRunning
case 0:
// Disable crash recovery if we have exceeded the timeout
this._lastSessionTime = this._sessionFile.lastModifiedTime;
let delta = Date.now() - this._lastSessionTime;
let timeout =
Services.prefs.getIntPref(
"browser.sessionstore.resume_from_crash_timeout");
this._shouldRestore = shutdownWasUnclean
&& (delta < (timeout * 60000));
break;
// 1 == Running
case 1:
// We should never encounter this situation
Components.utils.reportError("SessionRestore.init called with "
+ "previous execution state 'Running'");
this._shouldRestore = true;
break;
// 2 == Suspended
case 2:
// We should never encounter this situation
Components.utils.reportError("SessionRestore.init called with "
+ "previous execution state 'Suspended'");
this._shouldRestore = true;
break;
// 3 == Terminated
case 3:
// Terminated means that Windows terminated our already-suspended
// process to get back some resources. When we re-launch, we want
// to provide the illusion that our process was suspended the
// whole time, and never terminated.
this._shouldRestore = true;
break;
// 4 == ClosedByUser
case 4:
// ClosedByUser indicates that the user performed a "close" gesture
// on our tile. We should act as if the browser closed normally,
// even if we were closed from a suspended state (in which case
// we'll have determined that it was an unclean shtudown)
this._shouldRestore = false;
break;
}
}
if (!this._sessionCache.exists() || !this._sessionCache.isDirectory())
if (!this._sessionCache.exists() || !this._sessionCache.isDirectory()) {
this._sessionCache.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
}
} catch (ex) {
Cu.reportError(ex); // file was write-locked?
}

Просмотреть файл

@ -184,7 +184,17 @@ this.BrowserUITelemetry = {
}
},
_firstWindowMeasurements: null,
_registerWindow: function(aWindow) {
// We'll gather measurements on the first non-popup window that opens
// after it has painted. We do this here instead of waiting for
// UITelemetry to ask for our measurements because at that point
// all browser windows have probably been closed, since the vast
// majority of saved-session pings are gathered during shutdown.
if (!this._firstWindowMeasurements && aWindow.toolbar.visible) {
this._firstWindowMeasurements = this._getWindowMeasurements(aWindow);
}
aWindow.addEventListener("unload", this);
let document = aWindow.document;
@ -311,20 +321,8 @@ this.BrowserUITelemetry = {
}
},
getToolbarMeasures: function() {
// Grab the most recent non-popup, non-private browser window for us to
// analyze the toolbars in...
let win = RecentWindow.getMostRecentBrowserWindow({
private: false,
allowPopups: false
});
// If there are no such windows, we're out of luck. :(
if (!win) {
return {};
}
let document = win.document;
_getWindowMeasurements: function(aWindow) {
let document = aWindow.document;
let result = {};
// Determine if the Bookmarks bar is currently visible
@ -364,7 +362,7 @@ this.BrowserUITelemetry = {
// Now go through the items in the palette to see what default
// items are in there.
let paletteItems =
CustomizableUI.getUnusedWidgets(win.gNavToolbox.palette);
CustomizableUI.getUnusedWidgets(aWindow.gNavToolbox.palette);
let defaultRemoved = [item.id for (item of paletteItems)
if (DEFAULT_ITEMS.indexOf(item.id) != -1)];
@ -373,8 +371,12 @@ this.BrowserUITelemetry = {
result.nondefaultAdded = nondefaultAdded;
result.defaultRemoved = defaultRemoved;
result.countableEvents = this._countableEvents;
return result;
},
getToolbarMeasures: function() {
let result = this._firstWindowMeasurements || {};
result.countableEvents = this._countableEvents;
return result;
},
};

Просмотреть файл

@ -128,62 +128,14 @@ function prompt(aWindowID, aCallID, aAudioRequested, aVideoRequested, aDevices)
let message = stringBundle.getFormattedString("getUserMedia.share" + requestType + ".message",
[ host ]);
function listDevices(menupopup, devices) {
while (menupopup.lastChild)
menupopup.removeChild(menupopup.lastChild);
let deviceIndex = 0;
for (let device of devices) {
addDeviceToList(menupopup, device.name, deviceIndex);
deviceIndex++;
}
}
function addDeviceToList(menupopup, deviceName, deviceIndex) {
let menuitem = chromeDoc.createElement("menuitem");
menuitem.setAttribute("value", deviceIndex);
menuitem.setAttribute("label", deviceName);
menuitem.setAttribute("tooltiptext", deviceName);
menupopup.appendChild(menuitem);
}
chromeDoc.getElementById("webRTC-selectCamera").hidden = !videoDevices.length;
chromeDoc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length;
let camMenupopup = chromeDoc.getElementById("webRTC-selectCamera-menupopup");
let micMenupopup = chromeDoc.getElementById("webRTC-selectMicrophone-menupopup");
listDevices(camMenupopup, videoDevices);
listDevices(micMenupopup, audioDevices);
if (requestType == "CameraAndMicrophone") {
addDeviceToList(camMenupopup, stringBundle.getString("getUserMedia.noVideo.label"), "-1");
addDeviceToList(micMenupopup, stringBundle.getString("getUserMedia.noAudio.label"), "-1");
}
let mainAction = {
label: PluralForm.get(requestType == "CameraAndMicrophone" ? 2 : 1,
stringBundle.getString("getUserMedia.shareSelectedDevices.label")),
accessKey: stringBundle.getString("getUserMedia.shareSelectedDevices.accesskey"),
callback: function () {
let allowedDevices = Cc["@mozilla.org/supports-array;1"]
.createInstance(Ci.nsISupportsArray);
if (videoDevices.length) {
let videoDeviceIndex = chromeDoc.getElementById("webRTC-selectCamera-menulist").value;
if (videoDeviceIndex != "-1")
allowedDevices.AppendElement(videoDevices[videoDeviceIndex]);
}
if (audioDevices.length) {
let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value;
if (audioDeviceIndex != "-1")
allowedDevices.AppendElement(audioDevices[audioDeviceIndex]);
}
if (allowedDevices.Count() == 0) {
denyRequest(aCallID);
return;
}
Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID);
}
// The real callback will be set during the "showing" event. The
// empty function here is so that PopupNotifications.show doesn't
// reject the action.
callback: function() {}
};
let secondaryActions = [{
@ -194,7 +146,72 @@ function prompt(aWindowID, aCallID, aAudioRequested, aVideoRequested, aDevices)
}
}];
let options = null;
let options = {
eventCallback: function(aTopic, aNewBrowser) {
if (aTopic == "swapping")
return true;
if (aTopic != "showing")
return false;
let chromeDoc = this.browser.ownerDocument;
function listDevices(menupopup, devices) {
while (menupopup.lastChild)
menupopup.removeChild(menupopup.lastChild);
let deviceIndex = 0;
for (let device of devices) {
addDeviceToList(menupopup, device.name, deviceIndex);
deviceIndex++;
}
}
function addDeviceToList(menupopup, deviceName, deviceIndex) {
let menuitem = chromeDoc.createElement("menuitem");
menuitem.setAttribute("value", deviceIndex);
menuitem.setAttribute("label", deviceName);
menuitem.setAttribute("tooltiptext", deviceName);
menupopup.appendChild(menuitem);
}
chromeDoc.getElementById("webRTC-selectCamera").hidden = !videoDevices.length;
chromeDoc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length;
let camMenupopup = chromeDoc.getElementById("webRTC-selectCamera-menupopup");
let micMenupopup = chromeDoc.getElementById("webRTC-selectMicrophone-menupopup");
listDevices(camMenupopup, videoDevices);
listDevices(micMenupopup, audioDevices);
if (requestType == "CameraAndMicrophone") {
let stringBundle = chromeDoc.defaultView.gNavigatorBundle;
addDeviceToList(camMenupopup, stringBundle.getString("getUserMedia.noVideo.label"), "-1");
addDeviceToList(micMenupopup, stringBundle.getString("getUserMedia.noAudio.label"), "-1");
}
this.mainAction.callback = function() {
let allowedDevices = Cc["@mozilla.org/supports-array;1"]
.createInstance(Ci.nsISupportsArray);
if (videoDevices.length) {
let videoDeviceIndex = chromeDoc.getElementById("webRTC-selectCamera-menulist").value;
if (videoDeviceIndex != "-1")
allowedDevices.AppendElement(videoDevices[videoDeviceIndex]);
}
if (audioDevices.length) {
let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value;
if (audioDeviceIndex != "-1")
allowedDevices.AppendElement(audioDevices[audioDeviceIndex]);
}
if (allowedDevices.Count() == 0) {
denyRequest(aCallID);
return;
}
Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID);
};
return true;
}
};
chromeWin.PopupNotifications.show(browser, "webRTC-shareDevices", message,
"webRTC-shareDevices-notification-icon", mainAction,
@ -237,7 +254,8 @@ function showBrowserSpecificIndicator(aBrowser) {
let mainAction = null;
let secondaryActions = null;
let options = {
dismissed: true
dismissed: true,
eventCallback: function(aTopic) aTopic == "swapping"
};
chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingDevices", message,
"webRTC-sharingDevices-notification-icon", mainAction,

Просмотреть файл

@ -5,16 +5,12 @@
/* Sources and breakpoints pane */
#sources-pane {
min-width: 50px;
}
#sources-pane > tabs {
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#sources-and-editor-splitter {
-moz-border-start-color: transparent;
#sources-pane[selectedIndex="0"] + #sources-and-editor-splitter {
border-color: transparent;
}
/* Sources toolbar */
@ -72,7 +68,8 @@
#black-boxed-message,
#source-progress-container {
background: url(background-noise-toolbar.png) rgb(61,69,76);
/* Prevent the container deck from aquiring the height from this message. */
/* Prevent the container deck from aquiring the size from this message. */
min-width: 1px;
min-height: 1px;
color: white;
}
@ -110,11 +107,8 @@
}
#clear-tracer {
min-width: 22px !important;
}
#tracer-search {
min-width: 72px !important;
/* Make this button as narrow as the text inside it. */
min-width: 1px;
}
#tracer-message {
@ -126,23 +120,28 @@
-moz-padding-start: 4px !important;
}
#tracer-traces > scrollbox {
overflow: scroll;
/* Hack to enable hardware acceleration */
transform: translateZ(0);
}
/* Tracer dark theme */
.theme-dark #tracer-message {
color: #f5f7fa; /* Light foreground text */
background: url(background-noise-toolbar.png) #181d20; /* Content background sidebar */
}
.theme-dark #tracer-traces > scrollbox {
background-color: #181d20 !important; /* Content background sidebar */
}
.theme-dark .trace-item {
color: #f5f7fa; /* Light foreground text */
}
.trace-item.selected-matching {
background-color: #1d4f73; /* Select highlight blue */
.theme-dark .trace-item.selected-matching {
background-color: rgba(29, 79, 115, .4); /* Select highlight blue at 40% alpha */
}
.theme-dark .selected > .trace-item {
background-color: rgba(29, 79, 115, .75); /* Select highlight blue at 75% alpha */
}
.theme-dark .trace-call {
@ -159,30 +158,29 @@
}
.theme-dark .trace-param {
color: #8fa1b2; /* Content text grey */
color: #b8c8d9; /* Content text light */
}
.theme-dark .trace-syntax {
color: #5e88b0; /* highlight blue-grey */
color: #8fa1b2; /* Content text grey */
}
/* Tracer light theme */
.theme-light #tracer-message {
color: #292e33; /* Dark foreground text */
background: url(background-noise-toolbar.png) #f7f7f7; /* Content background sidebar */
}
.theme-light #tracer-traces > scrollbox {
background-color: #f7f7f7 !important; /* Content background sidebar */
}
.theme-light .trace-item {
color: #292e33; /* Dark foreground text */
}
.trace-item.selected-matching {
background-color: #4c9ed9; /* Select highlight blue */
.theme-light .trace-item.selected-matching {
background-color: rgba(76, 158, 217, .4); /* Select highlight blue at 40% alpha */
}
.theme-light .selected > .trace-item {
background-color: rgba(76, 158, 217, .75); /* Select highlight blue at 75% alpha */
}
.theme-light .trace-call {
@ -199,11 +197,11 @@
}
.theme-light .trace-param {
color: #8fa1b2; /* Content text grey */
color: #667380; /* Content text dark grey */
}
.theme-light .trace-syntax {
color: #5f88b0; /* highlight blue-grey */
color: #8fa1b2; /* Content text grey */
}
/* ListWidget items */
@ -485,7 +483,7 @@
}
#globalsearch + .devtools-horizontal-splitter {
-moz-border-top-colors: #bfbfbf;
border-color: #bfbfbf;
}
.dbg-source-results {

Просмотреть файл

@ -6,7 +6,6 @@
/* Generic pane helpers */
.generic-toggled-side-pane {
min-width: 50px;
-moz-margin-start: 0px !important;
/* Unfortunately, transitions don't work properly with locale-aware properties,
so both the left and right margins are set via js, while the start margin
@ -271,7 +270,7 @@
.side-menu-widget-container {
/* Hack: force hardware acceleration */
transform: translateX(0px);
transform: translateZ(1px);
}
.side-menu-widget-container[theme="dark"] {
@ -437,9 +436,9 @@
/* VariablesView */
.variables-view-container:not([empty]) {
.variables-view-container {
/* Hack: force hardware acceleration */
transform: translateX(1px);
transform: translateZ(1px);
}
.variables-view-empty-notice {

Просмотреть файл

@ -7,16 +7,12 @@
/* Sources and breakpoints pane */
#sources-pane {
min-width: 50px;
}
#sources-pane > tabs {
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#sources-and-editor-splitter {
-moz-border-start-color: transparent;
#sources-pane[selectedIndex="0"] + #sources-and-editor-splitter {
border-color: transparent;
}
/* Sources toolbar */
@ -74,7 +70,8 @@
#black-boxed-message,
#source-progress-container {
background: url(background-noise-toolbar.png) rgb(61,69,76);
/* Prevent the container deck from aquiring the height from this message. */
/* Prevent the container deck from aquiring the size from this message. */
min-width: 1px;
min-height: 1px;
color: white;
}
@ -112,11 +109,8 @@
}
#clear-tracer {
min-width: 22px !important;
}
#tracer-search {
min-width: 72px !important;
/* Make this button as narrow as the text inside it. */
min-width: 1px;
}
#tracer-message {
@ -128,23 +122,28 @@
-moz-padding-start: 4px !important;
}
#tracer-traces > scrollbox {
overflow: scroll;
/* Hack to enable hardware acceleration */
transform: translateZ(0);
}
/* Tracer dark theme */
.theme-dark #tracer-message {
color: #f5f7fa; /* Light foreground text */
background: url(background-noise-toolbar.png) #181d20; /* Content background sidebar */
}
.theme-dark #tracer-traces > scrollbox {
background-color: #181d20 !important; /* Content background sidebar */
}
.theme-dark .trace-item {
color: #f5f7fa; /* Light foreground text */
}
.trace-item.selected-matching {
background-color: #1d4f73; /* Select highlight blue */
.theme-dark .trace-item.selected-matching {
background-color: rgba(29, 79, 115, .4); /* Select highlight blue at 40% alpha */
}
.theme-dark .selected > .trace-item {
background-color: rgba(29, 79, 115, .75); /* Select highlight blue at 75% alpha */
}
.theme-dark .trace-call {
@ -161,30 +160,29 @@
}
.theme-dark .trace-param {
color: #8fa1b2; /* Content text grey */
color: #b8c8d9; /* Content text light */
}
.theme-dark .trace-syntax {
color: #5e88b0; /* highlight blue-grey */
color: #8fa1b2; /* Content text grey */
}
/* Tracer light theme */
.theme-light #tracer-message {
color: #292e33; /* Dark foreground text */
background: url(background-noise-toolbar.png) #f7f7f7; /* Content background sidebar */
}
.theme-light #tracer-traces > scrollbox {
background-color: #f7f7f7 !important; /* Content background sidebar */
}
.theme-light .trace-item {
color: #292e33; /* Dark foreground text */
}
.trace-item.selected-matching {
background-color: #4c9ed9; /* Select highlight blue */
.theme-light .trace-item.selected-matching {
background-color: rgba(76, 158, 217, .4); /* Select highlight blue at 40% alpha */
}
.theme-light .selected > .trace-item {
background-color: rgba(76, 158, 217, .75); /* Select highlight blue at 75% alpha */
}
.theme-light .trace-call {
@ -201,11 +199,11 @@
}
.theme-light .trace-param {
color: #8fa1b2; /* Content text grey */
color: #667380; /* Content text dark grey */
}
.theme-light .trace-syntax {
color: #5f88b0; /* highlight blue-grey */
color: #8fa1b2; /* Content text grey */
}
/* ListWidget items */
@ -487,7 +485,7 @@
}
#globalsearch + .devtools-horizontal-splitter {
-moz-border-top-colors: #bfbfbf;
border-color: #bfbfbf;
}
.dbg-source-results {

Просмотреть файл

@ -6,7 +6,6 @@
/* Generic pane helpers */
.generic-toggled-side-pane {
min-width: 50px;
-moz-margin-start: 0px !important;
/* Unfortunately, transitions don't work properly with locale-aware properties,
so both the left and right margins are set via js, while the start margin
@ -271,7 +270,7 @@
.side-menu-widget-container {
/* Hack: force hardware acceleration */
transform: translateX(0px);
transform: translateZ(1px);
}
.side-menu-widget-container[theme="dark"] {
@ -431,9 +430,9 @@
/* VariablesView */
.variables-view-container:not([empty]) {
.variables-view-container {
/* Hack: force hardware acceleration */
transform: translateX(1px);
transform: translateZ(1px);
}
.variables-view-empty-notice {

Просмотреть файл

@ -26,23 +26,23 @@
-moz-appearance: none;
background-image: none;
background-color: transparent;
border: 1px solid black;
border-width: 1px 0 0 0;
border: 0;
border-bottom: 1px solid black;
min-height: 3px;
height: 3px;
margin-bottom: -3px;
margin-top: -3px;
position: relative;
}
.devtools-side-splitter {
-moz-appearance: none;
background-image: none;
border: 0;
-moz-border-start: 1px solid black;
min-width: 0;
width: 3px;
background-color: transparent;
-moz-margin-end: -3px;
border: 0;
-moz-border-end: 1px solid black;
min-width: 3px;
width: 3px;
-moz-margin-start: -3px;
position: relative;
cursor: e-resize;
}

Просмотреть файл

@ -13,7 +13,7 @@
}
.devtools-toolbar {
min-height: 33px;
min-height: 33px;
}
.profiler-sidebar {
@ -21,7 +21,7 @@
}
.profiler-sidebar + .devtools-side-splitter {
-moz-border-start-color: transparent;
border-color: transparent;
}
.profiler-sidebar-item {
@ -86,4 +86,4 @@
#profiler-start[checked] {
-moz-image-region: rect(0px,32px,16px,16px);
}
}

Просмотреть файл

@ -46,7 +46,7 @@
}
#shaders-pane + .devtools-side-splitter {
-moz-border-start-color: transparent;
border-color: transparent;
}
.side-menu-widget-item-checkbox {
@ -88,7 +88,7 @@
/* Shader source editors */
#editors-splitter {
-moz-border-start-color: rgb(61,69,76);
border-color: rgb(61,69,76);
}
.editor-label {

Просмотреть файл

@ -5,16 +5,12 @@
/* Sources and breakpoints pane */
#sources-pane {
min-width: 50px;
}
#sources-pane > tabs {
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#sources-and-editor-splitter {
-moz-border-start-color: transparent;
#sources-pane[selectedIndex="0"] + #sources-and-editor-splitter {
border-color: transparent;
}
/* Sources toolbar */
@ -72,7 +68,8 @@
#black-boxed-message,
#source-progress-container {
background: url(background-noise-toolbar.png) rgb(61,69,76);
/* Prevent the container deck from aquiring the height from this message. */
/* Prevent the container deck from aquiring the size from this message. */
min-width: 1px;
min-height: 1px;
color: white;
}
@ -110,11 +107,8 @@
}
#clear-tracer {
min-width: 22px !important;
}
#tracer-search {
min-width: 72px !important;
/* Make this button as narrow as the text inside it. */
min-width: 1px;
}
#tracer-message {
@ -126,23 +120,28 @@
-moz-padding-start: 4px !important;
}
#tracer-traces > scrollbox {
overflow: scroll;
/* Hack to enable hardware acceleration */
transform: translateZ(0);
}
/* Tracer dark theme */
.theme-dark #tracer-message {
color: #f5f7fa; /* Light foreground text */
background: url(background-noise-toolbar.png) #181d20; /* Content background sidebar */
}
.theme-dark #tracer-traces > scrollbox {
background-color: #181d20 !important; /* Content background sidebar */
}
.theme-dark .trace-item {
color: #f5f7fa; /* Light foreground text */
}
.trace-item.selected-matching {
background-color: #1d4f73; /* Select highlight blue */
.theme-dark .trace-item.selected-matching {
background-color: rgba(29, 79, 115, .4); /* Select highlight blue at 40% alpha */
}
.theme-dark .selected > .trace-item {
background-color: rgba(29, 79, 115, .75); /* Select highlight blue at 75% alpha */
}
.theme-dark .trace-call {
@ -159,30 +158,29 @@
}
.theme-dark .trace-param {
color: #8fa1b2; /* Content text grey */
color: #b8c8d9; /* Content text light */
}
.theme-dark .trace-syntax {
color: #5e88b0; /* highlight blue-grey */
color: #8fa1b2; /* Content text grey */
}
/* Tracer light theme */
.theme-light #tracer-message {
color: #292e33; /* Dark foreground text */
background: url(background-noise-toolbar.png) #f7f7f7; /* Content background sidebar */
}
.theme-light #tracer-traces > scrollbox {
background-color: #f7f7f7 !important; /* Content background sidebar */
}
.theme-light .trace-item {
color: #292e33; /* Dark foreground text */
}
.trace-item.selected-matching {
background-color: #4c9ed9; /* Select highlight blue */
.theme-light .trace-item.selected-matching {
background-color: rgba(76, 158, 217, .4); /* Select highlight blue at 40% alpha */
}
.theme-light .selected > .trace-item {
background-color: rgba(76, 158, 217, .75); /* Select highlight blue at 75% alpha */
}
.theme-light .trace-call {
@ -199,11 +197,11 @@
}
.theme-light .trace-param {
color: #8fa1b2; /* Content text grey */
color: #667380; /* Content text dark grey */
}
.theme-light .trace-syntax {
color: #5f88b0; /* highlight blue-grey */
color: #8fa1b2; /* Content text grey */
}
/* ListWidget items */
@ -485,7 +483,7 @@
}
#globalsearch + .devtools-horizontal-splitter {
-moz-border-top-colors: #bfbfbf;
border-color: #bfbfbf;
}
.dbg-source-results {

Просмотреть файл

@ -6,7 +6,6 @@
/* Generic pane helpers */
.generic-toggled-side-pane {
min-width: 50px;
-moz-margin-start: 0px !important;
/* Unfortunately, transitions don't work properly with locale-aware properties,
so both the left and right margins are set via js, while the start margin
@ -275,7 +274,7 @@
.side-menu-widget-container {
/* Hack: force hardware acceleration */
transform: translateX(0px);
transform: translateZ(1px);
}
.side-menu-widget-container[theme="dark"] {
@ -434,9 +433,9 @@
/* VariablesView */
.variables-view-container:not([empty]) {
.variables-view-container {
/* Hack: force hardware acceleration */
transform: translateX(1px);
transform: translateZ(1px);
}
.variables-view-empty-notice {

Просмотреть файл

@ -559,8 +559,8 @@ browser.jar:
skin/classic/aero/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/aero/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
skin/classic/aero/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
skin/classic/aero/devtools/tracer-icon.png (devtools/tracer-icon.png)
skin/classic/aero/devtools/tracer-icon@2x.png (devtools/tracer-icon@2x.png)
skin/classic/aero/browser/devtools/tracer-icon.png (devtools/tracer-icon.png)
skin/classic/aero/browser/devtools/tracer-icon@2x.png (devtools/tracer-icon@2x.png)
skin/classic/aero/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
skin/classic/aero/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/aero/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)

Просмотреть файл

@ -62,6 +62,7 @@ SEARCH_PATHS = [
# Individual files providing mach commands.
MACH_MODULES = [
'addon-sdk/mach_commands.py',
'build/valgrind/mach_commands.py',
'dom/bindings/mach_commands.py',
'layout/tools/reftest/mach_commands.py',
'python/mach_commands.py',

Просмотреть файл

@ -0,0 +1,76 @@
# 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/.
from __future__ import print_function, unicode_literals
import os
import subprocess
from mozbuild.base import (
MachCommandBase,
MachCommandConditions as conditions,
)
from mach.decorators import (
CommandProvider,
Command,
)
@CommandProvider
class MachCommands(MachCommandBase):
'''
Easily run Valgrind tests.
'''
def __init__(self, context):
MachCommandBase.__init__(self, context)
@Command('valgrind-test', category='testing',
conditions=[conditions.is_firefox],
description='Run the Valgrind test job.')
def valgrind_test(self):
defines = self.config_environment.defines
if 'MOZ_VALGRIND' not in defines or 'MOZ_MEMORY' in defines:
print("sorry, this command requires a build configured with\n"
"--enable-valgrind and --disable-jemalloc build")
return 1
debugger_args = [
'--error-exitcode=1',
'--smc-check=all-non-file',
'--vex-iropt-register-updates=allregs-at-each-insn',
'--gen-suppressions=all',
'--num-callers=20',
'--leak-check=full',
'--show-possibly-lost=no',
'--track-origins=yes'
]
build_dir = os.path.join(self.topsrcdir, 'build')
supps_dir = os.path.join(build_dir, 'valgrind')
debugger_args.append('--suppressions=' + os.path.join(supps_dir, 'cross-architecture.sup'))
# MACHTYPE is an odd bash-only environment variable that doesn't show
# up in os.environ, so we have to get it another way.
machtype = subprocess.check_output(['bash', '-c', 'echo $MACHTYPE']).rstrip()
arch_specific_supps_file = os.path.join(supps_dir, machtype + '.sup')
if os.path.isfile(arch_specific_supps_file):
debugger_args.append('--suppressions=' + os.path.join(supps_dir, arch_specific_supps_file))
print('Using platform-specific suppression file: ',
arch_specific_supps_file + '\n')
else:
print('Warning: could not find a platform-specific suppression file\n')
env = os.environ.copy()
env['G_SLICE'] = 'always-malloc'
env['XPCOM_CC_RUN_DURING_SHUTDOWN'] = '1'
script = os.path.join(build_dir, 'valgrind', 'valgrind_test.py')
return subprocess.call([self.virtualenv_manager.python_path, script,
'--debugger=valgrind',
'--debugger-args=' + ' '.join(debugger_args) + ''],
env=env)

Просмотреть файл

@ -0,0 +1,77 @@
#!/usr/bin/python
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from mozprofile import FirefoxProfile, Profile, Preferences
from mozprofile.permissions import ServerLocations
from mozrunner import FirefoxRunner, CLI
from mozhttpd import MozHttpd
import json
import socket
import threading
import os
import sys
import shutil
import tempfile
from datetime import datetime
from mozbuild.base import MozbuildObject
PORT = 8888
if __name__ == '__main__':
cli = CLI()
debug_args, interactive = cli.debugger_arguments()
build = MozbuildObject.from_environment()
# XXX: currently we just use the PGO inputs for Valgrind runs. This may
# change in the future.
httpd = MozHttpd(port=PORT,
docroot=os.path.join(build.topsrcdir, "build", "pgo"))
httpd.start(block=False)
locations = ServerLocations()
locations.add_host(host='127.0.0.1',
port=PORT,
options='primary,privileged')
#TODO: mozfile.TemporaryDirectory
profilePath = tempfile.mkdtemp()
try:
#TODO: refactor this into mozprofile
prefpath = os.path.join(build.topsrcdir, "testing", "profiles", "prefs_general.js")
prefs = {}
prefs.update(Preferences.read_prefs(prefpath))
interpolation = { "server": "%s:%d" % httpd.httpd.server_address,
"OOP": "false"}
prefs = json.loads(json.dumps(prefs) % interpolation)
for pref in prefs:
prefs[pref] = Preferences.cast(prefs[pref])
profile = FirefoxProfile(profile=profilePath,
preferences=prefs,
addons=[os.path.join(build.distdir, 'xpi-stage', 'quitter')],
locations=locations)
env = os.environ.copy()
env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
env["XPCOM_DEBUG_BREAK"] = "warn"
jarlog = os.getenv("JARLOG_FILE")
if jarlog:
env["MOZ_JAR_LOG_FILE"] = os.path.abspath(jarlog)
print "jarlog: %s" % env["MOZ_JAR_LOG_FILE"]
cmdargs = ["http://localhost:%d/index.html" % PORT]
runner = FirefoxRunner(profile=profile,
binary=build.get_binary_path(),
cmdargs=cmdargs,
env=env)
runner.start(debug_args=debug_args, interactive=interactive)
status = runner.wait()
httpd.stop()
if status != 0:
status = 1 # normalize status, in case it's larger than 127
sys.exit(status)
finally:
shutil.rmtree(profilePath)

Просмотреть файл

@ -5880,21 +5880,30 @@ RIL[REQUEST_SET_PREFERRED_NETWORK_TYPE] = function REQUEST_SET_PREFERRED_NETWORK
return;
}
options.success = (options.rilRequestError == ERROR_SUCCESS);
if (options.rilRequestError) {
options.success = false;
options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
} else {
options.success = true;
}
this.sendChromeMessage(options);
};
RIL[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK_TYPE(length, options) {
let networkType;
if (!options.rilRequestError) {
networkType = RIL_PREFERRED_NETWORK_TYPE_TO_GECKO.indexOf(GECKO_PREFERRED_NETWORK_TYPE_DEFAULT);
let responseLen = Buf.readInt32(); // Number of INT32 responsed.
if (responseLen) {
this.preferredNetworkType = networkType = Buf.readInt32();
}
options.networkType = networkType;
if (options.rilRequestError) {
options.success = false;
options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
this.sendChromeMessage(options);
return;
}
options.success = (options.rilRequestError == ERROR_SUCCESS);
let networkType = RIL_PREFERRED_NETWORK_TYPE_TO_GECKO.indexOf(GECKO_PREFERRED_NETWORK_TYPE_DEFAULT);
let responseLen = Buf.readInt32(); // Number of INT32 responsed.
if (responseLen) {
this.preferredNetworkType = networkType = Buf.readInt32();
}
options.networkType = networkType;
options.success = true;
this.sendChromeMessage(options);
};
RIL[REQUEST_GET_NEIGHBORING_CELL_IDS] = null;

Просмотреть файл

@ -93,6 +93,35 @@ this.WbxmlEnd = {
},
};
/**
* Escape XML reserved characters &, <, >, " and ' which may appear in the
* WBXML-encoded strings in their original form.
*
* @param str
* A string with potentially unescaped characters
*
* @return A string with the &, <, >, " and ' characters turned into XML
* character entitites
*
* @see WAP-192-WBXML-20010725-A, clause 6.1
*/
this.escapeReservedCharacters = function escape_reserved_characters(str) {
let dst = "";
for (var i = 0; i < str.length; i++) {
switch (str[i]) {
case '&' : dst += "&amp;" ; break;
case '<' : dst += "&lt;" ; break;
case '>' : dst += "&gt;" ; break;
case '"' : dst += "&quot;"; break;
case '\'': dst += "&apos;"; break;
default : dst += str[i];
}
}
return dst;
}
/**
* Handle string table in WBXML message.
*
@ -118,7 +147,9 @@ this.readStringTable = function decode_wbxml_read_string_table(start, stringTabl
this.WbxmlStringTable = {
decode: function decode_wbxml_string_table(data, decodeInfo) {
let start = WSP.Octet.decode(data);
return readStringTable(start, decodeInfo.stringTable, decodeInfo.charset);
let str = readStringTable(start, decodeInfo.stringTable, decodeInfo.charset);
return escapeReservedCharacters(str);
}
};
@ -142,7 +173,9 @@ this.WbxmlInlineString = {
charCode = WSP.Octet.decode(data);
}
return WSP.PduHelper.decodeStringContent(stringData, decodeInfo.charset);
let str = WSP.PduHelper.decodeStringContent(stringData, decodeInfo.charset);
return escapeReservedCharacters(str);
},
};

Просмотреть файл

@ -110,6 +110,33 @@ add_test(function test_si_parse_wbxml_with_href() {
run_next_test();
});
/**
* SI compressed by WBXML with href attribute containing reserved XML character
*/
add_test(function test_si_parse_wbxml_with_href_reserved_char() {
let msg = {};
let contentType = "";
let data = {};
contentType = "application/vnd.wap.sic";
data.array = new Uint8Array([
0x02, 0x05, 0x6A, 0x00, 0x45, 0xC6, 0x0D, 0x03,
0x6F, 0x72, 0x65, 0x69, 0x6C, 0x6C, 0x79, 0x00,
0x85, 0x03, 0x66, 0x6F, 0x6F, 0x26, 0x62, 0x61,
0x72, 0x00, 0x01, 0x03, 0x43, 0x68, 0x65, 0x63,
0x6B, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x77,
0x65, 0x62, 0x73, 0x69, 0x74, 0x65, 0x00, 0x01,
0x01
]);
data.offset = 0;
let result = "<si><indication href=\"http://www.oreilly.com/foo&amp;bar\">" +
"Check this website</indication></si>";
let msg = SI.PduHelper.parse(data, contentType);
do_check_eq(msg.content, result);
run_next_test();
});
/**
* SI compressed by WBXML with href and date attribute
*/
@ -170,4 +197,4 @@ add_test(function test_si_parse_wbxml_with_attr_string_table() {
do_check_eq(msg.content, result);
run_next_test();
});
});

Просмотреть файл

@ -421,7 +421,7 @@ mozJSComponentLoader::LoadModule(FileLocation &aFile)
JSAutoRequest ar(mContext);
RootedValue dummy(mContext);
rv = ObjectForLocation(file, uri, &entry->obj,
rv = ObjectForLocation(file, uri, &entry->obj, &entry->thisObjectKey,
&entry->location, false, &dummy);
if (NS_FAILED(rv)) {
return nullptr;
@ -742,6 +742,7 @@ nsresult
mozJSComponentLoader::ObjectForLocation(nsIFile *aComponentFile,
nsIURI *aURI,
JSObject **aObject,
JSScript **aTableScript,
char **aLocation,
bool aPropagateExceptions,
MutableHandleValue aException)
@ -991,12 +992,17 @@ mozJSComponentLoader::ObjectForLocation(nsIFile *aComponentFile,
// See bug 384168.
*aObject = obj;
JSScript* tableScript = script;
RootedScript tableScript(cx, script);
if (!tableScript) {
tableScript = JS_GetFunctionScript(cx, function);
MOZ_ASSERT(tableScript);
}
*aTableScript = tableScript;
// tableScript stays in the table until shutdown. To avoid it being
// collected and another script getting the same address, we root
// tableScript lower down in this function.
mThisObjects.Put(tableScript, obj);
bool ok = false;
@ -1018,6 +1024,8 @@ mozJSComponentLoader::ObjectForLocation(nsIFile *aComponentFile,
JS_ClearPendingException(cx);
}
*aObject = nullptr;
*aTableScript = nullptr;
mThisObjects.Remove(tableScript);
return NS_ERROR_FAILURE;
}
@ -1025,10 +1033,13 @@ mozJSComponentLoader::ObjectForLocation(nsIFile *aComponentFile,
*aLocation = ToNewCString(nativePath);
if (!*aLocation) {
*aObject = nullptr;
*aTableScript = nullptr;
mThisObjects.Remove(tableScript);
return NS_ERROR_OUT_OF_MEMORY;
}
JS_AddNamedObjectRoot(cx, aObject, *aLocation);
JS_AddNamedScriptRoot(cx, aTableScript, *aLocation);
return NS_OK;
}
@ -1217,6 +1228,7 @@ mozJSComponentLoader::ImportInto(const nsACString &aLocation,
RootedValue exception(callercx);
rv = ObjectForLocation(sourceLocalFile, resURI, &newEntry->obj,
&newEntry->thisObjectKey,
&newEntry->location, true, &exception);
mInProgressImports.Remove(key);

Просмотреть файл

@ -73,6 +73,7 @@ class mozJSComponentLoader : public mozilla::ModuleLoader,
nsresult ObjectForLocation(nsIFile* aComponentFile,
nsIURI *aComponent,
JSObject **aObject,
JSScript **aTableScript,
char **location,
bool aCatchException,
JS::MutableHandleValue aException);
@ -102,6 +103,7 @@ class mozJSComponentLoader : public mozilla::ModuleLoader,
unloadProc = nullptr;
obj = nullptr;
thisObjectKey = nullptr;
location = nullptr;
}
@ -119,12 +121,15 @@ class mozJSComponentLoader : public mozilla::ModuleLoader,
JS_SetAllNonReservedSlotsToUndefined(sSelf->mContext, obj);
JS_RemoveObjectRoot(sSelf->mContext, &obj);
if (thisObjectKey)
JS_RemoveScriptRoot(sSelf->mContext, &thisObjectKey);
}
if (location)
NS_Free(location);
obj = nullptr;
thisObjectKey = nullptr;
location = nullptr;
}
@ -135,6 +140,7 @@ class mozJSComponentLoader : public mozilla::ModuleLoader,
nsCOMPtr<xpcIJSGetFactory> getfactoryobj;
JSObject *obj;
JSScript *thisObjectKey;
char *location;
};

Просмотреть файл

@ -681,7 +681,7 @@ nsRefreshDriver::ChooseTimer() const
nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
: mActiveTimer(nullptr),
mPresContext(aPresContext),
mFrozen(false),
mFreezeCount(0),
mThrottled(false),
mTestControllingRefreshes(false),
mViewManagerFlushIsPending(false),
@ -826,7 +826,7 @@ nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer)
if (mActiveTimer && !aAdjustingTimer)
return;
if (mFrozen || !mPresContext) {
if (IsFrozen() || !mPresContext) {
// If we don't want to start it now, or we've been disconnected.
StopTimer();
return;
@ -999,7 +999,7 @@ NS_IMPL_ISUPPORTS1(nsRefreshDriver, nsISupports)
void
nsRefreshDriver::DoTick()
{
NS_PRECONDITION(!mFrozen, "Why are we notified while frozen?");
NS_PRECONDITION(!IsFrozen(), "Why are we notified while frozen?");
NS_PRECONDITION(mPresContext, "Why are we notified after disconnection?");
NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),
"Shouldn't have a JSContext on the stack");
@ -1037,7 +1037,7 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
// We're either frozen or we were disconnected (likely in the middle
// of a tick iteration). Just do nothing here, since our
// prescontext went away.
if (mFrozen || !mPresContext) {
if (IsFrozen() || !mPresContext) {
return;
}
@ -1293,23 +1293,28 @@ nsRefreshDriver::StartTableRefresh(const uint32_t& aDelay,
void
nsRefreshDriver::Freeze()
{
NS_ASSERTION(!mFrozen, "Freeze called on already-frozen refresh driver");
StopTimer();
mFrozen = true;
mFreezeCount++;
}
void
nsRefreshDriver::Thaw()
{
NS_ASSERTION(mFrozen, "Thaw called on an unfrozen refresh driver");
mFrozen = false;
if (ObserverCount() || ImageRequestCount()) {
// FIXME: This isn't quite right, since our EnsureTimerStarted call
// updates our mMostRecentRefresh, but the DoRefresh call won't run
// and notify our observers until we get back to the event loop.
// Thus MostRecentRefresh() will lie between now and the DoRefresh.
NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &nsRefreshDriver::DoRefresh));
EnsureTimerStarted(false);
NS_ASSERTION(mFreezeCount > 0, "Thaw() called on an unfrozen refresh driver");
if (mFreezeCount > 0) {
mFreezeCount--;
}
if (mFreezeCount == 0) {
if (ObserverCount() || ImageRequestCount()) {
// FIXME: This isn't quite right, since our EnsureTimerStarted call
// updates our mMostRecentRefresh, but the DoRefresh call won't run
// and notify our observers until we get back to the event loop.
// Thus MostRecentRefresh() will lie between now and the DoRefresh.
NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &nsRefreshDriver::DoRefresh));
EnsureTimerStarted(false);
}
}
}
@ -1330,7 +1335,7 @@ void
nsRefreshDriver::DoRefresh()
{
// Don't do a refresh unless we're in a state where we should be refreshing.
if (!mFrozen && mPresContext && mActiveTimer) {
if (!IsFrozen() && mPresContext && mActiveTimer) {
DoTick();
}
}

Просмотреть файл

@ -213,15 +213,20 @@ public:
mPresContext = nullptr;
}
bool IsFrozen() { return mFreezeCount > 0; }
/**
* Freeze the refresh driver. It should stop delivering future
* refreshes until thawed.
* refreshes until thawed. Note that the number of calls to Freeze() must
* match the number of calls to Thaw() in order for the refresh driver to
* be un-frozen.
*/
void Freeze();
/**
* Thaw the refresh driver. If needed, it should start delivering
* refreshes again.
* Thaw the refresh driver. If the number of calls to Freeze() matches the
* number of calls to this function, the refresh driver should start
* delivering refreshes again.
*/
void Thaw();
@ -299,7 +304,7 @@ private:
nsPresContext *mPresContext; // weak; pres context passed in constructor
// and unset in Disconnect
bool mFrozen;
uint32_t mFreezeCount;
bool mThrottled;
bool mTestControllingRefreshes;
bool mViewManagerFlushIsPending;

Просмотреть файл

@ -2252,6 +2252,19 @@ ScrollFrameHelper::ExpandRect(const nsRect& aRect) const
return rect;
}
static bool IsFocused(nsIContent* aContent)
{
// Some content elements, like the GetContent() of a scroll frame
// for a text input field, are inside anonymous subtrees, but the focus
// manager always reports a non-anonymous element as the focused one, so
// walk up the tree until we reach a non-anonymous element.
while (aContent && aContent->IsInAnonymousSubtree()) {
aContent = aContent->GetParent();
}
return aContent ? nsContentUtils::IsFocusedContent(aContent) : false;
}
void
ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
@ -2394,10 +2407,16 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder,
} else {
nsRect scrollRange = GetScrollRange();
ScrollbarStyles styles = GetScrollbarStylesFromFrame();
bool hasScrollableOverflow =
(scrollRange.width > 0 || scrollRange.height > 0) &&
((styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN && mHScrollbarBox) ||
(styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN && mVScrollbarBox));
bool isFocused = IsFocused(mOuter->GetContent());
bool isVScrollable = (scrollRange.height > 0)
&& (styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN);
bool isHScrollable = (scrollRange.width > 0)
&& (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN);
// The check for scroll bars was added in bug 825692 to prevent layerization
// of text inputs for performance reasons. However, if a text input is
// focused we want to layerize it so we can async scroll it (bug 946408).
bool wantLayerV = isVScrollable && (mVScrollbarBox || isFocused);
bool wantLayerH = isHScrollable && (mHScrollbarBox || isFocused);
// TODO Turn this on for inprocess OMTC on all platforms
bool wantSubAPZC = (XRE_GetProcessType() == GeckoProcessType_Content);
#ifdef XP_WIN
@ -2407,7 +2426,7 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder,
#endif
shouldBuildLayer =
wantSubAPZC &&
hasScrollableOverflow &&
(wantLayerV || wantLayerH) &&
(!mIsRoot || !mOuter->PresContext()->IsRootContentDocument());
}

Просмотреть файл

@ -584,11 +584,7 @@ class TransportConduitTest : public ::testing::Test
err = mVideoSession->ConfigureSendMediaCodec(&cinst1);
ASSERT_EQ(mozilla::kMediaConduitNoError, err);
err = mVideoSession->ConfigureRecvMediaCodecs(rcvCodecList);
ASSERT_EQ(mozilla::kMediaConduitNoError, err);
err = mVideoSession2->ConfigureSendMediaCodec(&cinst1);
ASSERT_EQ(mozilla::kMediaConduitNoError, err);
err = mVideoSession2->ConfigureRecvMediaCodecs(rcvCodecList);
ASSERT_EQ(mozilla::kMediaConduitNoError, err);
@ -613,8 +609,9 @@ class TransportConduitTest : public ::testing::Test
cerr << " Done With The Testing " << endl;
cerr << " **************************************************" << endl;
ASSERT_EQ(0, vidStatsGlobal.numFramesRenderedWrongly);
ASSERT_EQ(vidStatsGlobal.numRawFramesInserted,
vidStatsGlobal.numFramesRenderedSuccessfully);
}
void TestVideoConduitCodecAPI()

Просмотреть файл

@ -274,6 +274,10 @@
android:authorities="@ANDROID_PACKAGE_NAME@.db.tabs"
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
<provider android:name="org.mozilla.gecko.db.HomeListsProvider"
android:authorities="@ANDROID_PACKAGE_NAME@.db.homelists"
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
<service
android:exported="false"
android:name="org.mozilla.gecko.updater.UpdateService"

Просмотреть файл

@ -24,6 +24,9 @@ public class BrowserContract {
public static final String TABS_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.tabs";
public static final Uri TABS_AUTHORITY_URI = Uri.parse("content://" + TABS_AUTHORITY);
public static final String HOME_LISTS_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.homelists";
public static final Uri HOME_LISTS_AUTHORITY_URI = Uri.parse("content://" + HOME_LISTS_AUTHORITY);
public static final String PARAM_PROFILE = "profile";
public static final String PARAM_PROFILE_PATH = "profilePath";
public static final String PARAM_LIMIT = "limit";
@ -284,4 +287,17 @@ public class BrowserContract {
// timestamp provided by Sync during insertion.
public static final String LAST_MODIFIED = "last_modified";
}
// Data storage for custom lists on about:home
@RobocopTarget
public static final class HomeListItems implements CommonColumns, URLColumns {
private HomeListItems() {}
public static final Uri CONTENT_FAKE_URI = Uri.withAppendedPath(HOME_LISTS_AUTHORITY_URI, "items/fake");
public static final Uri CONTENT_URI = Uri.withAppendedPath(HOME_LISTS_AUTHORITY_URI, "items");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/homelistitem";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/homelistitem";
public static final String PROVIDER_ID = "provider_id";
}
}

Просмотреть файл

@ -0,0 +1,48 @@
/* 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/. */
package org.mozilla.gecko.db;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.db.BrowserContract.HomeListItems;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
final class HomeListsDatabaseHelper extends SQLiteOpenHelper {
static final int DATABASE_VERSION = 1;
static final String DATABASE_NAME = "homelists.db";
static final String TABLE_ITEMS = "items";
public HomeListsDatabaseHelper(Context context, String databasePath) {
super(context, databasePath, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_ITEMS + "(" +
HomeListItems._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
HomeListItems.PROVIDER_ID + " TEXT," +
HomeListItems.TITLE + " TEXT," +
HomeListItems.URL + " TEXT)"
);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Unimpelemented
}
@Override
public void onOpen(SQLiteDatabase db) {
// Unimplemented
}
}

Просмотреть файл

@ -0,0 +1,383 @@
/* 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/. */
package org.mozilla.gecko.db;
import org.mozilla.gecko.db.BrowserContract.HomeListItems;
import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
import org.mozilla.gecko.mozglue.RobocopTarget;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
public class HomeListsProvider extends ContentProvider {
private static final String LOGTAG = "GeckoHomeListsProvider";
// Flag to disable DB usage. This prevents us from creating (and later needing to upgrade) a DB.
private static final boolean DB_DISABLED = true;
private PerProfileDatabases<HomeListsDatabaseHelper> mDatabases;
// Endpoint to return static fake data.
static final int ITEMS_FAKE = 100;
static final int ITEMS = 101;
static final int ITEMS_ID = 102;
static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
static {
URI_MATCHER.addURI(BrowserContract.HOME_LISTS_AUTHORITY, "items/fake", ITEMS_FAKE);
URI_MATCHER.addURI(BrowserContract.HOME_LISTS_AUTHORITY, "items", ITEMS);
URI_MATCHER.addURI(BrowserContract.HOME_LISTS_AUTHORITY, "items/#", ITEMS_ID);
}
private static boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
private static boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
protected static void trace(String message) {
if (logVerbose) {
Log.v(LOGTAG, message);
}
}
protected static void debug(String message) {
if (logDebug) {
Log.d(LOGTAG, message);
}
}
private boolean isTest(Uri uri) {
String isTest = uri.getQueryParameter(BrowserContract.PARAM_IS_TEST);
return !TextUtils.isEmpty(isTest);
}
private SQLiteDatabase getReadableDatabase(Uri uri) {
if (DB_DISABLED) {
throw new UnsupportedOperationException("Database operations are disabled!");
}
String profile = null;
if (uri != null) {
profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
}
return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getReadableDatabase();
}
private SQLiteDatabase getWritableDatabase(Uri uri) {
if (DB_DISABLED) {
throw new UnsupportedOperationException("Database operations are disabled!");
}
String profile = null;
if (uri != null) {
profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
}
return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getWritableDatabase();
}
@Override
public boolean onCreate() {
if (DB_DISABLED) {
return true;
}
synchronized (this) {
mDatabases = new PerProfileDatabases<HomeListsDatabaseHelper>(getContext(), HomeListsDatabaseHelper.DATABASE_NAME,
new DatabaseHelperFactory<HomeListsDatabaseHelper>() {
@Override
public HomeListsDatabaseHelper makeDatabaseHelper(Context context, String databasePath) {
return new HomeListsDatabaseHelper(context, databasePath);
}
});
}
return true;
}
@Override
public String getType(Uri uri) {
trace("Getting URI type: " + uri);
final int match = URI_MATCHER.match(uri);
switch (match) {
case ITEMS_FAKE: {
trace("URI is ITEMS_FAKE: " + uri);
return HomeListItems.CONTENT_TYPE;
}
case ITEMS: {
trace("URI is ITEMS: " + uri);
return HomeListItems.CONTENT_TYPE;
}
case ITEMS_ID: {
trace("URI is ITEMS_ID: " + uri);
return HomeListItems.CONTENT_ITEM_TYPE;
}
}
debug("URI has unrecognized type: " + uri);
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
trace("Calling delete on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
int deleted = 0;
if (Build.VERSION.SDK_INT >= 11) {
trace("Beginning delete transaction: " + uri);
db.beginTransaction();
try {
deleted = deleteInTransaction(uri, selection, selectionArgs);
db.setTransactionSuccessful();
trace("Successful delete transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
deleted = deleteInTransaction(uri, selection, selectionArgs);
}
if (deleted > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return deleted;
}
public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
trace("Calling delete in transaction on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
int deleted = 0;
final int match = URI_MATCHER.match(uri);
switch (match) {
case ITEMS: {
trace("Delete on ITEMS: " + uri);
deleted = db.delete(HomeListsDatabaseHelper.TABLE_ITEMS, selection, selectionArgs);
break;
}
case ITEMS_ID: {
trace("Delete on ITEMS: " + uri);
// Not implemented
break;
}
default:
throw new UnsupportedOperationException("Unknown delete URI " + uri);
}
debug("Deleted " + deleted + " rows for URI: " + uri);
return deleted;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
trace("Calling insert on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
Uri result = null;
if (Build.VERSION.SDK_INT >= 11) {
trace("Beginning insert transaction: " + uri);
db.beginTransaction();
try {
result = insertInTransaction(uri, values);
db.setTransactionSuccessful();
trace("Successful insert transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
result = insertInTransaction(uri, values);
}
if (result != null) {
getContext().getContentResolver().notifyChange(uri, null);
}
return result;
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
if (values == null) {
return 0;
}
final SQLiteDatabase db = getWritableDatabase(uri);
final int numValues = values.length;
int successes = 0;
db.beginTransaction();
try {
for (int i = 0; i < numValues; i++) {
try {
insertInTransaction(uri, values[i]);
successes++;
} catch (SQLException e) {
Log.e(LOGTAG, "SQLException in bulkInsert", e);
// Restart the transaction to continue insertions.
db.setTransactionSuccessful();
db.endTransaction();
db.beginTransaction();
}
}
trace("Flushing DB bulkinsert...");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (successes > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return successes;
}
public Uri insertInTransaction(Uri uri, ContentValues values) {
trace("Calling insert in transaction on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
long id = -1;
final int match = URI_MATCHER.match(uri);
switch (match) {
case ITEMS: {
trace("Insert on ITEMS: " + uri);
id = db.insertOrThrow(HomeListsDatabaseHelper.TABLE_ITEMS, null, values);
break;
}
case ITEMS_ID: {
trace("Insert on ITEMS_ID: " + uri);
// Not implemented
break;
}
default:
throw new UnsupportedOperationException("Unknown insert URI " + uri);
}
if (id >= 0) {
debug("Inserted ID in database: " + id);
return ContentUris.withAppendedId(uri, id);
}
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
trace("Calling update on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
int updated = 0;
if (Build.VERSION.SDK_INT >= 11) {
trace("Beginning update transaction: " + uri);
db.beginTransaction();
try {
updated = updateInTransaction(uri, values, selection, selectionArgs);
db.setTransactionSuccessful();
trace("Successful update transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
updated = updateInTransaction(uri, values, selection, selectionArgs);
}
if (updated > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return updated;
}
public int updateInTransaction(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
trace("Calling update in transaction on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
int updated = 0;
final int match = URI_MATCHER.match(uri);
switch (match) {
case ITEMS: {
trace("Update on ITEMS: " + uri);
updated = db.update(HomeListsDatabaseHelper.TABLE_ITEMS, values, selection, selectionArgs);
break;
}
case ITEMS_ID: {
trace("Update on ITEMS_ID: " + uri);
// Not implemented
break;
}
default:
throw new UnsupportedOperationException("Unknown update URI " + uri);
}
debug("Updated " + updated + " rows for URI: " + uri);
return updated;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final int match = URI_MATCHER.match(uri);
// If we're querying the fake items, don't try to get the database.
if (match == ITEMS_FAKE) {
trace("Query on ITEMS_FAKE: " + uri);
return queryFakeItems(uri, projection, selection, selectionArgs, sortOrder);
}
final SQLiteDatabase db = getReadableDatabase(uri);
Cursor cursor = null;
switch (match) {
case ITEMS: {
trace("Query on ITEMS: " + uri);
cursor = db.query(HomeListsDatabaseHelper.TABLE_ITEMS, projection, selection, selectionArgs, null, null, sortOrder, null);
break;
}
case ITEMS_ID: {
trace("Query on ITEMS_ID: " + uri);
// Not implemented
break;
}
default:
throw new UnsupportedOperationException("Unknown query URI " + uri);
}
return cursor;
}
/**
* Returns a cursor populated with static fake data.
*/
private Cursor queryFakeItems(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final String[] itemsColumns = new String[] {
HomeListItems._ID,
HomeListItems.PROVIDER_ID,
HomeListItems.URL,
HomeListItems.TITLE
};
// XXX: Return more items (from JSON file?) and filter fake items by provider specified in selection
final MatrixCursor c = new MatrixCursor(itemsColumns);
c.addRow(new Object[] { 1, "fake-provider", "http://example.com", "Example" });
c.addRow(new Object[] { 2, "fake-provider", "http://mozilla.org", "Mozilla" });
return c;
}
}

Просмотреть файл

@ -200,4 +200,4 @@ final class HomeConfig {
public static HomeConfig getDefault(Context context) {
return new HomeConfig(new HomeConfigMemBackend(context));
}
}
}

Просмотреть файл

@ -10,9 +10,11 @@ import org.mozilla.gecko.home.HomeConfig.HomeConfigBackend;
import org.mozilla.gecko.home.HomeConfig.OnChangeListener;
import org.mozilla.gecko.home.HomeConfig.PageEntry;
import org.mozilla.gecko.home.HomeConfig.PageType;
import org.mozilla.gecko.home.ListManager.ListInfo;
import org.mozilla.gecko.util.HardwareUtils;
import android.content.Context;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collections;
@ -20,10 +22,14 @@ import java.util.EnumSet;
import java.util.List;
class HomeConfigMemBackend implements HomeConfigBackend {
private static final String LOGTAG = "GeckoHomeConfigMemBackend";
private final Context mContext;
private final ListManager mListManager;
public HomeConfigMemBackend(Context context) {
mContext = context;
mListManager = new ListManager(context);
}
public List<PageEntry> load() {
@ -54,6 +60,12 @@ class HomeConfigMemBackend implements HomeConfigBackend {
pageEntries.add(0, historyEntry);
}
// Add registered list pages.
List<ListInfo> listInfos = mListManager.getListInfos();
for (ListInfo info : listInfos) {
pageEntries.add(new PageEntry(PageType.LIST, info.title, info.id));
}
return Collections.unmodifiableList(pageEntries);
}
@ -64,4 +76,4 @@ class HomeConfigMemBackend implements HomeConfigBackend {
public void setOnChangeListener(OnChangeListener listener) {
// This is a static backend, do nothing.
}
}
}

Просмотреть файл

@ -0,0 +1,87 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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/. */
package org.mozilla.gecko.home;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.util.GeckoEventListener;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class ListManager implements GeckoEventListener {
private static final String LOGTAG = "GeckoListManager";
public class ListInfo {
public final String id;
public final String title;
public ListInfo(String id, String title) {
this.id = id;
this.title = title;
}
}
private final Context mContext;
public ListManager(Context context) {
mContext = context;
// Add a listener to handle any new lists that are added after the lists have been loaded.
GeckoAppShell.getEventDispatcher().registerEventListener("HomeLists:Added", this);
}
/**
* Reads list info from SharedPreferences. Don't call this on the main thread!
*
* @return List<ListInfo> A list of ListInfos for each registered list.
*/
public List<ListInfo> getListInfos() {
final ArrayList<ListInfo> listInfos = new ArrayList<ListInfo>();
// XXX: We need to use PreferenceManager right now because that's what SharedPreferences.jsm uses (see bug 940575)
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
final String prefValue = prefs.getString("home_lists", "");
if (!TextUtils.isEmpty(prefValue)) {
try {
final JSONArray lists = new JSONArray(prefValue);
for (int i = 0; i < lists.length(); i++) {
final JSONObject list = lists.getJSONObject(i);
final ListInfo info = new ListInfo(list.getString("id"), list.getString("title"));
listInfos.add(info);
}
} catch (JSONException e) {
Log.e(LOGTAG, "Exception getting list info", e);
}
}
return listInfos;
}
/**
* Listens for "HomeLists:Added"
*/
@Override
public void handleMessage(String event, JSONObject message) {
try {
final ListInfo info = new ListInfo(message.getString("id"), message.getString("title"));
// Do something to update the set of list pages.
} catch (JSONException e) {
Log.e(LOGTAG, "Exception handling " + event + " message", e);
}
}
}

Просмотреть файл

@ -6,17 +6,22 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.HomeListItems;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.HomeConfig.PageEntry;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Configuration;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -28,6 +33,8 @@ import java.util.EnumSet;
* Fragment that displays custom lists.
*/
public class ListPage extends HomeFragment {
private static final String LOGTAG = "GeckoListPage";
// Cursor loader ID for the lists
private static final int LOADER_ID_LIST = 0;
@ -132,14 +139,27 @@ public class ListPage extends HomeFragment {
* Cursor loader for the lists.
*/
private static class HomeListLoader extends SimpleCursorLoader {
public HomeListLoader(Context context) {
private String mProviderId;
public HomeListLoader(Context context, String providerId) {
super(context);
mProviderId = providerId;
}
@Override
public Cursor loadCursor() {
// Do nothing
return null;
final ContentResolver cr = getContext().getContentResolver();
// XXX: Use the test URI for static fake data
final Uri fakeItemsUri = HomeListItems.CONTENT_FAKE_URI.buildUpon().
appendQueryParameter(BrowserContract.PARAM_PROFILE, "default").build();
final String selection = HomeListItems.PROVIDER_ID + " = ?";
final String[] selectionArgs = new String[] { mProviderId };
Log.i(LOGTAG, "Loading fake data for list provider: " + mProviderId);
return cr.query(fakeItemsUri, null, selection, selectionArgs, null);
}
}
@ -169,7 +189,7 @@ public class ListPage extends HomeFragment {
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new HomeListLoader(getActivity());
return new HomeListLoader(getActivity(), mPageEntry.getId());
}
@Override

Просмотреть файл

@ -114,6 +114,8 @@ gbjar.sources += [
'db/BrowserProvider.java',
'db/DBUtils.java',
'db/FormHistoryProvider.java',
'db/HomeListsDatabaseHelper.java',
'db/HomeListsProvider.java',
'db/LocalBrowserDB.java',
'db/PasswordsProvider.java',
'db/PerProfileContentProvider.java',
@ -218,6 +220,7 @@ gbjar.sources += [
'home/HomePager.java',
'home/HomePagerTabStrip.java',
'home/LastTabsPage.java',
'home/ListManager.java',
'home/ListPage.java',
'home/MostRecentPage.java',
'home/MultiTypeCursorAdapter.java',

Просмотреть файл

@ -34,6 +34,7 @@ skip-if = processor == "x86"
[testInputUrlBar]
[testJarReader]
[testLinkContextMenu]
[testHomeListsProvider]
[testLoad]
[testMailToContextMenu]
[testMasterPassword]

Просмотреть файл

@ -0,0 +1,124 @@
package org.mozilla.gecko.tests;
import android.content.ContentValues;
import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
public class testHomeListsProvider extends ContentProviderTest {
private Uri mItemsFakeUri;
private Uri mItemsUri;
private String mItemsIdCol;
private String mItemsProviderIdCol;
private String mItemsTitleCol;
private String mItemsUrlCol;
@Override
protected int getTestType() {
return TEST_MOCHITEST;
}
private void loadContractInfo() throws Exception {
mItemsFakeUri = getUriColumn("HomeListItems", "CONTENT_FAKE_URI");
mItemsUri = getContentUri("HomeListItems");
mItemsIdCol = getStringColumn("HomeListItems", "_ID");
mItemsProviderIdCol = getStringColumn("HomeListItems", "PROVIDER_ID");
mItemsTitleCol = getStringColumn("HomeListItems", "TITLE");
mItemsUrlCol = getStringColumn("HomeListItems", "URL");
}
private void ensureEmptyDatabase() throws Exception {
// Delete all the list entries.
mProvider.delete(mItemsUri, null, null);
final Cursor c = mProvider.query(mItemsUri, null, null, null, null);
mAsserter.is(c.getCount(), 0, "All list entries were deleted");
c.close();
}
@Override
public void setUp() throws Exception {
super.setUp("org.mozilla.gecko.db.HomeListsProvider", "AUTHORITY", "homelists.db");
loadContractInfo();
mTests.add(new TestFakeItems());
// Disabled until database support lands
//mTests.add(new TestInsertItem());
}
public void testListsProvider() throws Exception {
for (int i = 0; i < mTests.size(); i++) {
Runnable test = mTests.get(i);
setTestName(test.getClass().getSimpleName());
// Disabled until database support lands
//ensureEmptyDatabase();
test.run();
}
}
abstract class Test implements Runnable {
@Override
public void run() {
try {
test();
} catch (Exception e) {
mAsserter.is(true, false, "Test " + this.getClass().getName() +
" threw exception: " + e);
}
}
public abstract void test() throws Exception;
}
class TestFakeItems extends Test {
@Override
public void test() throws Exception {
final long id = 1;
final String providerId = "fake-provider";
final String title = "Example";
final String url = "http://example.com";
final Cursor c = mProvider.query(mItemsFakeUri, null, null, null, null);
mAsserter.is(c.moveToFirst(), true, "Fake list item found");
mAsserter.is(c.getLong(c.getColumnIndex(mItemsIdCol)), id, "Fake list item has correct ID");
mAsserter.is(c.getString(c.getColumnIndex(mItemsProviderIdCol)), providerId, "Fake list item has correct provider ID");
mAsserter.is(c.getString(c.getColumnIndex(mItemsTitleCol)), title, "Fake list item has correct title");
mAsserter.is(c.getString(c.getColumnIndex(mItemsUrlCol)), url, "Fake list item has correct URL");
c.close();
}
}
class TestInsertItem extends Test {
@Override
public void test() throws Exception {
final String providerId = "{c77da387-4c80-0c45-9f22-70276c29b3ed}";
final String title = "Mozilla";
final String url = "https://mozilla.org";
// Insert a new list item with test values.
final ContentValues cv = new ContentValues();
cv.put(mItemsProviderIdCol, providerId);
cv.put(mItemsTitleCol, title);
cv.put(mItemsUrlCol, url);
final long id = ContentUris.parseId(mProvider.insert(mItemsUri, cv));
// Check that the item was inserted correctly.
final Cursor c = mProvider.query(mItemsUri, null, mItemsIdCol + " = ?", new String[] { String.valueOf(id) }, null);
mAsserter.is(c.moveToFirst(), true, "Inserted list item found");
mAsserter.is(c.getString(c.getColumnIndex(mItemsProviderIdCol)), providerId, "Inserted list item has correct provider ID");
mAsserter.is(c.getString(c.getColumnIndex(mItemsTitleCol)), title, "Inserted list item has correct title");
mAsserter.is(c.getString(c.getColumnIndex(mItemsUrlCol)), url, "Inserted list item has correct URL");
c.close();
}
}
}

Просмотреть файл

@ -346,6 +346,21 @@ var SelectionHandler = {
this._sendMessage("TextSelection:Update");
},
addAction: function(action) {
if (!action.id)
action.id = uuidgen.generateUUID().toString()
if (this.actions[action.id])
throw "Action with id " + action.id + " already added";
this.actions[action.id] = action;
return action.id;
},
removeAction: function(id) {
delete this.actions[id];
},
actions: {
SELECT_ALL: {
label: Strings.browser.GetStringFromName("contextmenu.selectAll"),

Просмотреть файл

@ -6638,15 +6638,15 @@ var SearchEngines = {
(form.enctype != "text/plain") && (form.enctype != "multipart/form-data");
}
};
SelectionHandler.actions.SEARCH_ADD = {
id: "add_search_action",
SelectionHandler.addAction({
id: "search_add_action",
label: Strings.browser.GetStringFromName("contextmenu.addSearchEngine"),
icon: "drawable://ic_url_bar_search",
selector: filter,
action: function(aElement) {
SearchEngines.addEngine(aElement);
}
}
});
},
uninit: function uninit() {

Просмотреть файл

@ -10,6 +10,7 @@ this.EXPORTED_SYMBOLS = ["Home"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/SharedPreferences.jsm");
// See bug 915424
function resolveGeckoURI(aURI) {
@ -136,7 +137,73 @@ let HomeBanner = {
}
};
function List(options) {
if ("id" in options)
this.id = options.id;
if ("title" in options)
this.title = options.title;
}
function HomeLists() {
this.PREF_KEY = "home_lists";
this._sharedPrefs = new SharedPreferences();
this._lists = {};
let prefValue = this._sharedPrefs.getCharPref(this.PREF_KEY);
if (!prefValue) {
return;
}
JSON.parse(prefValue).forEach(data => {
let list = new List(data);
this._lists[list.id] = list;
});
}
HomeLists.prototype = {
add: function(options) {
let list = new List(options);
if (!list.id || !list.title) {
throw "Can't create a home list without an id and title!";
}
// Bail if the list already exists
if (list.id in this._lists) {
throw "List already exists: " + list.id;
}
this._lists[list.id] = list;
this._updateSharedPref();
// Send a message to Java to update the home pager if it's currently showing
sendMessageToJava({
type: "HomeLists:Added",
id: list.id,
title: list.title
});
},
remove: function(id) {
delete this._lists[id];
this._updateSharedPref();
},
// Set a shared pref so that Java can know about this list before Gecko is running
_updateSharedPref: function() {
let lists = [];
for (let id in this._lists) {
let list = this._lists[id];
lists.push({ id: list.id, title: list.title});
}
this._sharedPrefs.setCharPref(this.PREF_KEY, JSON.stringify(lists));
}
};
// Public API
this.Home = {
banner: HomeBanner
banner: HomeBanner,
lists: new HomeLists()
}

Просмотреть файл

@ -3901,6 +3901,8 @@ pref("signon.rememberSignons", true);
pref("signon.autofillForms", true);
pref("signon.autologin.proxy", false);
pref("signon.debug", false);
// Override autocomplete=false for password manager
pref("signon.overrideAutocomplete", false);
// Satchel (Form Manager) prefs
pref("browser.formfill.debug", false);

Просмотреть файл

@ -51,7 +51,11 @@ this.Tracker = function Tracker(name, engine) {
this.ignoreAll = false;
this.changedIDs = {};
this.loadChangedIDs();
}
Svc.Obs.add("weave:engine:start-tracking", this);
Svc.Obs.add("weave:engine:stop-tracking", this);
};
Tracker.prototype = {
/*
* Score can be called as often as desired to decide which engines to sync
@ -73,7 +77,7 @@ Tracker.prototype = {
},
// Should be called by service everytime a sync has been done for an engine
resetScore: function T_resetScore() {
resetScore: function () {
this._score = 0;
},
@ -88,7 +92,7 @@ Tracker.prototype = {
this._log.debug("Not saving changedIDs.");
return;
}
Utils.namedTimer(function() {
Utils.namedTimer(function () {
this._log.debug("Saving changed IDs to " + this.file);
Utils.jsonSave("changes/" + this.file, this, this.changedIDs, cb);
}, 1000, this, "_lazySave");
@ -112,39 +116,43 @@ Tracker.prototype = {
// being processed, or that shouldn't be synced.
// But note: not persisted to disk
ignoreID: function T_ignoreID(id) {
ignoreID: function (id) {
this.unignoreID(id);
this._ignored.push(id);
},
unignoreID: function T_unignoreID(id) {
unignoreID: function (id) {
let index = this._ignored.indexOf(id);
if (index != -1)
this._ignored.splice(index, 1);
},
addChangedID: function addChangedID(id, when) {
addChangedID: function (id, when) {
if (!id) {
this._log.warn("Attempted to add undefined ID to tracker");
return false;
}
if (this.ignoreAll || (id in this._ignored))
if (this.ignoreAll || (id in this._ignored)) {
return false;
}
// Default to the current time in seconds if no time is provided
if (when == null)
// Default to the current time in seconds if no time is provided.
if (when == null) {
when = Math.floor(Date.now() / 1000);
}
// Add/update the entry if we have a newer time
// Add/update the entry if we have a newer time.
if ((this.changedIDs[id] || -Infinity) < when) {
this._log.trace("Adding changed ID: " + id + ", " + when);
this.changedIDs[id] = when;
this.saveChangedIDs(this.onSavedChangedIDs);
}
return true;
},
removeChangedID: function T_removeChangedID(id) {
removeChangedID: function (id) {
if (!id) {
this._log.warn("Attempted to remove undefined ID to tracker");
return false;
@ -159,10 +167,64 @@ Tracker.prototype = {
return true;
},
clearChangedIDs: function T_clearChangedIDs() {
clearChangedIDs: function () {
this._log.trace("Clearing changed ID list");
this.changedIDs = {};
this.saveChangedIDs();
},
_isTracking: false,
// Override these in your subclasses.
startTracking: function () {
},
stopTracking: function () {
},
engineIsEnabled: function () {
if (!this.engine) {
// Can't tell -- we must be running in a test!
return true;
}
return this.engine.enabled;
},
onEngineEnabledChanged: function (engineEnabled) {
if (engineEnabled == this._isTracking) {
return;
}
if (engineEnabled) {
this.startTracking();
this._isTracking = true;
} else {
this.stopTracking();
this._isTracking = false;
this.clearChangedIDs();
}
},
observe: function (subject, topic, data) {
switch (topic) {
case "weave:engine:start-tracking":
if (!this.engineIsEnabled()) {
return;
}
this._log.trace("Got start-tracking.");
if (!this._isTracking) {
this.startTracking();
this._isTracking = true;
}
return;
case "weave:engine:stop-tracking":
this._log.trace("Got stop-tracking.");
if (this._isTracking) {
this.stopTracking();
this._isTracking = false;
}
return;
}
}
};
@ -228,7 +290,7 @@ Store.prototype = {
* @param records Array of records to apply
* @return Array of record IDs which did not apply cleanly
*/
applyIncomingBatch: function applyIncomingBatch(records) {
applyIncomingBatch: function (records) {
let failed = [];
for each (let record in records) {
try {
@ -260,7 +322,7 @@ Store.prototype = {
* @param record
* Record to apply
*/
applyIncoming: function Store_applyIncoming(record) {
applyIncoming: function (record) {
if (record.deleted)
this.remove(record);
else if (!this.itemExists(record.id))
@ -280,7 +342,7 @@ Store.prototype = {
* @param record
* The store record to create an item from
*/
create: function Store_create(record) {
create: function (record) {
throw "override create in a subclass";
},
@ -293,7 +355,7 @@ Store.prototype = {
* @param record
* The store record to delete an item from
*/
remove: function Store_remove(record) {
remove: function (record) {
throw "override remove in a subclass";
},
@ -306,7 +368,7 @@ Store.prototype = {
* @param record
* The record to use to update an item from
*/
update: function Store_update(record) {
update: function (record) {
throw "override update in a subclass";
},
@ -320,7 +382,7 @@ Store.prototype = {
* string record ID
* @return boolean indicating whether record exists locally
*/
itemExists: function Store_itemExists(id) {
itemExists: function (id) {
throw "override itemExists in a subclass";
},
@ -338,7 +400,7 @@ Store.prototype = {
* constructor for the newly-created record.
* @return record type for this engine
*/
createRecord: function Store_createRecord(id, collection) {
createRecord: function (id, collection) {
throw "override createRecord in a subclass";
},
@ -350,7 +412,7 @@ Store.prototype = {
* @param newID
* string new record ID
*/
changeItemID: function Store_changeItemID(oldID, newID) {
changeItemID: function (oldID, newID) {
throw "override changeItemID in a subclass";
},
@ -360,7 +422,7 @@ Store.prototype = {
* @return Object with ID strings as keys and values of true. The values
* are ignored.
*/
getAllIDs: function Store_getAllIDs() {
getAllIDs: function () {
throw "override getAllIDs in a subclass";
},
@ -374,7 +436,7 @@ Store.prototype = {
* can be thought of as clearing out all state and restoring the "new
* browser" state.
*/
wipe: function Store_wipe() {
wipe: function () {
throw "override wipe in a subclass";
}
};
@ -388,14 +450,15 @@ this.EngineManager = function EngineManager(service) {
"log.logger.service.engines", "Debug")];
}
EngineManager.prototype = {
get: function get(name) {
get: function (name) {
// Return an array of engines if we have an array of names
if (Array.isArray(name)) {
let engines = [];
name.forEach(function(name) {
let engine = this.get(name);
if (engine)
if (engine) {
engines.push(engine);
}
}, this);
return engines;
}
@ -403,17 +466,18 @@ EngineManager.prototype = {
let engine = this._engines[name];
if (!engine) {
this._log.debug("Could not get engine: " + name);
if (Object.keys)
if (Object.keys) {
this._log.debug("Engines are: " + JSON.stringify(Object.keys(this._engines)));
}
}
return engine;
},
getAll: function getAll() {
getAll: function () {
return [engine for ([name, engine] in Iterator(this._engines))];
},
getEnabled: function getEnabled() {
getEnabled: function () {
return this.getAll().filter(function(engine) engine.enabled);
},
@ -425,19 +489,20 @@ EngineManager.prototype = {
* Engine object used to get an instance of the engine
* @return The engine object if anything failed
*/
register: function register(engineObject) {
if (Array.isArray(engineObject))
register: function (engineObject) {
if (Array.isArray(engineObject)) {
return engineObject.map(this.register, this);
}
try {
let engine = new engineObject(this.service);
let name = engine.name;
if (name in this._engines)
if (name in this._engines) {
this._log.error("Engine '" + name + "' is already registered!");
else
} else {
this._engines[name] = engine;
}
catch(ex) {
}
} catch (ex) {
this._log.error(CommonUtils.exceptionStr(ex));
let mesg = ex.message ? ex.message : ex;
@ -452,14 +517,15 @@ EngineManager.prototype = {
}
},
unregister: function unregister(val) {
unregister: function (val) {
let name = val;
if (val instanceof Engine)
if (val instanceof Engine) {
name = val.name;
}
delete this._engines[name];
},
clear: function clear() {
clear: function () {
for (let name in this._engines) {
delete this._engines[name];
}
@ -493,8 +559,14 @@ Engine.prototype = {
eEngineAbortApplyIncoming: "error.engine.abort.applyincoming",
get prefName() this.name,
get enabled() Svc.Prefs.get("engine." + this.prefName, false),
set enabled(val) Svc.Prefs.set("engine." + this.prefName, !!val),
get enabled() {
return Svc.Prefs.get("engine." + this.prefName, false);
},
set enabled(val) {
Svc.Prefs.set("engine." + this.prefName, !!val);
this._tracker.onEngineEnabledChanged(val);
},
get score() this._tracker.score,
@ -510,27 +582,30 @@ Engine.prototype = {
return tracker;
},
sync: function Engine_sync() {
if (!this.enabled)
sync: function () {
if (!this.enabled) {
return;
}
if (!this._sync)
if (!this._sync) {
throw "engine does not implement _sync method";
}
this._notify("sync", this.name, this._sync)();
},
/**
* Get rid of any local meta-data
* Get rid of any local meta-data.
*/
resetClient: function Engine_resetClient() {
if (!this._resetClient)
resetClient: function () {
if (!this._resetClient) {
throw "engine does not implement _resetClient method";
}
this._notify("reset-client", this.name, this._resetClient)();
},
_wipeClient: function Engine__wipeClient() {
_wipeClient: function () {
this.resetClient();
this._log.debug("Deleting all local data");
this._tracker.ignoreAll = true;
@ -539,7 +614,7 @@ Engine.prototype = {
this._tracker.clearChangedIDs();
},
wipeClient: function Engine_wipeClient() {
wipeClient: function () {
this._notify("wipe-client", this.name, this._wipeClient)();
}
};
@ -606,7 +681,7 @@ SyncEngine.prototype = {
// Store the value as a string to keep floating point precision
Svc.Prefs.set(this.name + ".lastSync", value.toString());
},
resetLastSync: function SyncEngine_resetLastSync() {
resetLastSync: function () {
this._log.debug("Resetting " + this.name + " last sync time");
Svc.Prefs.reset(this.name + ".lastSync");
Svc.Prefs.set(this.name + ".lastSync", "0");
@ -625,8 +700,8 @@ SyncEngine.prototype = {
}, 0, this, "_toFetchDelay");
},
loadToFetch: function loadToFetch() {
// Initialize to empty if there's no file
loadToFetch: function () {
// Initialize to empty if there's no file.
this._toFetch = [];
Utils.jsonLoad("toFetch/" + this.name, this, function(toFetch) {
if (toFetch) {
@ -647,7 +722,7 @@ SyncEngine.prototype = {
}, 0, this, "_previousFailedDelay");
},
loadPreviousFailed: function loadPreviousFailed() {
loadPreviousFailed: function () {
// Initialize to empty if there's no file
this._previousFailed = [];
Utils.jsonLoad("failed/" + this.name, this, function(previousFailed) {
@ -673,12 +748,12 @@ SyncEngine.prototype = {
* can override this method to bypass the tracker for certain or all
* changed items.
*/
getChangedIDs: function getChangedIDs() {
getChangedIDs: function () {
return this._tracker.changedIDs;
},
// Create a new record using the store and add in crypto fields
_createRecord: function SyncEngine__createRecord(id) {
// Create a new record using the store and add in crypto fields.
_createRecord: function (id) {
let record = this._store.createRecord(id, this.name);
record.id = id;
record.collection = this.name;
@ -686,7 +761,7 @@ SyncEngine.prototype = {
},
// Any setup that needs to happen at the beginning of each sync.
_syncStartup: function SyncEngine__syncStartup() {
_syncStartup: function () {
// Determine if we need to wipe on outdated versions
let metaGlobal = this.service.recordManager.get(this.metaURL);
@ -1031,11 +1106,11 @@ SyncEngine.prototype = {
*
* @return GUID of the similar item; falsy otherwise
*/
_findDupe: function _findDupe(item) {
_findDupe: function (item) {
// By default, assume there's no dupe items for the engine
},
_deleteId: function _deleteId(id) {
_deleteId: function (id) {
this._tracker.removeChangedID(id);
// Remember this id to delete at the end of sync
@ -1055,7 +1130,7 @@ SyncEngine.prototype = {
* @return boolean
* Truthy if incoming record should be applied. False if not.
*/
_reconcile: function _reconcile(item) {
_reconcile: function (item) {
if (this._log.level <= Log.Level.Trace) {
this._log.trace("Incoming: " + item);
}
@ -1227,8 +1302,8 @@ SyncEngine.prototype = {
return remoteIsNewer;
},
// Upload outgoing records
_uploadOutgoing: function SyncEngine__uploadOutgoing() {
// Upload outgoing records.
_uploadOutgoing: function () {
this._log.trace("Uploading local changes to server.");
let modifiedIDs = Object.keys(this._modified);
@ -1298,7 +1373,7 @@ SyncEngine.prototype = {
// Any cleanup necessary.
// Save the current snapshot so as to calculate changes at next sync
_syncFinish: function SyncEngine__syncFinish() {
_syncFinish: function () {
this._log.trace("Finishing up sync");
this._tracker.resetScore();
@ -1325,9 +1400,10 @@ SyncEngine.prototype = {
}
},
_syncCleanup: function _syncCleanup() {
if (!this._modified)
_syncCleanup: function () {
if (!this._modified) {
return;
}
// Mark failed WBOs as changed again so they are reuploaded next time.
for (let [id, when] in Iterator(this._modified)) {
@ -1336,7 +1412,7 @@ SyncEngine.prototype = {
this._modified = {};
},
_sync: function SyncEngine__sync() {
_sync: function () {
try {
this._syncStartup();
Observers.notify("weave:engine:sync:status", "process-incoming");
@ -1349,7 +1425,7 @@ SyncEngine.prototype = {
}
},
canDecrypt: function canDecrypt() {
canDecrypt: function () {
// Report failure even if there's nothing to decrypt
let canDecrypt = false;
@ -1377,13 +1453,13 @@ SyncEngine.prototype = {
return canDecrypt;
},
_resetClient: function SyncEngine__resetClient() {
_resetClient: function () {
this.resetLastSync();
this.previousFailed = [];
this.toFetch = [];
},
wipeServer: function wipeServer() {
wipeServer: function () {
let response = this.service.resource(this.engineURL).delete();
if (response.status != 200 && response.status != 404) {
throw response;
@ -1391,7 +1467,7 @@ SyncEngine.prototype = {
this._resetClient();
},
removeClientData: function removeClientData() {
removeClientData: function () {
// Implement this method in engines that store client specific data
// on the server.
},
@ -1412,7 +1488,7 @@ SyncEngine.prototype = {
*
* All return values will be part of the kRecoveryStrategy enumeration.
*/
handleHMACMismatch: function handleHMACMismatch(item, mayRetry) {
handleHMACMismatch: function (item, mayRetry) {
// By default we either try again, or bail out noisily.
return (this.service.handleHMACEvent() && mayRetry) ?
SyncEngine.kRecoveryStrategy.retry :

Просмотреть файл

@ -655,9 +655,6 @@ AddonsStore.prototype = {
*/
function AddonsTracker(name, engine) {
Tracker.call(this, name, engine);
Svc.Obs.add("weave:engine:start-tracking", this);
Svc.Obs.add("weave:engine:stop-tracking", this);
}
AddonsTracker.prototype = {
__proto__: Tracker.prototype,
@ -691,20 +688,16 @@ AddonsTracker.prototype = {
this.score += SCORE_INCREMENT_XLARGE;
},
observe: function(subject, topic, data) {
switch (topic) {
case "weave:engine:start-tracking":
if (this.engine.enabled) {
this.reconciler.startListening();
}
this.reconciler.addChangeListener(this);
break;
case "weave:engine:stop-tracking":
this.reconciler.removeChangeListener(this);
this.reconciler.stopListening();
break;
startTracking: function() {
if (this.engine.enabled) {
this.reconciler.startListening();
}
}
this.reconciler.addChangeListener(this);
},
stopTracking: function() {
this.reconciler.removeChangeListener(this);
this.reconciler.stopListening();
},
};

Просмотреть файл

@ -1298,34 +1298,28 @@ function BookmarksTracker(name, engine) {
Tracker.call(this, name, engine);
Svc.Obs.add("places-shutdown", this);
Svc.Obs.add("weave:engine:start-tracking", this);
Svc.Obs.add("weave:engine:stop-tracking", this);
}
BookmarksTracker.prototype = {
__proto__: Tracker.prototype,
_enabled: false,
observe: function observe(subject, topic, data) {
switch (topic) {
case "weave:engine:start-tracking":
if (!this._enabled) {
PlacesUtils.bookmarks.addObserver(this, true);
Svc.Obs.add("bookmarks-restore-begin", this);
Svc.Obs.add("bookmarks-restore-success", this);
Svc.Obs.add("bookmarks-restore-failed", this);
this._enabled = true;
}
break;
case "weave:engine:stop-tracking":
if (this._enabled) {
PlacesUtils.bookmarks.removeObserver(this);
Svc.Obs.remove("bookmarks-restore-begin", this);
Svc.Obs.remove("bookmarks-restore-success", this);
Svc.Obs.remove("bookmarks-restore-failed", this);
this._enabled = false;
}
break;
startTracking: function() {
PlacesUtils.bookmarks.addObserver(this, true);
Svc.Obs.add("bookmarks-restore-begin", this);
Svc.Obs.add("bookmarks-restore-success", this);
Svc.Obs.add("bookmarks-restore-failed", this);
},
stopTracking: function() {
PlacesUtils.bookmarks.removeObserver(this);
Svc.Obs.remove("bookmarks-restore-begin", this);
Svc.Obs.remove("bookmarks-restore-success", this);
Svc.Obs.remove("bookmarks-restore-failed", this);
},
observe: function observe(subject, topic, data) {
Tracker.prototype.observe.call(this, subject, topic, data);
switch (topic) {
case "bookmarks-restore-begin":
this._log.debug("Ignoring changes from importing bookmarks.");
this.ignoreAll = true;

Просмотреть файл

@ -202,8 +202,6 @@ FormStore.prototype = {
function FormTracker(name, engine) {
Tracker.call(this, name, engine);
Svc.Obs.add("weave:engine:start-tracking", this);
Svc.Obs.add("weave:engine:stop-tracking", this);
}
FormTracker.prototype = {
__proto__: Tracker.prototype,
@ -212,21 +210,18 @@ FormTracker.prototype = {
Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
_enabled: false,
startTracking: function() {
Svc.Obs.add("satchel-storage-changed", this);
},
stopTracking: function() {
Svc.Obs.remove("satchel-storage-changed", this);
},
observe: function (subject, topic, data) {
Tracker.prototype.observe.call(this, subject, topic, data);
switch (topic) {
case "weave:engine:start-tracking":
if (!this._enabled) {
Svc.Obs.add("satchel-storage-changed", this);
this._enabled = true;
}
break;
case "weave:engine:stop-tracking":
if (this._enabled) {
Svc.Obs.remove("satchel-storage-changed", this);
this._enabled = false;
}
break;
case "satchel-storage-changed":
if (data == "formhistory-add" || data == "formhistory-remove") {
let guid = subject.QueryInterface(Ci.nsISupportsString).toString();

Просмотреть файл

@ -348,28 +348,18 @@ HistoryStore.prototype = {
function HistoryTracker(name, engine) {
Tracker.call(this, name, engine);
Svc.Obs.add("weave:engine:start-tracking", this);
Svc.Obs.add("weave:engine:stop-tracking", this);
}
HistoryTracker.prototype = {
__proto__: Tracker.prototype,
_enabled: false,
observe: function observe(subject, topic, data) {
switch (topic) {
case "weave:engine:start-tracking":
if (!this._enabled) {
PlacesUtils.history.addObserver(this, true);
this._enabled = true;
}
break;
case "weave:engine:stop-tracking":
if (this._enabled) {
PlacesUtils.history.removeObserver(this);
this._enabled = false;
}
break;
}
startTracking: function() {
this._log.info("Adding Places observer.");
PlacesUtils.history.addObserver(this, true);
},
stopTracking: function() {
this._log.info("Removing Places observer.");
PlacesUtils.history.removeObserver(this);
},
QueryInterface: XPCOMUtils.generateQI([

Просмотреть файл

@ -263,49 +263,43 @@ function PasswordTracker(name, engine) {
PasswordTracker.prototype = {
__proto__: Tracker.prototype,
_enabled: false,
observe: function PasswordTracker_observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "weave:engine:start-tracking":
if (!this._enabled) {
Svc.Obs.add("passwordmgr-storage-changed", this);
this._enabled = true;
}
return;
case "weave:engine:stop-tracking":
if (this._enabled) {
Svc.Obs.remove("passwordmgr-storage-changed", this);
this._enabled = false;
}
return;
}
startTracking: function() {
Svc.Obs.add("passwordmgr-storage-changed", this);
},
if (this.ignoreAll)
stopTracking: function() {
Svc.Obs.remove("passwordmgr-storage-changed", this);
},
observe: function(subject, topic, data) {
Tracker.prototype.observe.call(this, subject, topic, data);
if (this.ignoreAll) {
return;
}
// A single add, remove or change or removing all items
// will trigger a sync for MULTI_DEVICE.
switch (aData) {
case 'modifyLogin':
aSubject = aSubject.QueryInterface(Ci.nsIArray).
queryElementAt(1, Ci.nsILoginMetaInfo);
// fallthrough
case 'addLogin':
case 'removeLogin':
// Skip over Weave password/passphrase changes
aSubject.QueryInterface(Ci.nsILoginMetaInfo).
QueryInterface(Ci.nsILoginInfo);
if (aSubject.hostname == PWDMGR_HOST)
break;
switch (data) {
case "modifyLogin":
subject = subject.QueryInterface(Ci.nsIArray).queryElementAt(1, Ci.nsILoginMetaInfo);
// fallthrough
case "addLogin":
case "removeLogin":
// Skip over Weave password/passphrase changes.
subject.QueryInterface(Ci.nsILoginMetaInfo).QueryInterface(Ci.nsILoginInfo);
if (subject.hostname == PWDMGR_HOST) {
break;
}
this.score += SCORE_INCREMENT_XLARGE;
this._log.trace(aData + ": " + aSubject.guid);
this.addChangedID(aSubject.guid);
break;
case 'removeAllLogins':
this._log.trace(aData);
this.score += SCORE_INCREMENT_XLARGE;
break;
this.score += SCORE_INCREMENT_XLARGE;
this._log.trace(data + ": " + subject.guid);
this.addChangedID(subject.guid);
break;
case "removeAllLogins":
this._log.trace(data);
this.score += SCORE_INCREMENT_XLARGE;
break;
}
}
};

Просмотреть файл

@ -67,7 +67,7 @@ PrefsEngine.prototype = {
function PrefStore(name, engine) {
Store.call(this, name, engine);
Svc.Obs.add("profile-before-change", function() {
Svc.Obs.add("profile-before-change", function () {
this.__prefs = null;
}, this);
}
@ -214,38 +214,36 @@ PrefTracker.prototype = {
__prefs: null,
get _prefs() {
if (!this.__prefs)
if (!this.__prefs) {
this.__prefs = new Preferences();
}
return this.__prefs;
},
_enabled: false,
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
case "weave:engine:start-tracking":
if (!this._enabled) {
Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch).addObserver("", this, false);
this._enabled = true;
}
break;
case "weave:engine:stop-tracking":
if (this._enabled)
this._enabled = false;
// Fall through to clean up.
startTracking: function () {
Services.prefs.addObserver("", this, false);
},
stopTracking: function () {
this.__prefs = null;
Services.prefs.removeObserver("", this);
},
observe: function (subject, topic, data) {
Tracker.prototype.observe.call(this, subject, topic, data);
switch (topic) {
case "profile-before-change":
this.__prefs = null;
Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch).removeObserver("", this);
this.stopTracking();
break;
case "nsPref:changed":
// Trigger a sync for MULTI-DEVICE for a change that determines
// which prefs are synced or a regular pref change.
if (aData.indexOf(WEAVE_SYNC_PREFS) == 0 ||
this._prefs.get(WEAVE_SYNC_PREFS + aData, false)) {
if (data.indexOf(WEAVE_SYNC_PREFS) == 0 ||
this._prefs.get(WEAVE_SYNC_PREFS + data, false)) {
this.score += SCORE_INCREMENT_XLARGE;
this.modified = true;
this._log.trace("Preference " + aData + " changed");
this._log.trace("Preference " + data + " changed");
}
break;
}

Просмотреть файл

@ -83,7 +83,7 @@ TabEngine.prototype = {
* reappear in the menu.
*/
locallyOpenTabMatchesURL: function TabEngine_localTabMatches(url) {
return this._store.getAllTabs().some(function(tab) {
return this._store.getAllTabs().some(function (tab) {
return tab.urlHistory[0] == url;
});
}
@ -122,11 +122,11 @@ TabStore.prototype = {
let currentState = JSON.parse(Svc.Session.getBrowserState());
let tabLastUsed = this.tabLastUsed;
currentState.windows.forEach(function(window) {
currentState.windows.forEach(function (window) {
if (window.isPrivate) {
return;
}
window.tabs.forEach(function(tab) {
window.tabs.forEach(function (tab) {
// Make sure there are history entries to look at.
if (!tab.entries.length)
return;
@ -158,7 +158,7 @@ TabStore.prototype = {
record.clientName = this.engine.service.clientsEngine.localName;
// Sort tabs in descending-used order to grab the most recently used
let tabs = this.getAllTabs(true).sort(function(a, b) {
let tabs = this.getAllTabs(true).sort(function (a, b) {
return b.lastUsed - a.lastUsed;
});
@ -178,7 +178,7 @@ TabStore.prototype = {
}
this._log.trace("Created tabs " + tabs.length + " of " + origLength);
tabs.forEach(function(tab) {
tabs.forEach(function (tab) {
this._log.trace("Wrapping tab: " + JSON.stringify(tab));
}, this);
@ -283,34 +283,32 @@ TabTracker.prototype = {
}
},
_enabled: false,
observe: function TabTracker_observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "weave:engine:start-tracking":
if (!this._enabled) {
Svc.Obs.add("domwindowopened", this);
let wins = Services.wm.getEnumerator("navigator:browser");
while (wins.hasMoreElements())
this._registerListenersForWindow(wins.getNext());
this._enabled = true;
}
break;
case "weave:engine:stop-tracking":
if (this._enabled) {
Svc.Obs.remove("domwindowopened", this);
let wins = Services.wm.getEnumerator("navigator:browser");
while (wins.hasMoreElements())
this._unregisterListenersForWindow(wins.getNext());
this._enabled = false;
}
return;
startTracking: function () {
Svc.Obs.add("domwindowopened", this);
let wins = Services.wm.getEnumerator("navigator:browser");
while (wins.hasMoreElements()) {
this._registerListenersForWindow(wins.getNext());
}
},
stopTracking: function () {
Svc.Obs.remove("domwindowopened", this);
let wins = Services.wm.getEnumerator("navigator:browser");
while (wins.hasMoreElements()) {
this._unregisterListenersForWindow(wins.getNext());
}
},
observe: function (subject, topic, data) {
Tracker.prototype.observe.call(this, subject, topic, data);
switch (topic) {
case "domwindowopened":
// Add tab listeners now that a window has opened
let self = this;
aSubject.addEventListener("load", function onLoad(event) {
aSubject.removeEventListener("load", onLoad, false);
// Only register after the window is done loading to avoid unloads
self._registerListenersForWindow(aSubject);
// Add tab listeners now that a window has opened.
subject.addEventListener("load", (event) => {
subject.removeEventListener("load", onLoad, false);
// Only register after the window is done loading to avoid unloads.
this._registerListenersForWindow(subject);
}, false);
break;
}

Просмотреть файл

@ -27,7 +27,7 @@ const addon1ID = "addon1@tests.mozilla.org";
function cleanup_and_advance() {
Svc.Obs.notify("weave:engine:stop-tracking");
tracker.observe(null, "weave:engine:stop-tracking");
tracker.stopTracking();
tracker.resetScore();
tracker.clearChangedIDs();

Просмотреть файл

@ -18,15 +18,15 @@ SteamStore.prototype = {
}
};
function SteamTracker(engine) {
Tracker.call(this, "Steam", engine);
function SteamTracker(name, engine) {
Tracker.call(this, name || "Steam", engine);
}
SteamTracker.prototype = {
__proto__: Tracker.prototype
};
function SteamEngine() {
Engine.call(this, "Steam", Service);
function SteamEngine(service) {
Engine.call(this, "Steam", service);
this.wasReset = false;
this.wasSynced = false;
}
@ -186,3 +186,32 @@ add_test(function test_sync() {
engineObserver.reset();
}
});
add_test(function test_disabled_no_track() {
_("When an engine is disabled, its tracker is not tracking.");
let engine = new SteamEngine(Service);
let tracker = engine._tracker;
do_check_eq(engine, tracker.engine);
do_check_false(engine.enabled);
do_check_false(tracker._isTracking);
do_check_empty(tracker.changedIDs);
do_check_false(tracker.engineIsEnabled());
tracker.observe(null, "weave:engine:start-tracking", null);
do_check_false(tracker._isTracking);
do_check_empty(tracker.changedIDs);
engine.enabled = true;
tracker.observe(null, "weave:engine:start-tracking", null);
do_check_true(tracker._isTracking);
do_check_empty(tracker.changedIDs);
tracker.addChangedID("abcdefghijkl");
do_check_true(0 < tracker.changedIDs["abcdefghijkl"]);
engine.enabled = false;
do_check_false(tracker._isTracking);
do_check_empty(tracker.changedIDs);
run_next_test();
});

Просмотреть файл

@ -72,7 +72,7 @@ add_test(function test_empty() {
_("Verify we've got an empty, disabled tracker to work with.");
do_check_empty(tracker.changedIDs);
do_check_eq(tracker.score, 0);
do_check_false(tracker._enabled);
do_check_false(tracker._isTracking);
run_next_test();
});

Просмотреть файл

@ -87,6 +87,11 @@ TEST_SUITES = {
'mach_command': 'reftest-ipc',
'kwargs': {'test_file': None},
},
'valgrind': {
'aliases': ('V', 'v'),
'mach_command': 'valgrind-test',
'kwargs': {},
},
'xpcshell': {
'aliases': ('X', 'x'),
'mach_command': 'xpcshell-test',

Просмотреть файл

@ -4,6 +4,8 @@
# 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/.
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
EXTRA_JS_MODULES = [
'CrashMonitor.jsm',
]

Просмотреть файл

@ -0,0 +1,22 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
let sessionCheckpointsPath;
/**
* Start the tasks of the different tests
*/
function run_test()
{
do_get_profile();
sessionCheckpointsPath = OS.Path.join(OS.Constants.Path.profileDir,
"sessionCheckpoints.json");
Components.utils.import("resource://gre/modules/CrashMonitor.jsm");
run_next_test();
}

Просмотреть файл

@ -0,0 +1,17 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test that calling |init| twice throws an error
*/
add_task(function test_init() {
CrashMonitor.init();
try {
CrashMonitor.init();
do_check_true(false);
} catch (ex) {
do_check_true(true);
}
});

Просмотреть файл

@ -0,0 +1,30 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test with sessionCheckpoints.json containing invalid data
*/
add_task(function test_invalid_file() {
// Write bogus data to checkpoint file
let data = "1234";
yield OS.File.writeAtomic(sessionCheckpointsPath, data,
{tmpPath: sessionCheckpointsPath + ".tmp"});
// An invalid file will cause |init| to throw an exception
try {
let status = yield CrashMonitor.init();
do_check_true(false);
} catch (ex) {
do_check_true(true);
}
// and |previousCheckpoints| will be rejected
try {
let checkpoints = yield CrashMonitor.previousCheckpoints;
do_check_true(false);
} catch (ex) {
do_check_true(true);
}
});

Просмотреть файл

@ -0,0 +1,13 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test with non-existing sessionCheckpoints.json
*/
add_task(function test_missing_file() {
CrashMonitor.init();
let checkpoints = yield CrashMonitor.previousCheckpoints;
do_check_eq(checkpoints, null);
});

Просмотреть файл

@ -0,0 +1,24 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test that CrashMonitor.jsm is correctly loaded from XPCOM component
*/
add_task(function test_register() {
let cm = Components.classes["@mozilla.org/toolkit/crashmonitor;1"]
.createInstance(Components.interfaces.nsIObserver);
// Send "profile-after-change" to trigger the initialization
cm.observe(null, "profile-after-change", null);
// If CrashMonitor was initialized properly a new call to |init|
// should fail
try {
CrashMonitor.init();
do_check_true(false);
} catch (ex) {
do_check_true(true);
}
});

Просмотреть файл

@ -0,0 +1,20 @@
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test with sessionCheckpoints.json containing valid data
*/
add_task(function test_valid_file() {
// Write valid data to checkpoint file
let data = JSON.stringify({"final-ui-startup": true});
yield OS.File.writeAtomic(sessionCheckpointsPath, data,
{tmpPath: sessionCheckpointsPath + ".tmp"});
CrashMonitor.init();
let checkpoints = yield CrashMonitor.previousCheckpoints;
do_check_true(checkpoints["final-ui-startup"]);
do_check_eq(Object.keys(checkpoints).length, 1);
});

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше