зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to inbound
This commit is contained in:
Коммит
b27e63c236
|
@ -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 += "&" ; break;
|
||||
case '<' : dst += "<" ; break;
|
||||
case '>' : dst += ">" ; break;
|
||||
case '"' : dst += """; break;
|
||||
case '\'': dst += "'"; 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&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);
|
||||
});
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче