mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 09:45:41 +00:00
Merge fx-team to m-c.
This commit is contained in:
commit
7b2d394944
@ -81,8 +81,11 @@ exports.set = function(aData, aDataType) {
|
||||
options.datatype = dataURL.mimeType;
|
||||
options.data = dataURL.data;
|
||||
}
|
||||
catch (e if e.name === "URIError") {
|
||||
// Not a valid Data URL
|
||||
catch (e) {
|
||||
// Ignore invalid URIs
|
||||
if (e.name !== "URIError") {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const self = require("../self");
|
||||
const prefs = require("../preferences/service");
|
||||
const { merge } = require("../util/object");
|
||||
const { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
const { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
|
||||
const DEFAULT_LOG_LEVEL = "error";
|
||||
const ADDON_LOG_LEVEL_PREF = "extensions." + self.id + ".sdk.console.logLevel";
|
||||
@ -44,12 +44,13 @@ let branch = Cc["@mozilla.org/preferences-service;1"].
|
||||
branch.addObserver(ADDON_LOG_LEVEL_PREF, logLevelObserver, true);
|
||||
branch.addObserver(SDK_LOG_LEVEL_PREF, logLevelObserver, true);
|
||||
|
||||
function PlainTextConsole(print) {
|
||||
function PlainTextConsole(print, innerID) {
|
||||
|
||||
let consoleOptions = {
|
||||
prefix: self.name + ": ",
|
||||
maxLogLevel: logLevel,
|
||||
dump: print
|
||||
dump: print,
|
||||
innerID: innerID
|
||||
};
|
||||
let console = new ConsoleAPI(consoleOptions);
|
||||
|
||||
|
@ -19,6 +19,7 @@ const { sandbox, evaluate, load } = require('../loader/sandbox');
|
||||
const { merge } = require('../util/object');
|
||||
const { getTabForContentWindow } = require('../tabs/utils');
|
||||
const { getInnerId } = require('../window/utils');
|
||||
const { PlainTextConsole } = require('../console/plain-text');
|
||||
|
||||
// WeakMap of sandboxes so we can access private values
|
||||
const sandboxes = new WeakMap();
|
||||
@ -197,8 +198,10 @@ const WorkerSandbox = Class({
|
||||
// script
|
||||
merge(model, result);
|
||||
|
||||
let console = new PlainTextConsole(null, getInnerId(window));
|
||||
|
||||
// Handle messages send by this script:
|
||||
setListeners(this);
|
||||
setListeners(this, console);
|
||||
|
||||
// Inject `addon` global into target document if document is trusted,
|
||||
// `addon` in document is equivalent to `self` in content script.
|
||||
@ -304,7 +307,7 @@ function importScripts (workerSandbox, ...urls) {
|
||||
}
|
||||
}
|
||||
|
||||
function setListeners (workerSandbox) {
|
||||
function setListeners (workerSandbox, console) {
|
||||
let { worker } = modelFor(workerSandbox);
|
||||
// console.xxx calls
|
||||
workerSandbox.on('console', function consoleListener (kind, ...args) {
|
||||
|
@ -1,243 +1,29 @@
|
||||
;(function(id, factory) { // Module boilerplate :(
|
||||
if (typeof(define) === 'function') { // RequireJS
|
||||
define(factory);
|
||||
} else if (typeof(require) === 'function') { // CommonJS
|
||||
factory.call(this, require, exports, module);
|
||||
} else if (String(this).indexOf('BackstagePass') >= 0) { // JSM
|
||||
this[factory.name] = {};
|
||||
try {
|
||||
this.console = this['Components'].utils
|
||||
.import('resource://gre/modules/devtools/Console.jsm', {}).console;
|
||||
}
|
||||
catch (ex) {
|
||||
// Avoid failures on different toolkit configurations.
|
||||
}
|
||||
factory(function require(uri) {
|
||||
var imports = {};
|
||||
this['Components'].utils.import(uri, imports);
|
||||
return imports;
|
||||
}, this[factory.name], { uri: __URI__, id: id });
|
||||
this.EXPORTED_SYMBOLS = [factory.name];
|
||||
} else if (~String(this).indexOf('Sandbox')) { // Sandbox
|
||||
factory(function require(uri) {}, this, { id: id });
|
||||
} else { // Browser or alike
|
||||
var globals = this;
|
||||
factory(function require(id) {
|
||||
return globals[id];
|
||||
}, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
|
||||
}
|
||||
}).call(this, 'promise/core', function Promise(require, exports, module) {
|
||||
/* 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';
|
||||
|
||||
/*
|
||||
* Uses `Promise.jsm` as a core implementation, with additional sugar
|
||||
* from previous implementation, with inspiration from `Q` and `when`
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm
|
||||
* https://github.com/cujojs/when
|
||||
* https://github.com/kriskowal/q
|
||||
*/
|
||||
const PROMISE_URI = 'resource://gre/modules/Promise.jsm';
|
||||
|
||||
getEnvironment.call(this, function ({ require, exports, module, Cu }) {
|
||||
|
||||
const { defer, resolve, all, reject, race } = Cu.import(PROMISE_URI, {}).Promise;
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
'stability': 'unstable'
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal utility: Wraps given `value` into simplified promise, successfully
|
||||
* fulfilled to a given `value`. Note the result is not a complete promise
|
||||
* implementation, as its method `then` does not returns anything.
|
||||
*/
|
||||
function fulfilled(value) {
|
||||
return { then: function then(fulfill) { fulfill(value); } };
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal utility: Wraps given input into simplified promise, pre-rejected
|
||||
* with a given `reason`. Note the result is not a complete promise
|
||||
* implementation, as its method `then` does not returns anything.
|
||||
*/
|
||||
function rejected(reason) {
|
||||
return { then: function then(fulfill, reject) { reject(reason); } };
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal utility: Returns `true` if given `value` is a promise. Value is
|
||||
* assumed to be a promise if it implements method `then`.
|
||||
*/
|
||||
function isPromise(value) {
|
||||
return value && typeof(value.then) === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates deferred object containing fresh promise & methods to either resolve
|
||||
* or reject it. The result is an object with the following properties:
|
||||
* - `promise` Eventual value representation implementing CommonJS [Promises/A]
|
||||
* (http://wiki.commonjs.org/wiki/Promises/A) API.
|
||||
* - `resolve` Single shot function that resolves enclosed `promise` with a
|
||||
* given `value`.
|
||||
* - `reject` Single shot function that rejects enclosed `promise` with a given
|
||||
* `reason`.
|
||||
*
|
||||
* An optional `prototype` argument is used as a prototype of the returned
|
||||
* `promise` allowing one to implement additional API. If prototype is not
|
||||
* passed then it falls back to `Object.prototype`.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* function fetchURI(uri, type) {
|
||||
* var deferred = defer();
|
||||
* var request = new XMLHttpRequest();
|
||||
* request.open("GET", uri, true);
|
||||
* request.responseType = type;
|
||||
* request.onload = function onload() {
|
||||
* deferred.resolve(request.response);
|
||||
* }
|
||||
* request.onerror = function(event) {
|
||||
* deferred.reject(event);
|
||||
* }
|
||||
* request.send();
|
||||
*
|
||||
* return deferred.promise;
|
||||
* }
|
||||
*/
|
||||
function defer(prototype) {
|
||||
// Define FIFO queue of observer pairs. Once promise is resolved & all queued
|
||||
// observers are forwarded to `result` and variable is set to `null`.
|
||||
var observers = [];
|
||||
|
||||
// Promise `result`, which will be assigned a resolution value once promise
|
||||
// is resolved. Note that result will always be assigned promise (or alike)
|
||||
// object to take care of propagation through promise chains. If result is
|
||||
// `null` promise is not resolved yet.
|
||||
var result = null;
|
||||
|
||||
prototype = (prototype || prototype === null) ? prototype : Object.prototype;
|
||||
|
||||
// Create an object implementing promise API.
|
||||
var promise = Object.create(prototype, {
|
||||
then: { value: function then(onFulfill, onError) {
|
||||
var deferred = defer(prototype);
|
||||
|
||||
function resolve(value) {
|
||||
// If `onFulfill` handler is provided resolve `deferred.promise` with
|
||||
// result of invoking it with a resolution value. If handler is not
|
||||
// provided propagate value through.
|
||||
try {
|
||||
deferred.resolve(onFulfill ? onFulfill(value) : value);
|
||||
}
|
||||
// `onFulfill` may throw exception in which case resulting promise
|
||||
// is rejected with thrown exception.
|
||||
catch(error) {
|
||||
if (exports._reportErrors && typeof(console) === 'object')
|
||||
console.error(error);
|
||||
// Note: Following is equivalent of `deferred.reject(error)`,
|
||||
// we use this shortcut to reduce a stack.
|
||||
deferred.resolve(rejected(error));
|
||||
}
|
||||
}
|
||||
|
||||
function reject(reason) {
|
||||
try {
|
||||
if (onError) deferred.resolve(onError(reason));
|
||||
else deferred.resolve(rejected(reason));
|
||||
}
|
||||
catch(error) {
|
||||
if (exports._reportErrors && typeof(console) === 'object')
|
||||
console.error(error);
|
||||
deferred.resolve(rejected(error));
|
||||
}
|
||||
}
|
||||
|
||||
// If enclosed promise (`this.promise`) observers queue is still alive
|
||||
// enqueue a new observer pair into it. Note that this does not
|
||||
// necessary means that promise is pending, it may already be resolved,
|
||||
// but we still have to queue observers to guarantee an order of
|
||||
// propagation.
|
||||
if (observers) {
|
||||
observers.push({ resolve: resolve, reject: reject });
|
||||
}
|
||||
// Otherwise just forward observer pair right to a `result` promise.
|
||||
else {
|
||||
result.then(resolve, reject);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}}
|
||||
})
|
||||
|
||||
var deferred = {
|
||||
promise: promise,
|
||||
/**
|
||||
* Resolves associated `promise` to a given `value`, unless it's already
|
||||
* resolved or rejected. Note that resolved promise is not necessary a
|
||||
* successfully fulfilled. Promise may be resolved with a promise `value`
|
||||
* in which case `value` promise's fulfillment / rejection will propagate
|
||||
* up to a promise resolved with `value`.
|
||||
*/
|
||||
resolve: function resolve(value) {
|
||||
if (!result) {
|
||||
// Store resolution `value` in a `result` as a promise, so that all
|
||||
// the subsequent handlers can be simply forwarded to it. Since
|
||||
// `result` will be a promise all the value / error propagation will
|
||||
// be uniformly taken care of.
|
||||
result = isPromise(value) ? value : fulfilled(value);
|
||||
|
||||
// Forward already registered observers to a `result` promise in the
|
||||
// order they were registered. Note that we intentionally dequeue
|
||||
// observer at a time until queue is exhausted. This makes sure that
|
||||
// handlers registered as side effect of observer forwarding are
|
||||
// queued instead of being invoked immediately, guaranteeing FIFO
|
||||
// order.
|
||||
while (observers.length) {
|
||||
var observer = observers.shift();
|
||||
result.then(observer.resolve, observer.reject);
|
||||
}
|
||||
|
||||
// Once `observers` queue is exhausted we `null`-ify it, so that
|
||||
// new handlers are forwarded straight to the `result`.
|
||||
observers = null;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Rejects associated `promise` with a given `reason`, unless it's already
|
||||
* resolved / rejected. This is just a (better performing) convenience
|
||||
* shortcut for `deferred.resolve(reject(reason))`.
|
||||
*/
|
||||
reject: function reject(reason) {
|
||||
// Note that if promise is resolved that does not necessary means that it
|
||||
// is successfully fulfilled. Resolution value may be a promise in which
|
||||
// case its result propagates. In other words if promise `a` is resolved
|
||||
// with promise `b`, `a` is either fulfilled or rejected depending
|
||||
// on weather `b` is fulfilled or rejected. Here `deferred.promise` is
|
||||
// resolved with a promise pre-rejected with a given `reason`, there for
|
||||
// `deferred.promise` is rejected with a given `reason`. This may feel
|
||||
// little awkward first, but doing it this way greatly simplifies
|
||||
// propagation through promise chains.
|
||||
deferred.resolve(rejected(reason));
|
||||
}
|
||||
};
|
||||
|
||||
return deferred;
|
||||
}
|
||||
exports.defer = defer;
|
||||
|
||||
/**
|
||||
* Returns a promise resolved to a given `value`. Optionally a second
|
||||
* `prototype` argument may be provided to be used as a prototype for the
|
||||
* returned promise.
|
||||
*/
|
||||
function resolve(value, prototype) {
|
||||
var deferred = defer(prototype);
|
||||
deferred.resolve(value);
|
||||
return deferred.promise;
|
||||
}
|
||||
exports.resolve = resolve;
|
||||
|
||||
/**
|
||||
* Returns a promise rejected with a given `reason`. Optionally a second
|
||||
* `prototype` argument may be provided to be used as a prototype for the
|
||||
* returned promise.
|
||||
*/
|
||||
function reject(reason, prototype) {
|
||||
var deferred = defer(prototype);
|
||||
deferred.reject(reason);
|
||||
return deferred.promise;
|
||||
}
|
||||
exports.reject = reject;
|
||||
|
||||
var promised = (function() {
|
||||
let promised = (function() {
|
||||
// Note: Define shortcuts and utility functions here in order to avoid
|
||||
// slower property accesses and unnecessary closure creations on each
|
||||
// call of this popular function.
|
||||
@ -247,15 +33,14 @@ var promised = (function() {
|
||||
|
||||
// Utility function that does following:
|
||||
// execute([ f, self, args...]) => f.apply(self, args)
|
||||
function execute(args) { return call.apply(call, args) }
|
||||
function execute (args) call.apply(call, args)
|
||||
|
||||
// Utility function that takes promise of `a` array and maybe promise `b`
|
||||
// as arguments and returns promise for `a.concat(b)`.
|
||||
function promisedConcat(promises, unknown) {
|
||||
return promises.then(function(values) {
|
||||
return resolve(unknown).then(function(value) {
|
||||
return values.concat([ value ]);
|
||||
});
|
||||
return promises.then(function (values) {
|
||||
return resolve(unknown)
|
||||
.then(function (value) values.concat([value]));
|
||||
});
|
||||
}
|
||||
|
||||
@ -280,11 +65,53 @@ var promised = (function() {
|
||||
// finally map that to promise of `f.apply(this, args...)`
|
||||
then(execute);
|
||||
};
|
||||
}
|
||||
};
|
||||
})();
|
||||
exports.promised = promised;
|
||||
|
||||
var all = promised(Array);
|
||||
exports.promised = promised;
|
||||
exports.all = all;
|
||||
exports.defer = defer;
|
||||
exports.resolve = resolve;
|
||||
exports.reject = reject;
|
||||
exports.race = race;
|
||||
exports.Promise = Promise;
|
||||
|
||||
});
|
||||
|
||||
function getEnvironment (callback) {
|
||||
let Cu, _exports, _module, _require;
|
||||
|
||||
// CommonJS / SDK
|
||||
if (typeof(require) === 'function') {
|
||||
Cu = require('chrome').Cu;
|
||||
_exports = exports;
|
||||
_module = module;
|
||||
_require = require;
|
||||
}
|
||||
// JSM
|
||||
else if (String(this).indexOf('BackstagePass') >= 0) {
|
||||
Cu = this['Components'].utils;
|
||||
_exports = this.Promise = {};
|
||||
_module = { uri: __URI__, id: 'promise/core' };
|
||||
_require = uri => {
|
||||
let imports = {};
|
||||
Cu.import(uri, imports);
|
||||
return imports;
|
||||
};
|
||||
this.EXPORTED_SYMBOLS = ['Promise'];
|
||||
// mozIJSSubScriptLoader.loadSubscript
|
||||
} else if (~String(this).indexOf('Sandbox')) {
|
||||
Cu = this['Components'].utils;
|
||||
_exports = this;
|
||||
_module = { id: 'promise/core' };
|
||||
_require = uri => {};
|
||||
}
|
||||
|
||||
callback({
|
||||
Cu: Cu,
|
||||
exports: _exports,
|
||||
module: _module,
|
||||
require: _require
|
||||
});
|
||||
}
|
||||
|
||||
|
25
addon-sdk/source/lib/sdk/model/core.js
Normal file
25
addon-sdk/source/lib/sdk/model/core.js
Normal file
@ -0,0 +1,25 @@
|
||||
/* 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": "unstable"
|
||||
};
|
||||
|
||||
const { dispatcher } = require("../util/dispatcher");
|
||||
|
||||
|
||||
// Define `modelFor` accessor function that can be implemented
|
||||
// for different types of views. Since view's we'll be dealing
|
||||
// with types that don't really play well with `instanceof`
|
||||
// operator we're gonig to use `dispatcher` that is slight
|
||||
// extension over polymorphic dispatch provided by method.
|
||||
// This allows models to extend implementations of this by
|
||||
// providing predicates:
|
||||
//
|
||||
// modelFor.when($ => $ && $.nodeName === "tab", findTabById($.id))
|
||||
const modelFor = dispatcher("modelFor");
|
||||
exports.modelFor = modelFor;
|
@ -15,27 +15,24 @@ const { merge } = require("../util/object");
|
||||
const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
|
||||
|
||||
/**
|
||||
* Open a channel synchronously for the URI given, with an optional charset, and
|
||||
* returns a resolved promise if succeed; rejected promise otherwise.
|
||||
* Reads a URI and returns a promise.
|
||||
*
|
||||
* @param uri {string} The URI to read
|
||||
* @param [options] {object} This parameter can have any or all of the following
|
||||
* fields: `charset`. By default the `charset` is set to 'UTF-8'.
|
||||
*
|
||||
* @returns {promise} The promise that will be resolved with the content of the
|
||||
* URL given.
|
||||
*
|
||||
* @example
|
||||
* let promise = readURI('resource://gre/modules/NetUtil.jsm', {
|
||||
* charset: 'US-ASCII'
|
||||
* });
|
||||
*/
|
||||
function readSync(uri, charset) {
|
||||
let { promise, resolve, reject } = defer();
|
||||
function readURI(uri, options) {
|
||||
options = options || {};
|
||||
let charset = options.charset || 'UTF-8';
|
||||
|
||||
try {
|
||||
resolve(readURISync(uri, charset));
|
||||
}
|
||||
catch (e) {
|
||||
reject("Failed to read: '" + uri + "' (Error Code: " + e.result + ")");
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a channel synchronously for the URI given, with an optional charset, and
|
||||
* returns a promise.
|
||||
*/
|
||||
function readAsync(uri, charset) {
|
||||
let channel = NetUtil.newChannel(uri, charset, null);
|
||||
|
||||
let { promise, resolve, reject } = defer();
|
||||
@ -59,34 +56,6 @@ function readAsync(uri, charset) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a URI and returns a promise. If the `sync` option is set to `true`, the
|
||||
* promise will be resolved synchronously.
|
||||
*
|
||||
* @param uri {string} The URI to read
|
||||
* @param [options] {object} This parameter can have any or all of the following
|
||||
* fields: `sync`, `charset`. By default the `charset` is set to 'UTF-8'.
|
||||
*
|
||||
* @returns {promise} The promise that will be resolved with the content of the
|
||||
* URL given.
|
||||
*
|
||||
* @example
|
||||
* let promise = readURI('resource://gre/modules/NetUtil.jsm', {
|
||||
* sync: true,
|
||||
* charset: 'US-ASCII'
|
||||
});
|
||||
*/
|
||||
function readURI(uri, options) {
|
||||
options = merge({
|
||||
charset: "UTF-8",
|
||||
sync: false
|
||||
}, options);
|
||||
|
||||
return options.sync
|
||||
? readSync(uri, options.charset)
|
||||
: readAsync(uri, options.charset);
|
||||
}
|
||||
|
||||
exports.readURI = readURI;
|
||||
|
||||
/**
|
||||
|
@ -45,15 +45,16 @@ exports.notify = function notifications_notify(options) {
|
||||
try {
|
||||
notifyWithOpts(notify);
|
||||
}
|
||||
catch (err if err instanceof Ci.nsIException &&
|
||||
err.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
|
||||
console.warn("The notification icon named by " + valOpts.iconURL +
|
||||
" does not exist. A default icon will be used instead.");
|
||||
delete valOpts.iconURL;
|
||||
notifyWithOpts(notify);
|
||||
}
|
||||
catch (err) {
|
||||
notifyWithOpts(notifyUsingConsole);
|
||||
if (err instanceof Ci.nsIException && err.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
|
||||
console.warn("The notification icon named by " + valOpts.iconURL +
|
||||
" does not exist. A default icon will be used instead.");
|
||||
delete valOpts.iconURL;
|
||||
notifyWithOpts(notify);
|
||||
}
|
||||
else {
|
||||
notifyWithOpts(notifyUsingConsole);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -367,11 +367,16 @@ function style(panel) {
|
||||
document.getAnonymousElementByAttribute(panel, "class",
|
||||
"panel-inner-arrowcontent");
|
||||
|
||||
let color = window.getComputedStyle(node).getPropertyValue("color");
|
||||
let { color, fontFamily, fontSize, fontWeight } = window.getComputedStyle(node);
|
||||
|
||||
let style = contentDocument.createElement("style");
|
||||
style.id = "sdk-panel-style";
|
||||
style.textContent = "body { color: " + color + "; }";
|
||||
style.textContent = "body { " +
|
||||
"color: " + color + ";" +
|
||||
"font-family: " + fontFamily + ";" +
|
||||
"font-weight: " + fontWeight + ";" +
|
||||
"font-size: " + fontSize + ";" +
|
||||
"}";
|
||||
|
||||
let container = contentDocument.head ? contentDocument.head :
|
||||
contentDocument.documentElement;
|
||||
|
@ -49,8 +49,9 @@ exports.TreeNode = TreeNode;
|
||||
|
||||
/*
|
||||
* Descends down from `node` applying `fn` to each in order.
|
||||
* Can be asynchronous if `fn` returns a promise. `fn` is passed
|
||||
* one argument, the current node, `curr`
|
||||
* `fn` can return values or promises -- if promise returned,
|
||||
* children are not processed until resolved. `fn` is passed
|
||||
* one argument, the current node, `curr`.
|
||||
*/
|
||||
function walk (curr, fn) {
|
||||
return promised(fn)(curr).then(val => {
|
||||
|
@ -137,7 +137,8 @@ exports.isSet = isSet;
|
||||
function reset(name) {
|
||||
try {
|
||||
prefSvc.clearUserPref(name);
|
||||
} catch (e if e.result == Cr.NS_ERROR_UNEXPECTED) {
|
||||
}
|
||||
catch (e) {
|
||||
// The pref service throws NS_ERROR_UNEXPECTED when the caller tries
|
||||
// to reset a pref that doesn't exist or is already set to its default
|
||||
// value. This interface fails silently in those cases, so callers
|
||||
@ -145,6 +146,9 @@ function reset(name) {
|
||||
// resetting first or trap exceptions after the fact. It passes through
|
||||
// other exceptions, however, so callers know about them, since we don't
|
||||
// know what other exceptions might be thrown and what they might mean.
|
||||
if (e.result != Cr.NS_ERROR_UNEXPECTED) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.reset = reset;
|
||||
|
@ -8,13 +8,16 @@ module.metadata = {
|
||||
"stability": "stable"
|
||||
};
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const file = require("./io/file");
|
||||
const prefs = require("./preferences/service");
|
||||
const jpSelf = require("./self");
|
||||
const timer = require("./timers");
|
||||
const unload = require("./system/unload");
|
||||
const { emit, on, off } = require("./event/core");
|
||||
const { defer } = require('./core/promise');
|
||||
|
||||
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
|
||||
const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
|
||||
const WRITE_PERIOD_DEFAULT = 300000; // 5 minutes
|
||||
@ -35,6 +38,57 @@ Object.defineProperties(exports, {
|
||||
}
|
||||
});
|
||||
|
||||
function getHash(data) {
|
||||
let { promise, resolve } = defer();
|
||||
|
||||
let crypto = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
|
||||
crypto.init(crypto.MD5);
|
||||
|
||||
let listener = {
|
||||
onStartRequest: function() { },
|
||||
|
||||
onDataAvailable: function(request, context, inputStream, offset, count) {
|
||||
crypto.updateFromStream(inputStream, count);
|
||||
},
|
||||
|
||||
onStopRequest: function(request, context, status) {
|
||||
resolve(crypto.finish(false));
|
||||
}
|
||||
};
|
||||
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
|
||||
createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
let stream = converter.convertToInputStream(data);
|
||||
let pump = Cc["@mozilla.org/network/input-stream-pump;1"].
|
||||
createInstance(Ci.nsIInputStreamPump);
|
||||
pump.init(stream, -1, -1, 0, 0, true);
|
||||
pump.asyncRead(listener, null);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function writeData(filename, data) {
|
||||
let { promise, resolve, reject } = defer();
|
||||
|
||||
let stream = file.open(filename, "w");
|
||||
try {
|
||||
stream.writeAsync(data, err => {
|
||||
if (err)
|
||||
reject(err);
|
||||
else
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
// writeAsync closes the stream after it's done, so only close on error.
|
||||
stream.close();
|
||||
reject(err);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
// A generic JSON store backed by a file on disk. This should be isolated
|
||||
// enough to move to its own module if need be...
|
||||
function JsonStore(options) {
|
||||
@ -43,11 +97,9 @@ function JsonStore(options) {
|
||||
this.writePeriod = options.writePeriod;
|
||||
this.onOverQuota = options.onOverQuota;
|
||||
this.onWrite = options.onWrite;
|
||||
|
||||
this.hash = null;
|
||||
unload.ensure(this);
|
||||
|
||||
this.writeTimer = timer.setInterval(this.write.bind(this),
|
||||
this.writePeriod);
|
||||
this.startTimer();
|
||||
}
|
||||
|
||||
JsonStore.prototype = {
|
||||
@ -81,11 +133,18 @@ JsonStore.prototype = {
|
||||
undefined;
|
||||
},
|
||||
|
||||
startTimer: function JsonStore_startTimer() {
|
||||
timer.setTimeout(() => {
|
||||
this.write().then(this.startTimer.bind(this));
|
||||
}, this.writePeriod);
|
||||
},
|
||||
|
||||
// Removes the backing file and all empty subdirectories.
|
||||
purge: function JsonStore_purge() {
|
||||
try {
|
||||
// This'll throw if the file doesn't exist.
|
||||
file.remove(this.filename);
|
||||
this.hash = null;
|
||||
let parentPath = this.filename;
|
||||
do {
|
||||
parentPath = file.dirname(parentPath);
|
||||
@ -105,31 +164,25 @@ JsonStore.prototype = {
|
||||
// errors cause tests to fail. Supporting "known" errors in the test
|
||||
// harness appears to be non-trivial. Maybe later.
|
||||
this.root = JSON.parse(str);
|
||||
let self = this;
|
||||
getHash(str).then(hash => this.hash = hash);
|
||||
}
|
||||
catch (err) {
|
||||
this.root = {};
|
||||
this.hash = null;
|
||||
}
|
||||
},
|
||||
|
||||
// If the store is under quota, writes the root to the backing file.
|
||||
// Otherwise quota observers are notified and nothing is written.
|
||||
write: function JsonStore_write() {
|
||||
if (this.quotaUsage > 1)
|
||||
this.onOverQuota(this);
|
||||
else
|
||||
this._write();
|
||||
},
|
||||
|
||||
// Cleans up on unload. If unloading because of uninstall, the store is
|
||||
// purged; otherwise it's written.
|
||||
unload: function JsonStore_unload(reason) {
|
||||
timer.clearInterval(this.writeTimer);
|
||||
timer.clearTimeout(this.writeTimer);
|
||||
this.writeTimer = null;
|
||||
|
||||
if (reason === "uninstall")
|
||||
this.purge();
|
||||
else
|
||||
this._write();
|
||||
this.write();
|
||||
},
|
||||
|
||||
// True if the root is an empty object.
|
||||
@ -148,32 +201,40 @@ JsonStore.prototype = {
|
||||
// Writes the root to the backing file, notifying write observers when
|
||||
// complete. If the store is over quota or if it's empty and the store has
|
||||
// never been written, nothing is written and write observers aren't notified.
|
||||
_write: function JsonStore__write() {
|
||||
write: Task.async(function JsonStore_write() {
|
||||
// Don't write if the root is uninitialized or if the store is empty and the
|
||||
// backing file doesn't yet exist.
|
||||
if (!this.isRootInited || (this._isEmpty && !file.exists(this.filename)))
|
||||
return;
|
||||
|
||||
let data = JSON.stringify(this.root);
|
||||
|
||||
// If the store is over quota, don't write. The current under-quota state
|
||||
// should persist.
|
||||
if (this.quotaUsage > 1)
|
||||
if ((this.quota > 0) && (data.length > this.quota)) {
|
||||
this.onOverQuota(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hash the data to compare it to any previously written data
|
||||
let hash = yield getHash(data);
|
||||
|
||||
if (hash == this.hash)
|
||||
return;
|
||||
|
||||
// Finally, write.
|
||||
let stream = file.open(this.filename, "w");
|
||||
try {
|
||||
stream.writeAsync(JSON.stringify(this.root), function writeAsync(err) {
|
||||
if (err)
|
||||
console.error("Error writing simple storage file: " + this.filename);
|
||||
else if (this.onWrite)
|
||||
this.onWrite(this);
|
||||
}.bind(this));
|
||||
yield writeData(this.filename, data);
|
||||
|
||||
this.hash = hash;
|
||||
if (this.onWrite)
|
||||
this.onWrite(this);
|
||||
}
|
||||
catch (err) {
|
||||
// writeAsync closes the stream after it's done, so only close on error.
|
||||
stream.close();
|
||||
console.error("Error writing simple storage file: " + this.filename);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
@ -1,10 +1,37 @@
|
||||
/* 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';
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
'stability': 'stable'
|
||||
"stability": "unstable",
|
||||
"engines": {
|
||||
"Firefox": "*",
|
||||
"Fennec": "*"
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = require('./tabs/tabs');
|
||||
const { modelFor } = require("./model/core");
|
||||
const { viewFor } = require("./view/core");
|
||||
const { isTab } = require("./tabs/utils");
|
||||
|
||||
|
||||
if (require("./system/xul-app").is("Fennec")) {
|
||||
module.exports = require("./windows/tabs-fennec").tabs;
|
||||
}
|
||||
else {
|
||||
module.exports = require("./tabs/tabs-firefox");
|
||||
}
|
||||
|
||||
const tabs = module.exports;
|
||||
|
||||
// Implement `modelFor` function for the Tab instances.
|
||||
// Finds a right model by iterating over all tab models
|
||||
// and finding one that wraps given `view`.
|
||||
modelFor.when(isTab, view => {
|
||||
for (let model of tabs) {
|
||||
if (viewFor(model) === view)
|
||||
return model;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
@ -1,19 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
|
||||
module.metadata = {
|
||||
'stability': 'unstable',
|
||||
'engines': {
|
||||
'Firefox': '*',
|
||||
'Fennec': '*'
|
||||
}
|
||||
};
|
||||
|
||||
if (require('../system/xul-app').name == 'Fennec') {
|
||||
module.exports = require('../windows/tabs-fennec').tabs;
|
||||
}
|
||||
else {
|
||||
module.exports = require('./tabs-firefox');
|
||||
}
|
@ -20,6 +20,31 @@ const { isGlobalPBSupported } = require('../private-browsing/utils');
|
||||
// Bug 834961: ignore private windows when they are not supported
|
||||
function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported });
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
// Define predicate functions that can be used to detech weather
|
||||
// we deal with fennec tabs or firefox tabs.
|
||||
|
||||
// Predicate to detect whether tab is XUL "Tab" node.
|
||||
const isXULTab = tab =>
|
||||
tab instanceof Ci.nsIDOMNode &&
|
||||
tab.nodeName === "tab" &&
|
||||
tab.namespaceURI === XUL_NS;
|
||||
exports.isXULTab = isXULTab;
|
||||
|
||||
// Predicate to detecet whether given tab is a fettec tab.
|
||||
// Unfortunately we have to guess via duck typinng of:
|
||||
// http://mxr.mozilla.org/mozilla-central/source/mobile/android/chrome/content/browser.js#2583
|
||||
const isFennecTab = tab =>
|
||||
tab &&
|
||||
tab.QueryInterface &&
|
||||
Ci.nsIBrowserTab &&
|
||||
tab.QueryInterface(Ci.nsIBrowserTab) === tab;
|
||||
exports.isFennecTab = isFennecTab;
|
||||
|
||||
const isTab = x => isXULTab(x) || isFennecTab(x);
|
||||
exports.isTab = isTab;
|
||||
|
||||
function activateTab(tab, window) {
|
||||
let gBrowser = getTabBrowserForTab(tab);
|
||||
|
||||
|
@ -8,7 +8,7 @@ const { resolveURI, Require,
|
||||
unload, override, descriptor } = require('../../toolkit/loader');
|
||||
const { ensure } = require('../system/unload');
|
||||
const addonWindow = require('../addon/window');
|
||||
const { PlainTextConsole } = require("sdk/console/plain-text");
|
||||
const { PlainTextConsole } = require('sdk/console/plain-text');
|
||||
|
||||
let defaultGlobals = override(require('../system/globals'), {
|
||||
console: console
|
||||
@ -43,33 +43,43 @@ function CustomLoader(module, globals, packaging, overrides={}) {
|
||||
};
|
||||
exports.Loader = CustomLoader;
|
||||
|
||||
function HookedPlainTextConsole(hook, print, innerID) {
|
||||
this.log = hook.bind(null, "log", innerID);
|
||||
this.info = hook.bind(null, "info", innerID);
|
||||
this.warn = hook.bind(null, "warn", innerID);
|
||||
this.error = hook.bind(null, "error", innerID);
|
||||
this.debug = hook.bind(null, "debug", innerID);
|
||||
this.exception = hook.bind(null, "exception", innerID);
|
||||
this.time = hook.bind(null, "time", innerID);
|
||||
this.timeEnd = hook.bind(null, "timeEnd", innerID);
|
||||
|
||||
this.__exposedProps__ = {
|
||||
log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
|
||||
exception: "rw", time: "rw", timeEnd: "rw"
|
||||
};
|
||||
}
|
||||
|
||||
// Creates a custom loader instance whose console module is hooked in order
|
||||
// to avoid printing messages to the console, and instead, expose them in the
|
||||
// returned `messages` array attribute
|
||||
exports.LoaderWithHookedConsole = function (module, callback) {
|
||||
let messages = [];
|
||||
function hook(msg) {
|
||||
messages.push({type: this, msg: msg});
|
||||
function hook(type, innerID, msg) {
|
||||
messages.push({ type: type, msg: msg, innerID: innerID });
|
||||
if (callback)
|
||||
callback(this, msg);
|
||||
callback(type, msg, innerID);
|
||||
}
|
||||
|
||||
return {
|
||||
loader: CustomLoader(module, {
|
||||
console: {
|
||||
log: hook.bind("log"),
|
||||
info: hook.bind("info"),
|
||||
warn: hook.bind("warn"),
|
||||
error: hook.bind("error"),
|
||||
debug: hook.bind("debug"),
|
||||
exception: hook.bind("exception"),
|
||||
time: hook.bind("time"),
|
||||
timeEnd: hook.bind("timeEnd"),
|
||||
__exposedProps__: {
|
||||
log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
|
||||
exception: "rw", time: "rw", timeEnd: "rw"
|
||||
console: new HookedPlainTextConsole(hook, null, null)
|
||||
}, override(require("@loader/options"), {
|
||||
modules: {
|
||||
'sdk/console/plain-text': {
|
||||
PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
|
||||
}
|
||||
}
|
||||
}),
|
||||
})),
|
||||
messages: messages
|
||||
};
|
||||
}
|
||||
@ -94,25 +104,19 @@ exports.LoaderWithHookedConsole2 = function (module, callback) {
|
||||
// console message type and message and if it returns false the message will
|
||||
// not be logged normally
|
||||
exports.LoaderWithFilteredConsole = function (module, callback) {
|
||||
function hook(msg) {
|
||||
if (callback && callback(this, msg) == false)
|
||||
function hook(type, innerID, msg) {
|
||||
if (callback && callback(type, msg, innerID) == false)
|
||||
return;
|
||||
console[this](msg);
|
||||
console[type](msg);
|
||||
}
|
||||
|
||||
return CustomLoader(module, {
|
||||
console: {
|
||||
log: hook.bind("log"),
|
||||
info: hook.bind("info"),
|
||||
warn: hook.bind("warn"),
|
||||
error: hook.bind("error"),
|
||||
debug: hook.bind("debug"),
|
||||
exception: hook.bind("exception"),
|
||||
time: hook.bind("time"),
|
||||
timeEnd: hook.bind("timeEnd"),
|
||||
__exposedProps__: {
|
||||
log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
|
||||
exception: "rw", time: "rw", timeEnd: "rw"
|
||||
console: new HookedPlainTextConsole(hook, null, null)
|
||||
}, override(require("@loader/options"), {
|
||||
modules: {
|
||||
'sdk/console/plain-text': {
|
||||
PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
@ -28,12 +28,14 @@ function newURI(uriStr, base) {
|
||||
let baseURI = base ? ios.newURI(base, null, null) : null;
|
||||
return ios.newURI(uriStr, null, baseURI);
|
||||
}
|
||||
catch (e if e.result == Cr.NS_ERROR_MALFORMED_URI) {
|
||||
throw new Error("malformed URI: " + uriStr);
|
||||
}
|
||||
catch (e if (e.result == Cr.NS_ERROR_FAILURE ||
|
||||
e.result == Cr.NS_ERROR_ILLEGAL_VALUE)) {
|
||||
throw new Error("invalid URI: " + uriStr);
|
||||
catch (e) {
|
||||
if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
|
||||
throw new Error("malformed URI: " + uriStr);
|
||||
}
|
||||
if (e.result == Cr.NS_ERROR_FAILURE ||
|
||||
e.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
|
||||
throw new Error("invalid URI: " + uriStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,9 +43,12 @@ function resolveResourceURI(uri) {
|
||||
var resolved;
|
||||
try {
|
||||
resolved = resProt.resolveURI(uri);
|
||||
} catch (e if e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
|
||||
throw new Error("resource does not exist: " + uri.spec);
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
|
||||
throw new Error("resource does not exist: " + uri.spec);
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
@ -63,8 +68,11 @@ let toFilename = exports.toFilename = function toFilename(url) {
|
||||
try {
|
||||
channel = channel.QueryInterface(Ci.nsIFileChannel);
|
||||
return channel.file.path;
|
||||
} catch (e if e.result == Cr.NS_NOINTERFACE) {
|
||||
throw new Error("chrome url isn't on filesystem: " + url);
|
||||
}
|
||||
catch (e) {
|
||||
if (e.result == Cr.NS_NOINTERFACE) {
|
||||
throw new Error("chrome url isn't on filesystem: " + url);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (uri.scheme == "file") {
|
||||
@ -84,17 +92,32 @@ function URL(url, base) {
|
||||
var userPass = null;
|
||||
try {
|
||||
userPass = uri.userPass ? uri.userPass : null;
|
||||
} catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
|
||||
}
|
||||
catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_FAILURE) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
var host = null;
|
||||
try {
|
||||
host = uri.host;
|
||||
} catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
|
||||
}
|
||||
catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_FAILURE) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
var port = null;
|
||||
try {
|
||||
port = uri.port == -1 ? null : uri.port;
|
||||
} catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
|
||||
}
|
||||
catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_FAILURE) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
let uriData = [uri.path, uri.path.length, {}, {}, {}, {}, {}, {}];
|
||||
URLParser.parsePath.apply(URLParser, uriData);
|
||||
@ -262,16 +285,21 @@ let getTLD = exports.getTLD = function getTLD (url) {
|
||||
let tld = null;
|
||||
try {
|
||||
tld = tlds.getPublicSuffix(uri);
|
||||
} catch (e if
|
||||
e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS ||
|
||||
e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {}
|
||||
}
|
||||
catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS &&
|
||||
e.result != Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return tld;
|
||||
};
|
||||
|
||||
let isValidURI = exports.isValidURI = function (uri) {
|
||||
try {
|
||||
newURI(uri);
|
||||
} catch(e) {
|
||||
}
|
||||
catch(e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
55
addon-sdk/source/lib/sdk/util/dispatcher.js
Normal file
55
addon-sdk/source/lib/sdk/util/dispatcher.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* 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"
|
||||
};
|
||||
|
||||
const method = require("method/core");
|
||||
|
||||
// Utility function that is just an enhancement over `method` to
|
||||
// allow predicate based dispatch in addition to polymorphic
|
||||
// dispatch. Unfortunately polymorphic dispatch does not quite
|
||||
// cuts it in the world of XPCOM where no types / classes exist
|
||||
// and all the XUL nodes share same type / prototype.
|
||||
// Probably this is more generic and belongs some place else, but
|
||||
// we can move it later once this will be relevant.
|
||||
let dispatcher = hint => {
|
||||
const base = method(hint);
|
||||
// Make a map for storing predicate, implementation mappings.
|
||||
let implementations = new Map();
|
||||
|
||||
// Dispatcher function goes through `predicate, implementation`
|
||||
// pairs to find predicate that matches first argument and
|
||||
// returns application of arguments on the associated
|
||||
// `implementation`. If no matching predicate is found delegates
|
||||
// to a `base` polymorphic function.
|
||||
let dispatch = (value, ...rest) => {
|
||||
for (let [predicate, implementation] of implementations) {
|
||||
if (predicate(value))
|
||||
return implementation(value, ...rest);
|
||||
}
|
||||
|
||||
return base(value, ...rest);
|
||||
};
|
||||
|
||||
// Expose base API.
|
||||
dispatch.define = base.define;
|
||||
dispatch.implement = base.implement;
|
||||
dispatch.toString = base.toString;
|
||||
|
||||
// Add a `when` function to allow extending function via
|
||||
// predicates.
|
||||
dispatch.when = (predicate, implementation) => {
|
||||
if (implementations.has(predicate))
|
||||
throw TypeError("Already implemented for the given predicate");
|
||||
implementations.set(predicate, implementation);
|
||||
};
|
||||
|
||||
return dispatch;
|
||||
};
|
||||
|
||||
exports.dispatcher = dispatcher;
|
@ -11,9 +11,26 @@ module.metadata = {
|
||||
}
|
||||
};
|
||||
|
||||
const { isBrowser } = require('./window/utils');
|
||||
const { modelFor } = require('./model/core');
|
||||
const { viewFor } = require('./view/core');
|
||||
|
||||
|
||||
if (require('./system/xul-app').is('Fennec')) {
|
||||
module.exports = require('./windows/fennec');
|
||||
}
|
||||
else {
|
||||
module.exports = require('./windows/firefox');
|
||||
}
|
||||
|
||||
|
||||
const browsers = module.exports.browserWindows;
|
||||
|
||||
//
|
||||
modelFor.when(isBrowser, view => {
|
||||
for (let model of browsers) {
|
||||
if (viewFor(model) === view)
|
||||
return model;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
@ -147,13 +147,13 @@ exports.testTabsGetFaviconPromiseFailure = function (assert, done) {
|
||||
exports.testRejects = function (assert, done) {
|
||||
getFavicon({})
|
||||
.then(invalidResolve(assert), validReject(assert, 'Object'))
|
||||
.then(getFavicon(null))
|
||||
.then(() => getFavicon(null))
|
||||
.then(invalidResolve(assert), validReject(assert, 'null'))
|
||||
.then(getFavicon(undefined))
|
||||
.then(() => getFavicon(undefined))
|
||||
.then(invalidResolve(assert), validReject(assert, 'undefined'))
|
||||
.then(getFavicon([]))
|
||||
.then(() => getFavicon([]))
|
||||
.then(invalidResolve(assert), validReject(assert, 'Array'))
|
||||
.then(done);
|
||||
.catch(assert.fail).then(done);
|
||||
};
|
||||
|
||||
function invalidResolve (assert) {
|
||||
|
@ -31,26 +31,25 @@ exports['test construct tree'] = function (assert) {
|
||||
assert.equal(tree.get(4).get(2), null, 'node.get descends from itself fails if not descendant');
|
||||
};
|
||||
|
||||
exports['test walk'] = function (assert) {
|
||||
exports['test walk'] = function (assert, done) {
|
||||
let resultsAll = [];
|
||||
let resultsNode = [];
|
||||
let tree = TreeNode(1);
|
||||
tree.add([2, 3, 4]);
|
||||
tree.get(2).add([2.1, 2.2]);
|
||||
|
||||
tree.walk(function (node) {
|
||||
resultsAll.push(node.value);
|
||||
});
|
||||
|
||||
[1, 2, 2.1, 2.2, 3, 4].forEach(function (num) {
|
||||
assert.ok(~resultsAll.indexOf(num), 'function applied to each node from root');
|
||||
});
|
||||
|
||||
let resultsNode = [];
|
||||
tree.get(2).walk(function (node) resultsNode.push(node.value));
|
||||
|
||||
[2, 2.1, 2.2].forEach(function (num) {
|
||||
assert.ok(~resultsNode.indexOf(num), 'function applied to each node from node');
|
||||
});
|
||||
}).then(() => {
|
||||
[1, 2, 2.1, 2.2, 3, 4].forEach(num => {
|
||||
assert.ok(~resultsAll.indexOf(num), 'function applied to each node from root');
|
||||
});
|
||||
return tree.get(2).walk(node => resultsNode.push(node.value));
|
||||
}).then(() => {
|
||||
[2, 2.1, 2.2].forEach(function (num) {
|
||||
assert.ok(~resultsNode.indexOf(num), 'function applied to each node from node');
|
||||
});
|
||||
}).catch(assert.fail).then(done);
|
||||
};
|
||||
|
||||
exports['test async walk'] = function (assert, done) {
|
||||
|
77
addon-sdk/source/test/test-dispatcher.js
Normal file
77
addon-sdk/source/test/test-dispatcher.js
Normal file
@ -0,0 +1,77 @@
|
||||
/* 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 { dispatcher } = require("sdk/util/dispatcher");
|
||||
|
||||
exports["test dispatcher API"] = assert => {
|
||||
const dispatch = dispatcher();
|
||||
|
||||
assert.equal(typeof(dispatch), "function",
|
||||
"dispatch is a function");
|
||||
|
||||
assert.equal(typeof(dispatch.define), "function",
|
||||
"dispatch.define is a function");
|
||||
|
||||
assert.equal(typeof(dispatch.implement), "function",
|
||||
"dispatch.implement is a function");
|
||||
|
||||
assert.equal(typeof(dispatch.when), "function",
|
||||
"dispatch.when is a function");
|
||||
};
|
||||
|
||||
exports["test dispatcher"] = assert => {
|
||||
const isDuck = dispatcher();
|
||||
|
||||
const quacks = x => x && typeof(x.quack) === "function";
|
||||
|
||||
const Duck = function() {};
|
||||
const Goose = function() {};
|
||||
|
||||
const True = _ => true;
|
||||
const False = _ => false;
|
||||
|
||||
|
||||
|
||||
isDuck.define(Goose, False);
|
||||
isDuck.define(Duck, True);
|
||||
isDuck.when(quacks, True);
|
||||
|
||||
assert.equal(isDuck(new Goose()), false,
|
||||
"Goose ain't duck");
|
||||
|
||||
assert.equal(isDuck(new Duck()), true,
|
||||
"Ducks are ducks");
|
||||
|
||||
assert.equal(isDuck({ quack: () => "Quaaaaaack!" }), true,
|
||||
"It's a duck if it quacks");
|
||||
|
||||
|
||||
assert.throws(() => isDuck({}), /Type does not implements method/, "not implemneted");
|
||||
|
||||
isDuck.define(Object, False);
|
||||
|
||||
assert.equal(isDuck({}), false,
|
||||
"Ain't duck if it does not quacks!");
|
||||
};
|
||||
|
||||
exports["test redefining fails"] = assert => {
|
||||
const isPM = dispatcher();
|
||||
const isAfternoon = time => time.getHours() > 12;
|
||||
|
||||
isPM.when(isAfternoon, _ => true);
|
||||
|
||||
assert.equal(isPM(new Date(Date.parse("Jan 23, 1985, 13:20:00"))), true,
|
||||
"yeap afternoon");
|
||||
assert.equal(isPM({ getHours: _ => 17 }), true,
|
||||
"seems like afternoon");
|
||||
|
||||
assert.throws(() => isPM.when(isAfternoon, x => x > 12 && x < 24),
|
||||
/Already implemented for the given predicate/,
|
||||
"can't redefine on same predicate");
|
||||
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
@ -29,18 +29,6 @@ exports["test async readURI"] = function(assert, done) {
|
||||
assert.equal(content, "", "The URL content is not load yet");
|
||||
}
|
||||
|
||||
exports["test sync readURI"] = function(assert) {
|
||||
let content = "";
|
||||
|
||||
readURI(data.url("test-net-url.txt"), { sync: true }).then(function(data) {
|
||||
content = data;
|
||||
}, function() {
|
||||
assert.fail("should not reject");
|
||||
})
|
||||
|
||||
assert.equal(content, utf8text, "The URL content is loaded properly");
|
||||
}
|
||||
|
||||
exports["test readURISync"] = function(assert) {
|
||||
let content = readURISync(data.url("test-net-url.txt"));
|
||||
|
||||
@ -62,21 +50,6 @@ exports["test async readURI with ISO-8859-1 charset"] = function(assert, done) {
|
||||
assert.equal(content, "", "The URL content is not load yet");
|
||||
}
|
||||
|
||||
exports["test sync readURI with ISO-8859-1 charset"] = function(assert) {
|
||||
let content = "";
|
||||
|
||||
readURI(data.url("test-net-url.txt"), {
|
||||
sync: true,
|
||||
charset: "ISO-8859-1"
|
||||
}).then(function(data) {
|
||||
content = data;
|
||||
}, function() {
|
||||
assert.fail("should not reject");
|
||||
})
|
||||
|
||||
assert.equal(content, latin1text, "The URL content is loaded properly");
|
||||
}
|
||||
|
||||
exports["test readURISync with ISO-8859-1 charset"] = function(assert) {
|
||||
let content = readURISync(data.url("test-net-url.txt"), "ISO-8859-1");
|
||||
|
||||
@ -93,14 +66,6 @@ exports["test async readURI with not existing file"] = function(assert, done) {
|
||||
})
|
||||
}
|
||||
|
||||
exports["test sync readURI with not existing file"] = function(assert) {
|
||||
readURI(data.url("test-net-url-fake.txt"), { sync: true }).then(function(data) {
|
||||
assert.fail("should not resolve");
|
||||
}, function(reason) {
|
||||
assert.ok(reason.indexOf("Failed to read:") === 0);
|
||||
})
|
||||
}
|
||||
|
||||
exports["test readURISync with not existing file"] = function(assert) {
|
||||
assert.throws(function() {
|
||||
readURISync(data.url("test-net-url-fake.txt"));
|
||||
@ -122,18 +87,6 @@ exports["test async readURI with data URI"] = function(assert, done) {
|
||||
assert.equal(content, "", "The URL content is not load yet");
|
||||
}
|
||||
|
||||
exports["test sync readURI with data URI"] = function(assert) {
|
||||
let content = "";
|
||||
|
||||
readURI(dataURIutf8, { sync: true }).then(function(data) {
|
||||
content = data;
|
||||
}, function() {
|
||||
assert.fail("should not reject");
|
||||
})
|
||||
|
||||
assert.equal(content, utf8text, "The URL content is loaded properly");
|
||||
}
|
||||
|
||||
exports["test readURISync with data URI"] = function(assert) {
|
||||
let content = readURISync(dataURIutf8);
|
||||
|
||||
@ -155,21 +108,6 @@ exports["test async readURI with data URI and ISO-8859-1 charset"] = function(as
|
||||
assert.equal(content, "", "The URL content is not load yet");
|
||||
}
|
||||
|
||||
exports["test sync readURI with data URI and ISO-8859-1 charset"] = function(assert) {
|
||||
let content = "";
|
||||
|
||||
readURI(dataURIlatin1, {
|
||||
sync: true,
|
||||
charset: "ISO-8859-1"
|
||||
}).then(function(data) {
|
||||
content = unescape(data);
|
||||
}, function() {
|
||||
assert.fail("should not reject");
|
||||
})
|
||||
|
||||
assert.equal(content, latin1text, "The URL content is loaded properly");
|
||||
}
|
||||
|
||||
exports["test readURISync with data URI and ISO-8859-1 charset"] = function(assert) {
|
||||
let content = unescape(readURISync(dataURIlatin1, "ISO-8859-1"));
|
||||
|
||||
@ -197,16 +135,4 @@ exports["test async readURI with chrome URI"] = function(assert, done) {
|
||||
assert.equal(content, "", "The URL content is not load yet");
|
||||
}
|
||||
|
||||
exports["test sync readURI with chrome URI"] = function(assert) {
|
||||
let content = "";
|
||||
|
||||
readURI(chromeURI, { sync: true }).then(function(data) {
|
||||
content = data;
|
||||
}, function() {
|
||||
assert.fail("should not reject");
|
||||
})
|
||||
|
||||
assert.equal(content, readURISync(chromeURI), "The URL content is loaded properly");
|
||||
}
|
||||
|
||||
require("test").run(exports)
|
||||
|
@ -24,6 +24,7 @@ const { isTabPBSupported, isWindowPBSupported, isGlobalPBSupported } = require('
|
||||
const promise = require("sdk/core/promise");
|
||||
const { pb } = require('./private-browsing/helper');
|
||||
const { URL } = require("sdk/url");
|
||||
const { LoaderWithHookedConsole } = require('sdk/test/loader');
|
||||
|
||||
const { waitUntil } = require("sdk/test/utils");
|
||||
const data = require("./fixtures");
|
||||
@ -1531,6 +1532,41 @@ exports.testDetachOnUnload = function(assert, done) {
|
||||
})
|
||||
}
|
||||
|
||||
exports.testConsole = function(assert, done) {
|
||||
let innerID;
|
||||
const TEST_URL = 'data:text/html;charset=utf-8,console';
|
||||
const { loader } = LoaderWithHookedConsole(module, onMessage);
|
||||
const { PageMod } = loader.require('sdk/page-mod');
|
||||
const system = require("sdk/system/events");
|
||||
|
||||
let seenMessage = false;
|
||||
function onMessage(type, msg, msgID) {
|
||||
seenMessage = true;
|
||||
innerID = msgID;
|
||||
}
|
||||
|
||||
let mod = PageMod({
|
||||
include: TEST_URL,
|
||||
contentScriptWhen: "ready",
|
||||
contentScript: Isolate(function() {
|
||||
console.log("Hello from the page mod");
|
||||
self.port.emit("done");
|
||||
}),
|
||||
onAttach: function(worker) {
|
||||
worker.port.on("done", function() {
|
||||
let window = getTabContentWindow(tab);
|
||||
let id = getInnerId(window);
|
||||
assert.ok(seenMessage, "Should have seen the console message");
|
||||
assert.equal(innerID, id, "Should have seen the right inner ID");
|
||||
closeTab(tab);
|
||||
done();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
let tab = openTab(getMostRecentBrowserWindow(), TEST_URL);
|
||||
}
|
||||
|
||||
exports.testSyntaxErrorInContentScript = function(assert, done) {
|
||||
const url = "data:text/html;charset=utf-8,testSyntaxErrorInContentScript";
|
||||
let hitError = null;
|
||||
|
@ -454,8 +454,13 @@ function isDestroyed(page) {
|
||||
try {
|
||||
page.postMessage("foo");
|
||||
}
|
||||
catch (err if err.message == ERR_DESTROYED) {
|
||||
return true;
|
||||
catch (err) {
|
||||
if (err.message == ERR_DESTROYED) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -901,7 +901,7 @@ exports['test passing DOM node as first argument'] = function (assert, done) {
|
||||
|
||||
let widgetNode = document.getElementById(widgetId);
|
||||
|
||||
all(warned.promise, shown.promise).
|
||||
all([warned.promise, shown.promise]).
|
||||
then(loader.unload).
|
||||
then(done, assert.fail)
|
||||
|
||||
|
@ -230,6 +230,31 @@ exports.testPlainTextConsoleBoundMethods = function(assert) {
|
||||
restorePrefs();
|
||||
};
|
||||
|
||||
exports.testConsoleInnerID = function(assert) {
|
||||
let Console = require("sdk/console/plain-text").PlainTextConsole;
|
||||
let { log, info, warn, error, debug, exception, trace } = new Console(function() {}, "test ID");
|
||||
|
||||
let messages = [];
|
||||
function onMessage({ subject }) {
|
||||
let message = subject.wrappedJSObject;
|
||||
messages.push({ msg: message.arguments[0], type: message.level, innerID: message.innerID });
|
||||
}
|
||||
|
||||
const system = require("sdk/system/events");
|
||||
system.on("console-api-log-event", onMessage);
|
||||
|
||||
log("Test log");
|
||||
warn("Test warning");
|
||||
error("Test error");
|
||||
|
||||
assert.equal(messages.length, 3, "Should see 3 log events");
|
||||
assert.deepEqual(messages[0], { msg: "Test log", type: "log", innerID: "test ID" }, "Should see the right event");
|
||||
assert.deepEqual(messages[1], { msg: "Test warning", type: "warn", innerID: "test ID" }, "Should see the right event");
|
||||
assert.deepEqual(messages[2], { msg: "Test error", type: "error", innerID: "test ID" }, "Should see the right event");
|
||||
|
||||
system.off("console-api-log-event", onMessage);
|
||||
};
|
||||
|
||||
function restorePrefs() {
|
||||
if (HAS_ORIGINAL_ADDON_LOG_LEVEL)
|
||||
prefs.set(ADDON_LOG_LEVEL_PREF, ORIGINAL_ADDON_LOG_LEVEL);
|
||||
|
@ -4,277 +4,235 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var core = require('sdk/core/promise'),
|
||||
defer = core.defer, resolve = core.resolve, reject = core.reject, all = core.all,
|
||||
promised = core.promised;
|
||||
|
||||
var { setTimeout } = require('sdk/timers');
|
||||
const { Cc, Cu, Ci } = require('chrome');
|
||||
const { setTimeout } = require('sdk/timers');
|
||||
const { prefixURI, name } = require('@loader/options');
|
||||
const addonPromiseURI = prefixURI + name + '/lib/sdk/core/promise.js';
|
||||
const builtPromiseURI = 'resource://gre/modules/commonjs/sdk/core/promise.js';
|
||||
let { Promise, defer, resolve, reject, all, promised } = require('sdk/core/promise');
|
||||
|
||||
exports['test all observers are notified'] = function(assert, done) {
|
||||
var expected = 'Taram pam param!'
|
||||
var deferred = defer()
|
||||
var pending = 10, i = 0
|
||||
let expected = 'Taram pam param!';
|
||||
let deferred = defer();
|
||||
let pending = 10, i = 0;
|
||||
|
||||
function resolved(value) {
|
||||
assert.equal(value, expected, 'value resolved as expected: #' + pending)
|
||||
if (!--pending) done()
|
||||
assert.equal(value, expected, 'value resolved as expected: #' + pending);
|
||||
if (!--pending) done();
|
||||
}
|
||||
|
||||
while (i++ < pending) deferred.promise.then(resolved)
|
||||
while (i++ < pending) deferred.promise.then(resolved);
|
||||
|
||||
deferred.resolve(expected)
|
||||
}
|
||||
deferred.resolve(expected);
|
||||
};
|
||||
|
||||
exports['test exceptions dont stop notifications'] = function(assert, done) {
|
||||
var threw = false, boom = Error('Boom!')
|
||||
var deferred = defer()
|
||||
let threw = false, boom = Error('Boom!');
|
||||
let deferred = defer();
|
||||
|
||||
var promise2 = deferred.promise.then(function() {
|
||||
threw = true
|
||||
throw boom
|
||||
})
|
||||
let promise2 = deferred.promise.then(function() {
|
||||
threw = true;
|
||||
throw boom;
|
||||
});
|
||||
|
||||
deferred.promise.then(function() {
|
||||
assert.ok(threw, 'observer is called even though previos one threw')
|
||||
assert.ok(threw, 'observer is called even though previos one threw');
|
||||
promise2.then(function() {
|
||||
assert.fail('should not resolve')
|
||||
assert.fail('should not resolve');
|
||||
}, function(reason) {
|
||||
assert.equal(reason, boom, 'rejects to thrown error')
|
||||
done()
|
||||
})
|
||||
})
|
||||
assert.equal(reason, boom, 'rejects to thrown error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
deferred.resolve('go!')
|
||||
}
|
||||
deferred.resolve('go!');
|
||||
};
|
||||
|
||||
exports['test subsequent resolves are ignored'] = function(assert, done) {
|
||||
var deferred = defer()
|
||||
deferred.resolve(1)
|
||||
deferred.resolve(2)
|
||||
deferred.reject(3)
|
||||
let deferred = defer();
|
||||
deferred.resolve(1);
|
||||
deferred.resolve(2);
|
||||
deferred.reject(3);
|
||||
|
||||
deferred.promise.then(function(actual) {
|
||||
assert.equal(actual, 1, 'resolves to first value')
|
||||
assert.equal(actual, 1, 'resolves to first value');
|
||||
}, function() {
|
||||
assert.fail('must not reject')
|
||||
})
|
||||
assert.fail('must not reject');
|
||||
});
|
||||
deferred.promise.then(function(actual) {
|
||||
assert.equal(actual, 1, 'subsequent resolutions are ignored')
|
||||
done()
|
||||
assert.equal(actual, 1, 'subsequent resolutions are ignored');
|
||||
done();
|
||||
}, function() {
|
||||
assert.fail('must not reject')
|
||||
})
|
||||
}
|
||||
assert.fail('must not reject');
|
||||
});
|
||||
};
|
||||
|
||||
exports['test subsequent rejections are ignored'] = function(assert, done) {
|
||||
var deferred = defer()
|
||||
deferred.reject(1)
|
||||
deferred.resolve(2)
|
||||
deferred.reject(3)
|
||||
let deferred = defer();
|
||||
deferred.reject(1);
|
||||
deferred.resolve(2);
|
||||
deferred.reject(3);
|
||||
|
||||
deferred.promise.then(function(actual) {
|
||||
assert.fail('must not resolve')
|
||||
assert.fail('must not resolve');
|
||||
}, function(actual) {
|
||||
assert.equal(actual, 1, 'must reject to first')
|
||||
})
|
||||
assert.equal(actual, 1, 'must reject to first');
|
||||
});
|
||||
deferred.promise.then(function(actual) {
|
||||
assert.fail('must not resolve')
|
||||
assert.fail('must not resolve');
|
||||
}, function(actual) {
|
||||
assert.equal(actual, 1, 'must reject to first')
|
||||
done()
|
||||
})
|
||||
}
|
||||
assert.equal(actual, 1, 'must reject to first');
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
exports['test error recovery'] = function(assert, done) {
|
||||
var boom = Error('Boom!')
|
||||
var deferred = defer()
|
||||
let boom = Error('Boom!');
|
||||
let deferred = defer();
|
||||
|
||||
deferred.promise.then(function() {
|
||||
assert.fail('rejected promise should not resolve')
|
||||
assert.fail('rejected promise should not resolve');
|
||||
}, function(reason) {
|
||||
assert.equal(reason, boom, 'rejection reason delivered')
|
||||
return 'recovery'
|
||||
assert.equal(reason, boom, 'rejection reason delivered');
|
||||
return 'recovery';
|
||||
}).then(function(value) {
|
||||
assert.equal(value, 'recovery', 'error handled by a handler')
|
||||
done()
|
||||
})
|
||||
|
||||
deferred.reject(boom)
|
||||
}
|
||||
assert.equal(value, 'recovery', 'error handled by a handler');
|
||||
done();
|
||||
});
|
||||
|
||||
deferred.reject(boom);
|
||||
};
|
||||
|
||||
exports['test error recovery with promise'] = function(assert, done) {
|
||||
var deferred = defer()
|
||||
let deferred = defer();
|
||||
|
||||
deferred.promise.then(function() {
|
||||
assert.fail('must reject')
|
||||
assert.fail('must reject');
|
||||
}, function(actual) {
|
||||
assert.equal(actual, 'reason', 'rejected')
|
||||
var deferred = defer()
|
||||
deferred.resolve('recovery')
|
||||
return deferred.promise
|
||||
assert.equal(actual, 'reason', 'rejected');
|
||||
let deferred = defer();
|
||||
deferred.resolve('recovery');
|
||||
return deferred.promise;
|
||||
}).then(function(actual) {
|
||||
assert.equal(actual, 'recovery', 'recorvered via promise')
|
||||
var deferred = defer()
|
||||
deferred.reject('error')
|
||||
return deferred.promise
|
||||
assert.equal(actual, 'recovery', 'recorvered via promise');
|
||||
let deferred = defer();
|
||||
deferred.reject('error');
|
||||
return deferred.promise;
|
||||
}).then(null, function(actual) {
|
||||
assert.equal(actual, 'error', 'rejected via promise')
|
||||
var deferred = defer()
|
||||
deferred.reject('end')
|
||||
return deferred.promise
|
||||
assert.equal(actual, 'error', 'rejected via promise');
|
||||
let deferred = defer();
|
||||
deferred.reject('end');
|
||||
return deferred.promise;
|
||||
}).then(null, function(actual) {
|
||||
assert.equal(actual, 'end', 'rejeced via promise')
|
||||
done()
|
||||
})
|
||||
assert.equal(actual, 'end', 'rejeced via promise');
|
||||
done();
|
||||
});
|
||||
|
||||
deferred.reject('reason')
|
||||
}
|
||||
deferred.reject('reason');
|
||||
};
|
||||
|
||||
exports['test propagation'] = function(assert, done) {
|
||||
var d1 = defer(), d2 = defer(), d3 = defer()
|
||||
let d1 = defer(), d2 = defer(), d3 = defer();
|
||||
|
||||
d1.promise.then(function(actual) {
|
||||
assert.equal(actual, 'expected', 'resolves to expected value')
|
||||
done()
|
||||
})
|
||||
assert.equal(actual, 'expected', 'resolves to expected value');
|
||||
done();
|
||||
});
|
||||
|
||||
d1.resolve(d2.promise)
|
||||
d2.resolve(d3.promise)
|
||||
d3.resolve('expected')
|
||||
}
|
||||
d1.resolve(d2.promise);
|
||||
d2.resolve(d3.promise);
|
||||
d3.resolve('expected');
|
||||
};
|
||||
|
||||
exports['test chaining'] = function(assert, done) {
|
||||
var boom = Error('boom'), brax = Error('braxXXx')
|
||||
var deferred = defer()
|
||||
let boom = Error('boom'), brax = Error('braxXXx');
|
||||
let deferred = defer();
|
||||
|
||||
deferred.promise.then().then().then(function(actual) {
|
||||
assert.equal(actual, 2, 'value propagates unchanged')
|
||||
return actual + 2
|
||||
assert.equal(actual, 2, 'value propagates unchanged');
|
||||
return actual + 2;
|
||||
}).then(null, function(reason) {
|
||||
assert.fail('should not reject')
|
||||
assert.fail('should not reject');
|
||||
}).then(function(actual) {
|
||||
assert.equal(actual, 4, 'value propagates through if not handled')
|
||||
throw boom
|
||||
assert.equal(actual, 4, 'value propagates through if not handled');
|
||||
throw boom;
|
||||
}).then(function(actual) {
|
||||
assert.fail('exception must reject promise')
|
||||
assert.fail('exception must reject promise');
|
||||
}).then().then(null, function(actual) {
|
||||
assert.equal(actual, boom, 'reason propagates unchanged')
|
||||
throw brax
|
||||
assert.equal(actual, boom, 'reason propagates unchanged');
|
||||
throw brax;
|
||||
}).then().then(null, function(actual) {
|
||||
assert.equal(actual, brax, 'reason changed becase of exception')
|
||||
return 'recovery'
|
||||
assert.equal(actual, brax, 'reason changed becase of exception');
|
||||
return 'recovery';
|
||||
}).then(function(actual) {
|
||||
assert.equal(actual, 'recovery', 'recovered from error')
|
||||
done()
|
||||
})
|
||||
|
||||
deferred.resolve(2)
|
||||
}
|
||||
assert.equal(actual, 'recovery', 'recovered from error');
|
||||
done();
|
||||
});
|
||||
|
||||
deferred.resolve(2);
|
||||
};
|
||||
|
||||
exports['test reject'] = function(assert, done) {
|
||||
var expected = Error('boom')
|
||||
let expected = Error('boom');
|
||||
|
||||
reject(expected).then(function() {
|
||||
assert.fail('should reject')
|
||||
assert.fail('should reject');
|
||||
}, function(actual) {
|
||||
assert.equal(actual, expected, 'rejected with expected reason')
|
||||
}).then(function() {
|
||||
done()
|
||||
})
|
||||
}
|
||||
assert.equal(actual, expected, 'rejected with expected reason');
|
||||
}).then(done, assert.fail);
|
||||
};
|
||||
|
||||
exports['test resolve to rejected'] = function(assert, done) {
|
||||
var expected = Error('boom')
|
||||
var deferred = defer()
|
||||
let expected = Error('boom');
|
||||
let deferred = defer();
|
||||
|
||||
deferred.promise.then(function() {
|
||||
assert.fail('should reject')
|
||||
assert.fail('should reject');
|
||||
}, function(actual) {
|
||||
assert.equal(actual, expected, 'rejected with expected failure')
|
||||
}).then(function() {
|
||||
done()
|
||||
})
|
||||
assert.equal(actual, expected, 'rejected with expected failure');
|
||||
}).then(done, assert.fail);
|
||||
|
||||
deferred.resolve(reject(expected))
|
||||
}
|
||||
deferred.resolve(reject(expected));
|
||||
};
|
||||
|
||||
exports['test resolve'] = function(assert, done) {
|
||||
var expected = 'value'
|
||||
let expected = 'value';
|
||||
resolve(expected).then(function(actual) {
|
||||
assert.equal(actual, expected, 'resolved as expected')
|
||||
}).then(function() {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
exports['test resolve with prototype'] = function(assert, done) {
|
||||
var seventy = resolve(70, {
|
||||
subtract: function subtract(y) {
|
||||
return this.then(function(x) { return x - y })
|
||||
}
|
||||
})
|
||||
|
||||
seventy.subtract(17).then(function(actual) {
|
||||
assert.equal(actual, 70 - 17, 'resolves to expected')
|
||||
done()
|
||||
})
|
||||
}
|
||||
assert.equal(actual, expected, 'resolved as expected');
|
||||
}).catch(assert.fail).then(done);
|
||||
};
|
||||
|
||||
exports['test promised with normal args'] = function(assert, done) {
|
||||
var sum = promised(function(x, y) { return x + y })
|
||||
let sum = promised((x, y) => x + y );
|
||||
|
||||
sum(7, 8).then(function(actual) {
|
||||
assert.equal(actual, 7 + 8, 'resolves as expected')
|
||||
done()
|
||||
})
|
||||
}
|
||||
assert.equal(actual, 7 + 8, 'resolves as expected');
|
||||
}).catch(assert.fail).then(done);
|
||||
};
|
||||
|
||||
exports['test promised with promise args'] = function(assert, done) {
|
||||
var sum = promised(function(x, y) { return x + y })
|
||||
var deferred = defer()
|
||||
let sum = promised((x, y) => x + y );
|
||||
let deferred = defer();
|
||||
|
||||
sum(11, deferred.promise).then(function(actual) {
|
||||
assert.equal(actual, 11 + 24, 'resolved as expected')
|
||||
done()
|
||||
})
|
||||
assert.equal(actual, 11 + 24, 'resolved as expected');
|
||||
}).catch(assert.fail).then(done);
|
||||
|
||||
deferred.resolve(24)
|
||||
}
|
||||
|
||||
exports['test promised with prototype'] = function(assert, done) {
|
||||
var deferred = defer()
|
||||
var numeric = {}
|
||||
numeric.subtract = promised(function(y) { return this - y }, numeric)
|
||||
|
||||
var sum = promised(function(x, y) { return x + y }, numeric)
|
||||
|
||||
sum(7, 70).
|
||||
subtract(14).
|
||||
subtract(deferred.promise).
|
||||
subtract(5).
|
||||
then(function(actual) {
|
||||
assert.equal(actual, 7 + 70 - 14 - 23 - 5, 'resolved as expected')
|
||||
done()
|
||||
})
|
||||
|
||||
deferred.resolve(23)
|
||||
}
|
||||
deferred.resolve(24);
|
||||
};
|
||||
|
||||
exports['test promised error handleing'] = function(assert, done) {
|
||||
var expected = Error('boom')
|
||||
var f = promised(function() {
|
||||
throw expected
|
||||
})
|
||||
let expected = Error('boom');
|
||||
let f = promised(function() {
|
||||
throw expected;
|
||||
});
|
||||
|
||||
f().then(function() {
|
||||
assert.fail('should reject')
|
||||
assert.fail('should reject');
|
||||
}, function(actual) {
|
||||
assert.equal(actual, expected, 'rejected as expected')
|
||||
done()
|
||||
})
|
||||
}
|
||||
assert.equal(actual, expected, 'rejected as expected');
|
||||
}).catch(assert.fail).then(done);
|
||||
};
|
||||
|
||||
exports['test errors in promise resolution handlers are propagated'] = function(assert, done) {
|
||||
var expected = Error('Boom');
|
||||
@ -289,56 +247,67 @@ exports['test errors in promise resolution handlers are propagated'] = function(
|
||||
}).then(done, assert.fail);
|
||||
|
||||
resolve({});
|
||||
}
|
||||
};
|
||||
|
||||
exports['test return promise form promised'] = function(assert, done) {
|
||||
var f = promised(function() {
|
||||
return resolve(17)
|
||||
})
|
||||
let f = promised(function() {
|
||||
return resolve(17);
|
||||
});
|
||||
|
||||
f().then(function(actual) {
|
||||
assert.equal(actual, 17, 'resolves to a promise resolution')
|
||||
done()
|
||||
})
|
||||
}
|
||||
assert.equal(actual, 17, 'resolves to a promise resolution');
|
||||
}).catch(assert.fail).then(done);
|
||||
};
|
||||
|
||||
exports['test promised returning failure'] = function(assert, done) {
|
||||
var expected = Error('boom')
|
||||
var f = promised(function() {
|
||||
return reject(expected)
|
||||
})
|
||||
let expected = Error('boom');
|
||||
let f = promised(function() {
|
||||
return reject(expected);
|
||||
});
|
||||
|
||||
f().then(function() {
|
||||
assert.fail('must reject')
|
||||
assert.fail('must reject');
|
||||
}, function(actual) {
|
||||
assert.equal(actual, expected, 'rejects with expected reason')
|
||||
done()
|
||||
})
|
||||
}
|
||||
assert.equal(actual, expected, 'rejects with expected reason');
|
||||
}).catch(assert.fail).then(done);
|
||||
};
|
||||
|
||||
exports['test promised are greedy'] = function(assert, done) {
|
||||
var runs = 0
|
||||
var f = promised(function() { ++runs })
|
||||
var promise = f()
|
||||
assert.equal(runs, 1, 'promised runs task right away')
|
||||
done()
|
||||
}
|
||||
/*
|
||||
* Changed for compliance in Bug 881047, promises are now always async
|
||||
*/
|
||||
exports['test promises are always async'] = function (assert, done) {
|
||||
let runs = 0;
|
||||
resolve(1)
|
||||
.then(val => ++runs)
|
||||
.catch(assert.fail).then(done);
|
||||
assert.equal(runs, 0, 'resolutions are called in following tick');
|
||||
};
|
||||
|
||||
/*
|
||||
* Changed for compliance in Bug 881047, promised's are now non greedy
|
||||
*/
|
||||
exports['test promised are not greedy'] = function(assert, done) {
|
||||
let runs = 0;
|
||||
promised(() => ++runs)()
|
||||
.catch(assert.fail).then(done);
|
||||
assert.equal(runs, 0, 'promised does not run task right away');
|
||||
};
|
||||
|
||||
exports['test arrays should not flatten'] = function(assert, done) {
|
||||
var a = defer()
|
||||
var b = defer()
|
||||
let a = defer();
|
||||
let b = defer();
|
||||
|
||||
var combine = promised(function(str, arr) {
|
||||
assert.equal(str, 'Hello', 'Array was not flattened')
|
||||
assert.deepEqual(arr, [ 'my', 'friend' ])
|
||||
})
|
||||
let combine = promised(function(str, arr) {
|
||||
assert.equal(str, 'Hello', 'Array was not flattened');
|
||||
assert.deepEqual(arr, [ 'my', 'friend' ]);
|
||||
});
|
||||
|
||||
combine(a.promise, b.promise).then(done)
|
||||
combine(a.promise, b.promise).catch(assert.fail).then(done);
|
||||
|
||||
|
||||
a.resolve('Hello')
|
||||
b.resolve([ 'my', 'friend' ])
|
||||
}
|
||||
a.resolve('Hello');
|
||||
b.resolve([ 'my', 'friend' ]);
|
||||
};
|
||||
|
||||
exports['test `all` for all promises'] = function (assert, done) {
|
||||
all([
|
||||
@ -366,7 +335,7 @@ exports['test `all` aborts upon first reject'] = function (assert, done) {
|
||||
});
|
||||
|
||||
function delayedResolve () {
|
||||
var deferred = defer();
|
||||
let deferred = defer();
|
||||
setTimeout(deferred.resolve, 50);
|
||||
return deferred.promise;
|
||||
}
|
||||
@ -404,4 +373,78 @@ exports['test `all` with multiple rejected'] = function (assert, done) {
|
||||
});
|
||||
};
|
||||
|
||||
require("test").run(exports)
|
||||
exports['test Promise constructor resolve'] = function (assert, done) {
|
||||
var isAsync = true;
|
||||
new Promise(function (resolve, reject) {
|
||||
resolve(5);
|
||||
}).then(x => {
|
||||
isAsync = false;
|
||||
assert.equal(x, 5, 'Promise constructor resolves correctly');
|
||||
}).catch(assert.fail).then(done);
|
||||
assert.ok(isAsync, 'Promise constructor runs async');
|
||||
};
|
||||
|
||||
exports['test Promise constructor reject'] = function (assert, done) {
|
||||
new Promise(function (resolve, reject) {
|
||||
reject(new Error('deferred4life'));
|
||||
}).then(assert.fail, (err) => {
|
||||
assert.equal(err.message, 'deferred4life', 'Promise constructor rejects correctly');
|
||||
}).catch(assert.fail).then(done);
|
||||
};
|
||||
|
||||
exports['test JSM Load and API'] = function (assert, done) {
|
||||
// Use addon URL when loading from cfx/local:
|
||||
// resource://90111c90-c31e-4dc7-ac35-b65947434435-at-jetpack/addon-sdk/lib/sdk/core/promise.js
|
||||
// Use built URL when testing on try, etc.
|
||||
// resource://gre/modules/commonjs/sdk/core/promise.js
|
||||
try {
|
||||
var { Promise } = Cu.import(addonPromiseURI, {});
|
||||
} catch (e) {
|
||||
var { Promise } = Cu.import(builtPromiseURI, {});
|
||||
}
|
||||
testEnvironment(Promise, assert, done, 'JSM');
|
||||
};
|
||||
|
||||
exports['test mozIJSSubScriptLoader exporting'] = function (assert, done) {
|
||||
let { Services } = Cu.import('resource://gre/modules/Services.jsm', {});
|
||||
let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
|
||||
let Promise = new Cu.Sandbox(systemPrincipal);
|
||||
let loader = Cc['@mozilla.org/moz/jssubscript-loader;1']
|
||||
.getService(Ci.mozIJSSubScriptLoader);
|
||||
|
||||
// Use addon URL when loading from cfx/local:
|
||||
// resource://90111c90-c31e-4dc7-ac35-b65947434435-at-jetpack/addon-sdk/lib/sdk/core/promise.js
|
||||
// Use built URL when testing on try, etc.
|
||||
// resource://gre/modules/commonjs/sdk/core/promise.js
|
||||
try {
|
||||
loader.loadSubScript(addonPromiseURI, Promise);
|
||||
} catch (e) {
|
||||
loader.loadSubScript(builtPromiseURI, Promise);
|
||||
}
|
||||
|
||||
testEnvironment(Promise, assert, done, 'mozIJSSubScript');
|
||||
};
|
||||
|
||||
function testEnvironment ({all, resolve, defer, reject, promised}, assert, done, type) {
|
||||
all([resolve(5), resolve(10), 925]).then(val => {
|
||||
assert.equal(val[0], 5, 'promise#all works ' + type);
|
||||
assert.equal(val[1], 10, 'promise#all works ' + type);
|
||||
assert.equal(val[2], 925, 'promise#all works ' + type);
|
||||
return resolve(1000);
|
||||
}).then(value => {
|
||||
assert.equal(value, 1000, 'promise#resolve works ' + type);
|
||||
return reject('testing reject');
|
||||
}).then(null, reason => {
|
||||
assert.equal(reason, 'testing reject', 'promise#reject works ' + type);
|
||||
let deferred = defer();
|
||||
setTimeout(() => deferred.resolve('\\m/'), 10);
|
||||
return deferred.promise;
|
||||
}).then(value => {
|
||||
assert.equal(value, '\\m/', 'promise#defer works ' + type);
|
||||
return promised(x => x * x)(5);
|
||||
}).then(value => {
|
||||
assert.equal(value, 25, 'promise#promised works ' + type);
|
||||
}).then(done, assert.fail);
|
||||
}
|
||||
|
||||
require("sdk/test").run(exports);
|
||||
|
@ -6,6 +6,7 @@ const file = require("sdk/io/file");
|
||||
const prefs = require("sdk/preferences/service");
|
||||
|
||||
const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
|
||||
const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
|
||||
|
||||
let {Cc,Ci} = require("chrome");
|
||||
|
||||
@ -18,6 +19,7 @@ let storeFile = Cc["@mozilla.org/file/directory_service;1"].
|
||||
storeFile.append("jetpack");
|
||||
storeFile.append(id);
|
||||
storeFile.append("simple-storage");
|
||||
file.mkpath(storeFile.path);
|
||||
storeFile.append("store.json");
|
||||
let storeFilename = storeFile.path;
|
||||
|
||||
@ -33,12 +35,13 @@ exports.testSetGet = function (assert, done) {
|
||||
// Load the module again and make sure the value stuck.
|
||||
loader = Loader(module);
|
||||
ss = loader.require("sdk/simple-storage");
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
file.remove(storeFilename);
|
||||
done();
|
||||
};
|
||||
assert.equal(ss.storage.foo, val, "Value should persist");
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.fail("Nothing should be written since `storage` was not changed.");
|
||||
};
|
||||
loader.unload();
|
||||
file.remove(storeFilename);
|
||||
done();
|
||||
};
|
||||
let val = "foo";
|
||||
ss.storage.foo = val;
|
||||
@ -104,6 +107,23 @@ exports.testEmpty = function (assert) {
|
||||
assert.ok(!file.exists(storeFilename), "Store file should not exist");
|
||||
};
|
||||
|
||||
exports.testStorageDataRecovery = function(assert) {
|
||||
const data = {
|
||||
a: true,
|
||||
b: [3, 13],
|
||||
c: "guilty!",
|
||||
d: { e: 1, f: 2 }
|
||||
};
|
||||
let stream = file.open(storeFilename, "w");
|
||||
stream.write(JSON.stringify(data));
|
||||
stream.close();
|
||||
let loader = Loader(module);
|
||||
let ss = loader.require("sdk/simple-storage");
|
||||
assert.deepEqual(ss.storage, data, "Recovered data should be the same as written");
|
||||
file.remove(storeFilename);
|
||||
loader.unload();
|
||||
}
|
||||
|
||||
exports.testMalformed = function (assert) {
|
||||
let stream = file.open(storeFilename, "w");
|
||||
stream.write("i'm not json");
|
||||
@ -116,6 +136,7 @@ exports.testMalformed = function (assert) {
|
||||
break;
|
||||
}
|
||||
assert.ok(empty, "Malformed storage should cause root to be empty");
|
||||
file.remove(storeFilename);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
@ -141,10 +162,11 @@ exports.testQuotaExceededHandle = function (assert, done) {
|
||||
assert.equal(ss.storage.x, 4, "x value should be correct");
|
||||
assert.equal(ss.storage.y, 5, "y value should be correct");
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
prefs.reset(QUOTA_PREF);
|
||||
done();
|
||||
assert.fail("Nothing should be written since `storage` was not changed.");
|
||||
};
|
||||
loader.unload();
|
||||
prefs.reset(QUOTA_PREF);
|
||||
done();
|
||||
};
|
||||
loader.unload();
|
||||
});
|
||||
@ -178,6 +200,9 @@ exports.testQuotaExceededNoHandle = function (assert, done) {
|
||||
assert.equal(ss.storage, val,
|
||||
"Over-quota value should not have been written, " +
|
||||
"old value should have persisted: " + ss.storage);
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.fail("Nothing should be written since `storage` was not changed.");
|
||||
};
|
||||
loader.unload();
|
||||
prefs.reset(QUOTA_PREF);
|
||||
done();
|
||||
@ -232,6 +257,184 @@ exports.testUninstall = function (assert, done) {
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports.testChangeInnerArray = function(assert, done) {
|
||||
prefs.set(WRITE_PERIOD_PREF, 10);
|
||||
|
||||
let expected = {
|
||||
x: [5, 7],
|
||||
y: [7, 28],
|
||||
z: [6, 2]
|
||||
};
|
||||
|
||||
// Load the module, set a value.
|
||||
let loader = Loader(module);
|
||||
let ss = loader.require("sdk/simple-storage");
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.ok(file.exists(storeFilename), "Store file should exist");
|
||||
|
||||
// Load the module again and check the result
|
||||
loader = Loader(module);
|
||||
ss = loader.require("sdk/simple-storage");
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
|
||||
// Add a property
|
||||
ss.storage.x.push(["bar"]);
|
||||
expected.x.push(["bar"]);
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
|
||||
// Modify a property
|
||||
ss.storage.y[0] = 42;
|
||||
expected.y[0] = 42;
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
|
||||
// Delete a property
|
||||
delete ss.storage.z[1];
|
||||
delete expected.z[1];
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
|
||||
// Modify the new inner-object
|
||||
ss.storage.x[2][0] = "baz";
|
||||
expected.x[2][0] = "baz";
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.fail("Nothing should be written since `storage` was not changed.");
|
||||
};
|
||||
loader.unload();
|
||||
|
||||
// Load the module again and check the result
|
||||
loader = Loader(module);
|
||||
ss = loader.require("sdk/simple-storage");
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
loader.unload();
|
||||
file.remove(storeFilename);
|
||||
prefs.reset(WRITE_PERIOD_PREF);
|
||||
done();
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
ss.storage = {
|
||||
x: [5, 7],
|
||||
y: [7, 28],
|
||||
z: [6, 2]
|
||||
};
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports.testChangeInnerObject = function(assert, done) {
|
||||
prefs.set(WRITE_PERIOD_PREF, 10);
|
||||
|
||||
let expected = {
|
||||
x: {
|
||||
a: 5,
|
||||
b: 7
|
||||
},
|
||||
y: {
|
||||
c: 7,
|
||||
d: 28
|
||||
},
|
||||
z: {
|
||||
e: 6,
|
||||
f: 2
|
||||
}
|
||||
};
|
||||
|
||||
// Load the module, set a value.
|
||||
let loader = Loader(module);
|
||||
let ss = loader.require("sdk/simple-storage");
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.ok(file.exists(storeFilename), "Store file should exist");
|
||||
|
||||
// Load the module again and check the result
|
||||
loader = Loader(module);
|
||||
ss = loader.require("sdk/simple-storage");
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
|
||||
// Add a property
|
||||
ss.storage.x.g = {foo: "bar"};
|
||||
expected.x.g = {foo: "bar"};
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
|
||||
// Modify a property
|
||||
ss.storage.y.c = 42;
|
||||
expected.y.c = 42;
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
|
||||
// Delete a property
|
||||
delete ss.storage.z.f;
|
||||
delete expected.z.f;
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
|
||||
// Modify the new inner-object
|
||||
ss.storage.x.g.foo = "baz";
|
||||
expected.x.g.foo = "baz";
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.fail("Nothing should be written since `storage` was not changed.");
|
||||
};
|
||||
loader.unload();
|
||||
|
||||
// Load the module again and check the result
|
||||
loader = Loader(module);
|
||||
ss = loader.require("sdk/simple-storage");
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
loader.unload();
|
||||
file.remove(storeFilename);
|
||||
prefs.reset(WRITE_PERIOD_PREF);
|
||||
done();
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
ss.storage = {
|
||||
x: {
|
||||
a: 5,
|
||||
b: 7
|
||||
},
|
||||
y: {
|
||||
c: 7,
|
||||
d: 28
|
||||
},
|
||||
z: {
|
||||
e: 6,
|
||||
f: 2
|
||||
}
|
||||
};
|
||||
assert.equal(JSON.stringify(ss.storage),
|
||||
JSON.stringify(expected), "Should see the expected object");
|
||||
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports.testSetNoSetRead = function (assert, done) {
|
||||
// Load the module, set a value.
|
||||
let loader = Loader(module);
|
||||
@ -250,12 +453,13 @@ exports.testSetNoSetRead = function (assert, done) {
|
||||
// Load the module a third time and make sure the value stuck.
|
||||
loader = Loader(module);
|
||||
ss = loader.require("sdk/simple-storage");
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
file.remove(storeFilename);
|
||||
done();
|
||||
};
|
||||
assert.equal(ss.storage.foo, val, "Value should persist");
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.fail("Nothing should be written since `storage` was not changed.");
|
||||
};
|
||||
loader.unload();
|
||||
file.remove(storeFilename);
|
||||
done();
|
||||
};
|
||||
let val = "foo";
|
||||
ss.storage.foo = val;
|
||||
@ -276,12 +480,13 @@ function setGetRoot(assert, done, val, compare) {
|
||||
// Load the module again and make sure the value stuck.
|
||||
loader = Loader(module);
|
||||
ss = loader.require("sdk/simple-storage");
|
||||
manager(loader).jsonStore.onWrite = function () {
|
||||
file.remove(storeFilename);
|
||||
done();
|
||||
};
|
||||
assert.ok(compare(ss.storage, val), "Value should persist");
|
||||
manager(loader).jsonStore.onWrite = function (storage) {
|
||||
assert.fail("Nothing should be written since `storage` was not changed.");
|
||||
};
|
||||
loader.unload();
|
||||
file.remove(storeFilename);
|
||||
done();
|
||||
};
|
||||
ss.storage = val;
|
||||
assert.ok(compare(ss.storage, val), "Value read should be value set");
|
||||
|
@ -2,13 +2,15 @@
|
||||
* 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 tabs = require("sdk/tabs"); // From addon-kit
|
||||
const windowUtils = require("sdk/deprecated/window-utils");
|
||||
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 { modelFor } = require("sdk/model/core");
|
||||
const { getTabId, isTab } = require("sdk/tabs/utils");
|
||||
const { defer } = require("sdk/lang/functional");
|
||||
|
||||
// The primary test tab
|
||||
@ -152,6 +154,22 @@ exports["test viewFor(tab)"] = (assert, done) => {
|
||||
}));
|
||||
|
||||
tabs.open({ url: "about:mozilla" });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports["test modelFor(xulTab)"] = (assert, done) => {
|
||||
tabs.open({
|
||||
url: "about:mozilla",
|
||||
onReady: tab => {
|
||||
const view = viewFor(tab);
|
||||
assert.ok(view, "view is returned");
|
||||
assert.ok(isTab(view), "view is underlaying tab");
|
||||
assert.equal(getTabId(view), tab.id, "tab has a same id");
|
||||
assert.equal(modelFor(view), tab, "modelFor(view) is SDK tab");
|
||||
|
||||
tab.close(defer(done));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
require("test").run(exports);
|
||||
|
@ -48,12 +48,12 @@ exports["test LoaderWithHookedConsole"] = function (assert) {
|
||||
console.debug("5th");
|
||||
console.exception("6th");
|
||||
assert.equal(messages.length, 6, "Got all console messages");
|
||||
assert.deepEqual(messages[0], {type: "log", msg: "1st"}, "Got log");
|
||||
assert.deepEqual(messages[1], {type: "error", msg: "2nd"}, "Got error");
|
||||
assert.deepEqual(messages[2], {type: "warn", msg: "3rd"}, "Got warn");
|
||||
assert.deepEqual(messages[3], {type: "info", msg: "4th"}, "Got info");
|
||||
assert.deepEqual(messages[4], {type: "debug", msg: "5th"}, "Got debug");
|
||||
assert.deepEqual(messages[5], {type: "exception", msg: "6th"}, "Got exception");
|
||||
assert.deepEqual(messages[0], {type: "log", msg: "1st", innerID: null}, "Got log");
|
||||
assert.deepEqual(messages[1], {type: "error", msg: "2nd", innerID: null}, "Got error");
|
||||
assert.deepEqual(messages[2], {type: "warn", msg: "3rd", innerID: null}, "Got warn");
|
||||
assert.deepEqual(messages[3], {type: "info", msg: "4th", innerID: null}, "Got info");
|
||||
assert.deepEqual(messages[4], {type: "debug", msg: "5th", innerID: null}, "Got debug");
|
||||
assert.deepEqual(messages[5], {type: "exception", msg: "6th", innerID: null}, "Got exception");
|
||||
assert.equal(count, 6, "Called for all messages");
|
||||
};
|
||||
|
||||
|
@ -48,9 +48,14 @@ exports.testFromExceptionWithString = function(assert) {
|
||||
try {
|
||||
throw "foob";
|
||||
assert.fail("an exception should've been thrown");
|
||||
} catch (e if e == "foob") {
|
||||
var tb = traceback.fromException(e);
|
||||
assert.equal(tb.length, 0);
|
||||
} catch (e) {
|
||||
if (e == "foob") {
|
||||
var tb = traceback.fromException(e);
|
||||
assert.equal(tb.length, 0);
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -65,11 +70,16 @@ exports.testFromExceptionWithError = function(assert) {
|
||||
try {
|
||||
throwError();
|
||||
assert.fail("an exception should've been thrown");
|
||||
} catch (e if e instanceof Error) {
|
||||
var tb = traceback.fromException(e);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
var tb = traceback.fromException(e);
|
||||
|
||||
var xulApp = require("sdk/system/xul-app");
|
||||
assert.equal(tb.slice(-1)[0].name, "throwError");
|
||||
var xulApp = require("sdk/system/xul-app");
|
||||
assert.equal(tb.slice(-1)[0].name, "throwError");
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -77,9 +87,14 @@ exports.testFromExceptionWithNsIException = function(assert) {
|
||||
try {
|
||||
throwNsIException();
|
||||
assert.fail("an exception should've been thrown");
|
||||
} catch (e if e.result == Cr.NS_ERROR_MALFORMED_URI) {
|
||||
var tb = traceback.fromException(e);
|
||||
assert.equal(tb[tb.length - 1].name, "throwNsIException");
|
||||
} catch (e) {
|
||||
if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
|
||||
var tb = traceback.fromException(e);
|
||||
assert.equal(tb[tb.length - 1].name, "throwNsIException");
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@ module.metadata = {
|
||||
}
|
||||
};
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
const { Frame } = require("sdk/ui/frame");
|
||||
const { Toolbar } = require("sdk/ui/toolbar");
|
||||
const { Loader } = require("sdk/test/loader");
|
||||
@ -16,15 +17,29 @@ const { identify } = require("sdk/ui/id");
|
||||
const { setTimeout } = require("sdk/timers");
|
||||
const { getMostRecentBrowserWindow, open } = require("sdk/window/utils");
|
||||
const { ready, loaded, close } = require("sdk/window/helpers");
|
||||
const { defer } = require("sdk/core/promise");
|
||||
const { defer, all } = require("sdk/core/promise");
|
||||
const { send } = require("sdk/event/utils");
|
||||
const { object } = require("sdk/util/sequence");
|
||||
const { OutputPort } = require("sdk/output/system");
|
||||
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
const output = new OutputPort({ id: "toolbar-change" });
|
||||
|
||||
const wait = (toolbar, event) => {
|
||||
const wait = (toolbar, event, times) => {
|
||||
let { promise, resolve } = defer();
|
||||
toolbar.once(event, resolve);
|
||||
if (times) {
|
||||
let resolveArray = [];
|
||||
let counter = 0;
|
||||
toolbar.on(event, function onEvent (e) {
|
||||
resolveArray.push(e);
|
||||
if (++counter === times) {
|
||||
toolbar.off(event, onEvent);
|
||||
resolve(resolveArray);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
toolbar.once(event, resolve);
|
||||
}
|
||||
return promise;
|
||||
};
|
||||
|
||||
@ -36,7 +51,9 @@ const stateEventsFor = frame =>
|
||||
const isAttached = ({id}, window=getMostRecentBrowserWindow()) =>
|
||||
!!window.document.getElementById(id);
|
||||
|
||||
exports["test frame API"] = function*(assert) {
|
||||
// Use `Task.spawn` instead of `Task.async` because the returned function does not contain
|
||||
// a length for the test harness to determine whether the test should be executed
|
||||
exports["test frame API"] = function* (assert) {
|
||||
const url = "data:text/html,frame-api";
|
||||
assert.throws(() => new Frame(),
|
||||
/The `options.url`/,
|
||||
@ -86,8 +103,7 @@ exports["test frame API"] = function*(assert) {
|
||||
f3.destroy();
|
||||
};
|
||||
|
||||
|
||||
exports["test frame in toolbar"] = function*(assert) {
|
||||
exports["test frame in toolbar"] = function* (assert) {
|
||||
const assertEvent = (event, type) => {
|
||||
assert.ok(event, "`" + type + "` event was dispatched");
|
||||
assert.equal(event.type, type, "event.type is: " + type);
|
||||
@ -116,7 +132,6 @@ exports["test frame in toolbar"] = function*(assert) {
|
||||
const [a2, r2, l2] = stateEventsFor(f1);
|
||||
const w2 = open();
|
||||
|
||||
|
||||
assertEvent((yield a2), "attach");
|
||||
assert.ok(isAttached(f1, w2), "frame is in the window#2");
|
||||
assertEvent((yield r2), "ready");
|
||||
@ -126,7 +141,6 @@ exports["test frame in toolbar"] = function*(assert) {
|
||||
|
||||
const d1 = wait(f1, "detach");
|
||||
yield close(w2);
|
||||
|
||||
assertEvent((yield d1), "detach");
|
||||
assert.pass("frame detached when window is closed");
|
||||
|
||||
@ -138,7 +152,7 @@ exports["test frame in toolbar"] = function*(assert) {
|
||||
};
|
||||
|
||||
|
||||
exports["test host to content messaging"] = function*(assert) {
|
||||
exports["test host to content messaging"] = function* (assert) {
|
||||
const url = "data:text/html,<script>new " + function() {
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data === "ping!")
|
||||
@ -159,7 +173,7 @@ exports["test host to content messaging"] = function*(assert) {
|
||||
};
|
||||
|
||||
|
||||
exports["test content to host messaging"] = function*(assert) {
|
||||
exports["test content to host messaging"] = function* (assert) {
|
||||
const url = "data:text/html,<script>new " + function() {
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data === "pong!")
|
||||
@ -182,10 +196,11 @@ exports["test content to host messaging"] = function*(assert) {
|
||||
|
||||
t1.destroy();
|
||||
yield wait(t1, "detach");
|
||||
|
||||
};
|
||||
|
||||
|
||||
exports["test direct messaging"] = function*(assert) {
|
||||
exports["test direct messaging"] = function* (assert) {
|
||||
const url = "data:text/html,<script>new " + function() {
|
||||
var n = 0;
|
||||
window.addEventListener("message", (event) => {
|
||||
@ -208,31 +223,30 @@ exports["test direct messaging"] = function*(assert) {
|
||||
yield wait(f1, "ready");
|
||||
assert.pass("document loaded in window#2");
|
||||
|
||||
let messages = wait(f1, "message", 2);
|
||||
f1.postMessage("inc", f1.origin);
|
||||
f1.postMessage("print", f1.origin);
|
||||
|
||||
const e1 = yield wait(f1, "message");
|
||||
const [e1, e2] = yield messages;
|
||||
assert.deepEqual(e1.data, {n: 1}, "received message from window#1");
|
||||
|
||||
const e2 = yield wait(f1, "message");
|
||||
assert.deepEqual(e2.data, {n: 1}, "received message from window#2");
|
||||
|
||||
let message = wait(f1, "message");
|
||||
e1.source.postMessage("inc", e1.origin);
|
||||
|
||||
e1.source.postMessage("print", e1.origin);
|
||||
|
||||
const e3 = yield wait(f1, "message");
|
||||
const e3 = yield message;
|
||||
assert.deepEqual(e3.data, {n: 2}, "state changed in window#1");
|
||||
|
||||
let message = wait(f1, "message");
|
||||
e2.source.postMessage("print", e2.origin);
|
||||
const e4 = yield wait(f1, "message");
|
||||
|
||||
yield message;
|
||||
assert.deepEqual(e2.data, {n:1}, "window#2 didn't received inc message");
|
||||
|
||||
yield close(w2);
|
||||
t1.destroy();
|
||||
|
||||
yield wait(t1, "detach");
|
||||
|
||||
};
|
||||
|
||||
require("sdk/test").run(exports);
|
||||
|
@ -829,7 +829,7 @@ exports.testShowingInOneWindowDoesNotAffectOtherWindows = function(assert, done)
|
||||
}, assert.fail);
|
||||
}
|
||||
|
||||
exports.testHidingAHiddenSidebarRejects = function(assert) {
|
||||
exports.testHidingAHiddenSidebarRejects = function(assert, done) {
|
||||
const { Sidebar } = require('sdk/ui/sidebar');
|
||||
let testName = 'testHidingAHiddenSidebarRejects';
|
||||
let url = 'data:text/html;charset=utf-8,'+testName;
|
||||
@ -1385,7 +1385,7 @@ exports.testEventListeners = function(assert, done) {
|
||||
onHide.resolve();
|
||||
});
|
||||
|
||||
all(constructorOnShow.promise,
|
||||
all([constructorOnShow.promise,
|
||||
constructorOnAttach.promise,
|
||||
constructorOnReady.promise,
|
||||
constructorOnHide.promise,
|
||||
@ -1396,7 +1396,7 @@ exports.testEventListeners = function(assert, done) {
|
||||
onShow.promise,
|
||||
onAttach.promise,
|
||||
onReady.promise,
|
||||
onHide.promise).then(function() {
|
||||
onHide.promise]).then(function() {
|
||||
assert.equal(eventListenerOrder.join(), [
|
||||
'onAttach',
|
||||
'once attach',
|
||||
@ -1436,31 +1436,22 @@ exports.testAttachDoesNotEmitWhenShown = function(assert, done) {
|
||||
}
|
||||
|
||||
if (++count == 1) {
|
||||
setTimeout(function() {
|
||||
let shown = false;
|
||||
let endShownTest = false;
|
||||
sidebar.once('show', function() {
|
||||
assert.pass('shown was emitted');
|
||||
shown = !endShownTest && true;
|
||||
});
|
||||
setImmediate(function() {
|
||||
let shownFired = 0;
|
||||
let onShow = () => shownFired++;
|
||||
sidebar.on('show', onShow);
|
||||
|
||||
sidebar.show().then(function() {
|
||||
assert.pass('calling hide');
|
||||
sidebar.hide();
|
||||
}).then(function() {
|
||||
endShownTest = true;
|
||||
|
||||
setTimeout(function() {
|
||||
sidebar.show().then(function() {
|
||||
assert.ok(!shown, 'show did not emit');
|
||||
|
||||
sidebar.hide().then(function() {
|
||||
sidebar.destroy();
|
||||
done();
|
||||
}).then(null, assert.fail);
|
||||
})
|
||||
})
|
||||
}).then(null, assert.fail);
|
||||
sidebar.show()
|
||||
.then(() => assert.equal(shownFired, 0, 'shown should not be fired again when already showing from after attach'))
|
||||
.then(sidebar.hide.bind(sidebar))
|
||||
.then(sidebar.show.bind(sidebar))
|
||||
.then(() => assert.equal(shownFired, 1, 'shown was emitted when `show` called after being hidden'))
|
||||
.then(sidebar.show.bind(sidebar))
|
||||
.then(() => {
|
||||
assert.equal(shownFired, 1, 'shown was not emitted again if already being shown');
|
||||
sidebar.off('show', onShow);
|
||||
sidebar.destroy();
|
||||
}).catch(assert.fail).then(done);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,10 @@
|
||||
|
||||
const { Loader } = require('sdk/test/loader');
|
||||
const { browserWindows } = require('sdk/windows');
|
||||
const { viewFor } = require('sdk/view/core');
|
||||
const { modelFor } = require('sdk/model/core');
|
||||
const { Ci } = require("chrome");
|
||||
const { isBrowser, getWindowTitle } = require("sdk/window/utils");
|
||||
|
||||
// TEST: browserWindows Iterator
|
||||
exports.testBrowserWindowsIterator = function(assert) {
|
||||
@ -57,4 +60,37 @@ 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);
|
||||
|
||||
assert.ok(view instanceof Ci.nsIDOMWindow, "view is a window");
|
||||
assert.ok(isBrowser(view), "view is a browser window");
|
||||
assert.equal(getWindowTitle(view), window.title,
|
||||
"window has a right title");
|
||||
|
||||
window.close(done);
|
||||
});
|
||||
|
||||
|
||||
browserWindows.open({ url: "data:text/html;charset=utf-8,<title>yo</title>" });
|
||||
};
|
||||
|
||||
|
||||
exports["test modelFor(window)"] = function(assert, done) {
|
||||
browserWindows.once("open", window => {
|
||||
const view = viewFor(window);
|
||||
|
||||
assert.ok(view instanceof Ci.nsIDOMWindow, "view is a window");
|
||||
assert.ok(isBrowser(view), "view is a browser window");
|
||||
assert.ok(modelFor(view) === window, "modelFor(browserWindow) is SDK window");
|
||||
|
||||
window.close(done);
|
||||
});
|
||||
|
||||
|
||||
browserWindows.open({ url: "data:text/html;charset=utf-8,<title>yo</title>" });
|
||||
};
|
||||
|
||||
require('sdk/test').run(exports);
|
||||
|
@ -290,6 +290,11 @@ toolbarpaletteitem > #personal-bookmarks > #bookmarks-toolbar-placeholder,
|
||||
display: -moz-box;
|
||||
}
|
||||
|
||||
toolbar:not(#TabsToolbar) > #wrapper-personal-bookmarks,
|
||||
toolbar:not(#TabsToolbar) > #personal-bookmarks {
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
|
||||
#zoom-controls[cui-areatype="toolbar"]:not([overflowedItem=true]) > #zoom-reset-button > .toolbarbutton-text {
|
||||
display: -moz-box;
|
||||
}
|
||||
|
@ -944,7 +944,6 @@
|
||||
collapsed="true"
|
||||
customizable="true">
|
||||
<toolbaritem id="personal-bookmarks"
|
||||
flex="1"
|
||||
title="&bookmarksToolbarItem.label;"
|
||||
cui-areatype="toolbar"
|
||||
removable="true">
|
||||
|
@ -198,7 +198,7 @@ let gGrid = {
|
||||
|
||||
let availSpace = document.documentElement.clientHeight - this._cellMargin -
|
||||
document.querySelector("#newtab-margin-undo-container").offsetHeight -
|
||||
document.querySelector("#newtab-search-form").offsetHeight;
|
||||
document.querySelector("#newtab-search-container").offsetHeight;
|
||||
let visibleRows = Math.floor(availSpace / this._cellHeight);
|
||||
this._node.style.height = this._computeHeight() + "px";
|
||||
this._node.style.maxHeight = this._computeHeight(visibleRows) + "px";
|
||||
|
@ -28,6 +28,7 @@ input[type=button] {
|
||||
#newtab-undo-container {
|
||||
transition: opacity 100ms ease-out;
|
||||
display: -moz-box;
|
||||
margin-bottom: 26px; /* 32 - 6 search form top "padding" */
|
||||
-moz-box-align: center;
|
||||
-moz-box-pack: center;
|
||||
}
|
||||
@ -60,7 +61,6 @@ input[type=button] {
|
||||
#newtab-margin-undo-container {
|
||||
display: -moz-box;
|
||||
-moz-box-pack: center;
|
||||
margin-bottom: 26px; /* 32 - 6 search form top "padding" */
|
||||
}
|
||||
|
||||
#newtab-horizontal-margin {
|
||||
|
@ -19,6 +19,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Rect",
|
||||
"resource://gre/modules/Geometry.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
|
||||
"resource://gre/modules/UpdateChannel.jsm");
|
||||
|
||||
let {
|
||||
links: gLinks,
|
||||
|
@ -20,7 +20,8 @@
|
||||
title="&newtab.pageTitle;">
|
||||
|
||||
<xul:panel id="sponsored-panel" orient="vertical" type="arrow">
|
||||
<xul:description>&newtab.panel.message;</xul:description>
|
||||
<xul:description id="sponsored-panel-release-descr">&newtab.sponsored.release.message;</xul:description>
|
||||
<xul:description id="sponsored-panel-trial-descr">&newtab.sponsored.trial.message;</xul:description>
|
||||
<xul:label class="text-link"
|
||||
href="https://support.mozilla.org/kb/how-do-sponsored-tiles-work"
|
||||
value="&newtab.panel.link.text;" />
|
||||
|
@ -28,6 +28,12 @@ let gPage = {
|
||||
this._sponsoredPanel = document.getElementById("sponsored-panel");
|
||||
let link = this._sponsoredPanel.querySelector(".text-link");
|
||||
link.addEventListener("click", () => this._sponsoredPanel.hidePopup());
|
||||
if (UpdateChannel.get().startsWith("release")) {
|
||||
document.getElementById("sponsored-panel-trial-descr").style.display = "none";
|
||||
}
|
||||
else {
|
||||
document.getElementById("sponsored-panel-release-descr").style.display = "none";
|
||||
}
|
||||
|
||||
// Check if the new tab feature is enabled.
|
||||
let enabled = gAllPages.enabled;
|
||||
|
@ -165,6 +165,15 @@ let CustomizableUIInternal = {
|
||||
panelPlacements.push("switch-to-metro-button");
|
||||
}
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
if (gPalette.has("e10s-button")) {
|
||||
let newWindowIndex = panelPlacements.indexOf("new-window-button");
|
||||
if (newWindowIndex > -1) {
|
||||
panelPlacements.splice(newWindowIndex + 1, 0, "e10s-button");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
let showCharacterEncoding = Services.prefs.getComplexValue(
|
||||
"browser.menu.showCharacterEncoding",
|
||||
Ci.nsIPrefLocalizedString
|
||||
|
@ -912,3 +912,36 @@ if (Services.metro && Services.metro.supported) {
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
/**
|
||||
* The e10s button's purpose is to lower the barrier of entry
|
||||
* for our Nightly testers to use e10s windows. We'll be removing it
|
||||
* once remote tabs are enabled. This button should never ever make it
|
||||
* to production. If it does, that'd be bad, and we should all feel bad.
|
||||
*/
|
||||
if (Services.prefs.getBoolPref("browser.tabs.remote")) {
|
||||
let getCommandFunction = function(aOpenRemote) {
|
||||
return function(aEvent) {
|
||||
let win = aEvent.view;
|
||||
if (win && typeof win.OpenBrowserWindow == "function") {
|
||||
win.OpenBrowserWindow({remote: aOpenRemote});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let openRemote = !Services.prefs.getBoolPref("browser.tabs.remote.autostart");
|
||||
// Like the XUL menuitem counterparts, we hard-code these strings in because
|
||||
// this button should never roll into production.
|
||||
let buttonLabel = openRemote ? "New e10s Window"
|
||||
: "New Non-e10s Window";
|
||||
|
||||
CustomizableWidgets.push({
|
||||
id: "e10s-button",
|
||||
label: buttonLabel,
|
||||
tooltiptext: buttonLabel,
|
||||
defaultArea: CustomizableUI.AREA_PANEL,
|
||||
onCommand: getCommandFunction(openRemote),
|
||||
});
|
||||
}
|
||||
#endif
|
@ -65,19 +65,6 @@ skip-if = e10s # Bug ?????? - test uses promiseTabLoadEvent() which isn't e10s f
|
||||
[browser_943683_migration_test.js]
|
||||
[browser_944887_destroyWidget_should_destroy_in_palette.js]
|
||||
[browser_945739_showInPrivateBrowsing_customize_mode.js]
|
||||
[browser_947914_button_addons.js]
|
||||
[browser_947914_button_copy.js]
|
||||
[browser_947914_button_cut.js]
|
||||
[browser_947914_button_find.js]
|
||||
[browser_947914_button_history.js]
|
||||
[browser_947914_button_newPrivateWindow.js]
|
||||
[browser_947914_button_newWindow.js]
|
||||
[browser_947914_button_paste.js]
|
||||
[browser_947914_button_print.js]
|
||||
[browser_947914_button_savePage.js]
|
||||
[browser_947914_button_zoomIn.js]
|
||||
[browser_947914_button_zoomOut.js]
|
||||
[browser_947914_button_zoomReset.js]
|
||||
[browser_947987_removable_default.js]
|
||||
[browser_948985_non_removable_defaultArea.js]
|
||||
[browser_952963_areaType_getter_no_area.js]
|
||||
|
@ -1,34 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function() {
|
||||
info("Check addons button existence and functionality");
|
||||
|
||||
let initialLocation = gBrowser.currentURI.spec;
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
let addonsButton = document.getElementById("add-ons-button");
|
||||
ok(addonsButton, "Add-ons button exists in Panel Menu");
|
||||
addonsButton.click();
|
||||
|
||||
yield waitForCondition(function() gBrowser.currentURI &&
|
||||
gBrowser.currentURI.spec == "about:addons");
|
||||
|
||||
let addonsPage = gBrowser.selectedBrowser.contentWindow.document.
|
||||
getElementById("addons-page");
|
||||
ok(addonsPage, "Add-ons page was opened");
|
||||
|
||||
// close the add-ons tab
|
||||
if(gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
}
|
||||
else {
|
||||
var tabToRemove = gBrowser.selectedTab;
|
||||
gBrowser.addTab(initialLocation);
|
||||
gBrowser.removeTab(tabToRemove);
|
||||
}
|
||||
});
|
@ -1,51 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function() {
|
||||
info("Check copy button existence and functionality");
|
||||
|
||||
var testText = "copy text test";
|
||||
let initialLocation = gBrowser.currentURI.spec;
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
let copyButton = document.getElementById("copy-button");
|
||||
ok(copyButton, "Copy button exists in Panel Menu");
|
||||
is(copyButton.getAttribute("disabled"), "true", "Copy button is initially disabled");
|
||||
|
||||
// copy text from URL bar
|
||||
gURLBar.value = testText;
|
||||
gURLBar.focus();
|
||||
gURLBar.select();
|
||||
yield PanelUI.show();
|
||||
|
||||
ok(!copyButton.hasAttribute("disabled"), "Copy button gets enabled");
|
||||
|
||||
copyButton.click();
|
||||
is(gURLBar.value, testText, "Selected text is unaltered when clicking copy");
|
||||
|
||||
// check that the text was added to the clipboard
|
||||
let clipboard = Services.clipboard;
|
||||
let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
|
||||
const globalClipboard = clipboard.kGlobalClipboard;
|
||||
|
||||
transferable.init(null);
|
||||
transferable.addDataFlavor("text/unicode");
|
||||
clipboard.getData(transferable, globalClipboard);
|
||||
let str = {}, strLength = {};
|
||||
transferable.getTransferData("text/unicode", str, strLength);
|
||||
let clipboardValue = "";
|
||||
|
||||
if (str.value) {
|
||||
str.value.QueryInterface(Ci.nsISupportsString);
|
||||
clipboardValue = str.value.data;
|
||||
}
|
||||
is(clipboardValue, testText, "Data was copied to the clipboard.");
|
||||
|
||||
// restore the tab location and clear the clipboard
|
||||
Services.clipboard.emptyClipboard(globalClipboard);
|
||||
gURLBar.value = initialLocation;
|
||||
});
|
@ -1,50 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function() {
|
||||
info("Check cut button existence and functionality");
|
||||
|
||||
var testText = "cut text test";
|
||||
let initialLocation = gBrowser.currentURI.spec;
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
let cutButton = document.getElementById("cut-button");
|
||||
ok(cutButton, "Cut button exists in Panel Menu");
|
||||
ok(cutButton.getAttribute("disabled"), "Cut button is disabled");
|
||||
|
||||
// cut text from URL bar
|
||||
gURLBar.value = testText;
|
||||
gURLBar.focus();
|
||||
gURLBar.select();
|
||||
yield PanelUI.show();
|
||||
|
||||
ok(!cutButton.hasAttribute("disabled"), "Cut button gets enabled");
|
||||
cutButton.click();
|
||||
is(gURLBar.value, "", "Selected text is removed from source when clicking on cut");
|
||||
|
||||
// check that the text was added to the clipboard
|
||||
let clipboard = Services.clipboard;
|
||||
let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
|
||||
const globalClipboard = clipboard.kGlobalClipboard;
|
||||
|
||||
transferable.init(null);
|
||||
transferable.addDataFlavor("text/unicode");
|
||||
clipboard.getData(transferable, globalClipboard);
|
||||
let str = {}, strLength = {};
|
||||
transferable.getTransferData("text/unicode", str, strLength);
|
||||
let clipboardValue = "";
|
||||
|
||||
if (str.value) {
|
||||
str.value.QueryInterface(Ci.nsISupportsString);
|
||||
clipboardValue = str.value.data;
|
||||
}
|
||||
is(clipboardValue, testText, "Data was copied to the clipboard.");
|
||||
|
||||
// restore the tab location and clear the clipboard
|
||||
gBrowser.value = initialLocation;
|
||||
Services.clipboard.emptyClipboard(globalClipboard);
|
||||
});
|
@ -1,20 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function() {
|
||||
info("Check find button existence and functionality");
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
let findButton = document.getElementById("find-button");
|
||||
ok(findButton, "Find button exists in Panel Menu");
|
||||
|
||||
findButton.click();
|
||||
ok(!gFindBar.hasAttribute("hidden"), "Findbar opened successfully");
|
||||
|
||||
// close find bar
|
||||
gFindBar.close();
|
||||
});
|
@ -1,22 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function() {
|
||||
info("Check history button existence and functionality");
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
let historyButton = document.getElementById("history-panelmenu");
|
||||
ok(historyButton, "History button appears in Panel Menu");
|
||||
|
||||
historyButton.click();
|
||||
let historyPanel = document.getElementById("PanelUI-history");
|
||||
ok(historyPanel.getAttribute("current"), "History Panel is in view");
|
||||
|
||||
let panelHiddenPromise = promisePanelHidden(window);
|
||||
PanelUI.hide();
|
||||
yield panelHiddenPromise
|
||||
});
|
@ -1,45 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function() {
|
||||
info("Check private browsing button existence and functionality");
|
||||
yield PanelUI.show();
|
||||
|
||||
var windowWasHandled = false;
|
||||
let privateWindow = null;
|
||||
|
||||
let observerWindowOpened = {
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic == "domwindowopened") {
|
||||
privateWindow = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
|
||||
privateWindow.addEventListener("load", function newWindowHandler() {
|
||||
privateWindow.removeEventListener("load", newWindowHandler, false);
|
||||
is(privateWindow.location.href, "chrome://browser/content/browser.xul",
|
||||
"A new browser window was opened");
|
||||
ok(PrivateBrowsingUtils.isWindowPrivate(privateWindow), "Window is private");
|
||||
windowWasHandled = true;
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Services.ww.registerNotification(observerWindowOpened);
|
||||
|
||||
let privateBrowsingButton = document.getElementById("privatebrowsing-button");
|
||||
ok(privateBrowsingButton, "Private browsing button exists in Panel Menu");
|
||||
privateBrowsingButton.click();
|
||||
|
||||
try{
|
||||
yield waitForCondition(() => windowWasHandled);
|
||||
yield promiseWindowClosed(privateWindow);
|
||||
}
|
||||
catch(e) {
|
||||
ok(false, "The new private browser window was not properly handled");
|
||||
}
|
||||
finally {
|
||||
Services.ww.unregisterNotification(observerWindowOpened);
|
||||
}
|
||||
});
|
@ -1,45 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function() {
|
||||
info("Check new window button existence and functionality");
|
||||
yield PanelUI.show();
|
||||
|
||||
var windowWasHandled = false;
|
||||
var newWindow = null;
|
||||
|
||||
let observerWindowOpened = {
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic == "domwindowopened") {
|
||||
newWindow = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
|
||||
newWindow.addEventListener("load", function newWindowHandler() {
|
||||
newWindow.removeEventListener("load", newWindowHandler, false);
|
||||
is(newWindow.location.href, "chrome://browser/content/browser.xul",
|
||||
"A new browser window was opened");
|
||||
ok(!PrivateBrowsingUtils.isWindowPrivate(newWindow), "Window is not private");
|
||||
windowWasHandled = true;
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Services.ww.registerNotification(observerWindowOpened);
|
||||
|
||||
let newWindowButton = document.getElementById("new-window-button");
|
||||
ok(newWindowButton, "New Window button exists in Panel Menu");
|
||||
newWindowButton.click();
|
||||
|
||||
try{
|
||||
yield waitForCondition(() => windowWasHandled);
|
||||
yield promiseWindowClosed(newWindow);
|
||||
}
|
||||
catch(e) {
|
||||
ok(false, "The new browser window was not properly handled");
|
||||
}
|
||||
finally {
|
||||
Services.ww.unregisterNotification(observerWindowOpened);
|
||||
}
|
||||
});
|
@ -1,35 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function() {
|
||||
info("Check paste button existence and functionality");
|
||||
|
||||
let initialLocation = gBrowser.currentURI.spec;
|
||||
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
|
||||
const globalClipboard = Services.clipboard.kGlobalClipboard;
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
let pasteButton = document.getElementById("paste-button");
|
||||
ok(pasteButton, "Paste button exists in Panel Menu");
|
||||
|
||||
// add text to clipboard
|
||||
var text = "Sample text for testing";
|
||||
clipboard.copyString(text);
|
||||
|
||||
// test paste button by pasting text to URL bar
|
||||
gURLBar.focus();
|
||||
yield PanelUI.show();
|
||||
|
||||
ok(!pasteButton.hasAttribute("disabled"), "Paste button is enabled");
|
||||
pasteButton.click();
|
||||
|
||||
is(gURLBar.value, text, "Text pasted successfully");
|
||||
|
||||
// clear the clipboard and restore the tab location as it was at the begining of the test
|
||||
Services.clipboard.emptyClipboard(globalClipboard);
|
||||
gURLBar.value = initialLocation;
|
||||
});
|
@ -1,32 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const isOSX = (Services.appinfo.OS === "Darwin");
|
||||
|
||||
add_task(function() {
|
||||
info("Check print button existence and functionality");
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
let printButton = document.getElementById("print-button");
|
||||
ok(printButton, "Print button exists in Panel Menu");
|
||||
|
||||
if(isOSX) {
|
||||
let panelHiddenPromise = promisePanelHidden(window);
|
||||
PanelUI.hide();
|
||||
yield panelHiddenPromise;
|
||||
}
|
||||
else {
|
||||
printButton.click();
|
||||
yield waitForCondition(() => window.gInPrintPreviewMode);
|
||||
|
||||
ok(window.gInPrintPreviewMode, "Entered print preview mode");
|
||||
|
||||
// close print preview
|
||||
PrintUtils.exitPrintPreview();
|
||||
yield waitForCondition(() => !window.gInPrintPreviewMode);
|
||||
}
|
||||
});
|
@ -1,18 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function() {
|
||||
info("Check save page button existence");
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
let savePageButton = document.getElementById("save-page-button");
|
||||
ok(savePageButton, "Save Page button exists in Panel Menu");
|
||||
|
||||
let panelHiddenPromise = promisePanelHidden(window);
|
||||
PanelUI.hide();
|
||||
yield panelHiddenPromise;
|
||||
});
|
@ -1,31 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function() {
|
||||
info("Check zoom in button existence and functionality");
|
||||
|
||||
let initialPageZoom = ZoomManager.zoom;
|
||||
is(initialPageZoom, 1, "Initial zoom factor should be 1");
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
let zoomInButton = document.getElementById("zoom-in-button");
|
||||
ok(zoomInButton, "Zoom in button exists in Panel Menu");
|
||||
|
||||
zoomInButton.click();
|
||||
let pageZoomLevel = parseInt(ZoomManager.zoom * 100);
|
||||
let zoomResetButton = document.getElementById("zoom-reset-button");
|
||||
let expectedZoomLevel = parseInt(zoomResetButton.getAttribute("label"), 10);
|
||||
ok(pageZoomLevel > 100 && pageZoomLevel == expectedZoomLevel, "Page zoomed in correctly");
|
||||
|
||||
// close the Panel
|
||||
let panelHiddenPromise = promisePanelHidden(window);
|
||||
PanelUI.hide();
|
||||
yield panelHiddenPromise;
|
||||
|
||||
// reset zoom level
|
||||
ZoomManager.zoom = initialPageZoom;
|
||||
});
|
@ -1,32 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function() {
|
||||
info("Check zoom out button existence and functionality");
|
||||
|
||||
let initialPageZoom = ZoomManager.zoom;
|
||||
is(initialPageZoom, 1, "Initial zoom factor should be 1");
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
let zoomOutButton = document.getElementById("zoom-out-button");
|
||||
ok(zoomOutButton, "Zoom out button exists in Panel Menu");
|
||||
|
||||
zoomOutButton.click();
|
||||
let pageZoomLevel = Math.round(ZoomManager.zoom*100);
|
||||
|
||||
let zoomResetButton = document.getElementById("zoom-reset-button");
|
||||
let expectedZoomLevel = parseInt(zoomResetButton.getAttribute("label"), 10);
|
||||
ok(pageZoomLevel < 100 && pageZoomLevel == expectedZoomLevel, "Page zoomed out correctly");
|
||||
|
||||
// close the panel
|
||||
let panelHiddenPromise = promisePanelHidden(window);
|
||||
PanelUI.hide();
|
||||
yield panelHiddenPromise;
|
||||
|
||||
// reset zoom level
|
||||
ZoomManager.zoom = initialPageZoom;
|
||||
});
|
@ -1,30 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function() {
|
||||
info("Check zoom reset button existence and functionality");
|
||||
|
||||
let initialPageZoom = ZoomManager.zoom;
|
||||
is(initialPageZoom, 1, "Initial zoom factor should be 1");
|
||||
ZoomManager.zoom = 0.5;
|
||||
yield PanelUI.show();
|
||||
|
||||
let zoomResetButton = document.getElementById("zoom-reset-button");
|
||||
ok(zoomResetButton, "Zoom reset button exists in Panel Menu");
|
||||
|
||||
zoomResetButton.click();
|
||||
let pageZoomLevel = parseInt(ZoomManager.zoom * 100);
|
||||
let expectedZoomLevel = parseInt(zoomResetButton.getAttribute("label"), 10);
|
||||
ok(pageZoomLevel == expectedZoomLevel && pageZoomLevel == 100, "Page zoom reset correctly");
|
||||
|
||||
// close the panel
|
||||
let panelHiddenPromise = promisePanelHidden(window);
|
||||
PanelUI.hide();
|
||||
yield panelHiddenPromise;
|
||||
|
||||
//reset the zoom level
|
||||
ZoomManager.zoom = initialPageZoom;
|
||||
});
|
@ -1687,8 +1687,10 @@ var gApplicationsPane = {
|
||||
var typeItem = this._list.selectedItem;
|
||||
var handlerInfo = this._handledTypes[typeItem.type];
|
||||
|
||||
document.documentElement.openSubDialog("chrome://browser/content/preferences/applicationManager.xul",
|
||||
"", handlerInfo);
|
||||
openDialog("chrome://browser/content/preferences/applicationManager.xul",
|
||||
"",
|
||||
"modal,centerscreen,resizable=no",
|
||||
handlerInfo);
|
||||
|
||||
// Rebuild the actions menu so that we revert to the previous selection,
|
||||
// or "Always ask" if the previous default application has been removed
|
||||
|
@ -115,6 +115,8 @@ function Eyedropper(chromeWindow, opts = { copyOnSelect: true }) {
|
||||
this._dragging = true;
|
||||
this.loaded = false;
|
||||
|
||||
this._mouseMoveCounter = 0;
|
||||
|
||||
this.format = Services.prefs.getCharPref(FORMAT_PREF); // color value format
|
||||
this.zoom = Services.prefs.getIntPref(ZOOM_PREF); // zoom level - integer
|
||||
|
||||
@ -378,6 +380,11 @@ Eyedropper.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._OS == "Linux" && ++this._mouseMoveCounter % 2 == 0) {
|
||||
// skip every other mousemove to preserve performance.
|
||||
return;
|
||||
}
|
||||
|
||||
this._setCoordinates(event);
|
||||
this._drawWindow();
|
||||
|
||||
|
@ -1036,7 +1036,7 @@ function detectIndentation(ed) {
|
||||
|
||||
// see how much this line is offset from the line above it
|
||||
let indent = Math.abs(width - last);
|
||||
if (indent > 1) {
|
||||
if (indent > 1 && indent <= 8) {
|
||||
spaces[indent] = (spaces[indent] || 0) + 1;
|
||||
}
|
||||
last = width;
|
||||
@ -1053,7 +1053,7 @@ function detectIndentation(ed) {
|
||||
}
|
||||
|
||||
// find most frequent non-zero width difference between adjacent lines
|
||||
let freqIndent = null, max = 0;
|
||||
let freqIndent = null, max = 1;
|
||||
for (let width in spaces) {
|
||||
width = parseInt(width, 10);
|
||||
let tally = spaces[width];
|
||||
@ -1062,6 +1062,9 @@ function detectIndentation(ed) {
|
||||
freqIndent = width;
|
||||
}
|
||||
}
|
||||
if (!freqIndent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { tabs: false, spaces: freqIndent };
|
||||
}
|
||||
|
@ -57,6 +57,13 @@ const TABS_CODE = [
|
||||
"}"
|
||||
].join("\n");
|
||||
|
||||
const NONE_CODE = [
|
||||
"var x = 0;",
|
||||
" // stray thing",
|
||||
"var y = 9;",
|
||||
" ",
|
||||
""
|
||||
].join("\n");
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
@ -67,6 +74,12 @@ function test() {
|
||||
is(ed.getOption("indentWithTabs"), false,
|
||||
"spaces is default");
|
||||
|
||||
ed.setText(NONE_CODE);
|
||||
is(ed.getOption("indentUnit"), 2,
|
||||
"2 spaces after un-detectable code");
|
||||
is(ed.getOption("indentWithTabs"), false,
|
||||
"spaces still set after un-detectable code");
|
||||
|
||||
ed.setText(FOUR_SPACES_CODE);
|
||||
is(ed.getOption("indentUnit"), 4,
|
||||
"4 spaces detected in 4 space code");
|
||||
|
@ -68,9 +68,6 @@ const COMPAT = {
|
||||
|
||||
// The indent of a console group in pixels.
|
||||
GROUP_INDENT: 12,
|
||||
|
||||
// The default indent in pixels, applied even without any groups.
|
||||
GROUP_INDENT_DEFAULT: 6,
|
||||
};
|
||||
|
||||
// A map from the console API call levels to the Web Console severities.
|
||||
@ -817,9 +814,10 @@ Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
|
||||
|
||||
// Apply the current group by indenting appropriately.
|
||||
// TODO: remove this once bug 778766 is fixed.
|
||||
let iconMarginLeft = this._groupDepthCompat * COMPAT.GROUP_INDENT +
|
||||
COMPAT.GROUP_INDENT_DEFAULT;
|
||||
icon.style.marginLeft = iconMarginLeft + "px";
|
||||
let indent = this._groupDepthCompat * COMPAT.GROUP_INDENT;
|
||||
let indentNode = this.document.createElementNS(XHTML_NS, "span");
|
||||
indentNode.className = "indent";
|
||||
indentNode.style.width = indent + "px";
|
||||
|
||||
let body = this._renderBody();
|
||||
this._repeatID.textContent += "|" + body.textContent;
|
||||
@ -833,6 +831,7 @@ Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
|
||||
}
|
||||
|
||||
this.element.appendChild(timestamp.element);
|
||||
this.element.appendChild(indentNode);
|
||||
this.element.appendChild(icon);
|
||||
this.element.appendChild(body);
|
||||
if (repeatNode) {
|
||||
@ -1110,14 +1109,14 @@ Messages.Extended.prototype = Heritage.extend(Messages.Simple.prototype,
|
||||
{
|
||||
let map = {
|
||||
"number": "cm-number",
|
||||
"longstring": "cm-string",
|
||||
"string": "cm-string",
|
||||
"longstring": "console-string",
|
||||
"string": "console-string",
|
||||
"regexp": "cm-string-2",
|
||||
"boolean": "cm-atom",
|
||||
"-infinity": "cm-atom",
|
||||
"infinity": "cm-atom",
|
||||
"null": "cm-atom",
|
||||
"undefined": "cm-atom",
|
||||
"undefined": "cm-comment",
|
||||
};
|
||||
|
||||
let className = map[typeof grip];
|
||||
@ -2117,8 +2116,8 @@ Widgets.ObjectRenderers.add({
|
||||
});
|
||||
|
||||
// Strings are property names.
|
||||
if (keyElem.classList && keyElem.classList.contains("cm-string")) {
|
||||
keyElem.classList.remove("cm-string");
|
||||
if (keyElem.classList && keyElem.classList.contains("console-string")) {
|
||||
keyElem.classList.remove("console-string");
|
||||
keyElem.classList.add("cm-property");
|
||||
}
|
||||
|
||||
@ -2192,7 +2191,7 @@ Widgets.ObjectRenderers.add({
|
||||
|
||||
if (!this.options.concise) {
|
||||
this._text(" ");
|
||||
this.element.appendChild(this.el("span.cm-string",
|
||||
this.element.appendChild(this.el("span.console-string",
|
||||
VariablesView.getString(preview.text)));
|
||||
}
|
||||
},
|
||||
@ -2336,7 +2335,8 @@ Widgets.ObjectRenderers.add({
|
||||
}
|
||||
|
||||
this._text("=", fragment);
|
||||
fragment.appendChild(this.el("span.cm-string", '"' + escapeHTML(value) + '"'));
|
||||
fragment.appendChild(this.el("span.console-string",
|
||||
'"' + escapeHTML(value) + '"'));
|
||||
|
||||
return fragment;
|
||||
},
|
||||
@ -2350,7 +2350,7 @@ Widgets.ObjectRenderers.add({
|
||||
this._text(" ");
|
||||
|
||||
let text = VariablesView.getString(preview.textContent);
|
||||
this.element.appendChild(this.el("span.cm-string", text));
|
||||
this.element.appendChild(this.el("span.console-string", text));
|
||||
},
|
||||
|
||||
_renderCommentNode: function()
|
||||
@ -2678,7 +2678,7 @@ Widgets.LongString.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
|
||||
}
|
||||
|
||||
let result = this.element = this.document.createElementNS(XHTML_NS, "span");
|
||||
result.className = "longString cm-string";
|
||||
result.className = "longString console-string";
|
||||
this._renderString(this.longStringActor.initial);
|
||||
result.appendChild(this._renderEllipsis());
|
||||
|
||||
|
@ -36,9 +36,6 @@ const SEVERITY_LOG = 3;
|
||||
// The indent of a console group in pixels.
|
||||
const GROUP_INDENT = 12;
|
||||
|
||||
// The default indent in pixels, applied even without any groups.
|
||||
const GROUP_INDENT_DEFAULT = 6;
|
||||
|
||||
const WEBCONSOLE_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
|
||||
let WCU_l10n = new WebConsoleUtils.l10n(WEBCONSOLE_STRINGS_URI);
|
||||
|
||||
@ -1204,10 +1201,10 @@ function waitForMessages(aOptions)
|
||||
}
|
||||
|
||||
if ("groupDepth" in aRule) {
|
||||
let icon = aElement.querySelector(".icon");
|
||||
let indent = (GROUP_INDENT * aRule.groupDepth + GROUP_INDENT_DEFAULT) + "px";
|
||||
if (!icon || icon.style.marginLeft != indent) {
|
||||
is(icon.style.marginLeft, indent,
|
||||
let indentNode = aElement.querySelector(".indent");
|
||||
let indent = (GROUP_INDENT * aRule.groupDepth) + "px";
|
||||
if (!indentNode || indentNode.style.width != indent) {
|
||||
is(indentNode.style.width, indent,
|
||||
"group depth check failed for message rule: " + displayRule(aRule));
|
||||
return false;
|
||||
}
|
||||
|
@ -145,9 +145,6 @@ const HISTORY_FORWARD = 1;
|
||||
// The indent of a console group in pixels.
|
||||
const GROUP_INDENT = 12;
|
||||
|
||||
// The default indent in pixels, applied even without any groups.
|
||||
const GROUP_INDENT_DEFAULT = 6;
|
||||
|
||||
// The number of messages to display in a single display update. If we display
|
||||
// too many messages at once we slow the Firefox UI too much.
|
||||
const MESSAGES_IN_INTERVAL = DEFAULT_LOG_LIMIT;
|
||||
@ -2449,16 +2446,19 @@ WebConsoleFrame.prototype = {
|
||||
aClipboardText = aBody.innerText;
|
||||
}
|
||||
|
||||
let indentNode = this.document.createElementNS(XHTML_NS, "span");
|
||||
indentNode.className = "indent";
|
||||
|
||||
// Apply the current group by indenting appropriately.
|
||||
let indent = this.groupDepth * GROUP_INDENT;
|
||||
indentNode.style.width = indent + "px";
|
||||
|
||||
// Make the icon container, which is a vertical box. Its purpose is to
|
||||
// ensure that the icon stays anchored at the top of the message even for
|
||||
// long multi-line messages.
|
||||
let iconContainer = this.document.createElementNS(XHTML_NS, "span");
|
||||
iconContainer.className = "icon";
|
||||
|
||||
// Apply the current group by indenting appropriately.
|
||||
let iconMarginLeft = this.groupDepth * GROUP_INDENT + GROUP_INDENT_DEFAULT;
|
||||
iconContainer.style.marginLeft = iconMarginLeft + "px";
|
||||
|
||||
// Create the message body, which contains the actual text of the message.
|
||||
let bodyNode = this.document.createElementNS(XHTML_NS, "span");
|
||||
bodyNode.className = "message-body-wrapper message-body devtools-monospace";
|
||||
@ -2528,6 +2528,7 @@ WebConsoleFrame.prototype = {
|
||||
}
|
||||
|
||||
node.appendChild(timestampNode);
|
||||
node.appendChild(indentNode);
|
||||
node.appendChild(iconContainer);
|
||||
|
||||
// Display the variables view after the message node.
|
||||
|
@ -259,7 +259,7 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
||||
<!ENTITY devToolbarMenu.label "Developer Toolbar">
|
||||
<!ENTITY devToolbarMenu.accesskey "v">
|
||||
<!ENTITY devAppMgrMenu.label "App Manager">
|
||||
<!ENTITY devAppMgrMenu.accesskey "a">
|
||||
<!ENTITY devAppMgrMenu.accesskey "A">
|
||||
<!ENTITY devToolbar.keycode "VK_F2">
|
||||
<!ENTITY devToolbar.keytext "F2">
|
||||
<!ENTITY devToolboxMenuItem.label "Toggle Tools">
|
||||
|
@ -58,7 +58,7 @@ open.commandkey=VK_F7
|
||||
|
||||
# LOCALIZATION NOTE (open.accesskey): The access key used to open the style
|
||||
# editor.
|
||||
open.accesskey=y
|
||||
open.accesskey=l
|
||||
|
||||
# LOCALIZATION NOTE (saveStyleSheet.commandkey): This the key to use in
|
||||
# conjunction with accel (Command on Mac or Ctrl on other platforms) to Save
|
||||
|
@ -8,5 +8,6 @@
|
||||
<!ENTITY newtab.undo.undoButton "Undo.">
|
||||
<!ENTITY newtab.undo.restoreButton "Restore All.">
|
||||
<!ENTITY newtab.undo.closeTooltip "Hide">
|
||||
<!ENTITY newtab.panel.message "This site is being suggested because it has sponsored Mozilla, helping us promote openness, innovation and opportunity on the Web.">
|
||||
<!ENTITY newtab.sponsored.release.message "This Sponsor site was suggested because we hoped you’d find it interesting and because it supports Mozilla’s mission.">
|
||||
<!ENTITY newtab.sponsored.trial.message "This Trial Sponsor site was suggested because we hoped you’d find it interesting and because it supports Mozilla’s mission.">
|
||||
<!ENTITY newtab.panel.link.text "Learn more…">
|
||||
|
@ -26,8 +26,6 @@ checkbox {
|
||||
|
||||
checkbox:hover::before,
|
||||
checkbox[checked]::before {
|
||||
margin-top: 7px;
|
||||
margin-bottom: 6px;
|
||||
-moz-margin-end: -19px;
|
||||
-moz-margin-start: 4px;
|
||||
background-repeat: no-repeat;
|
||||
@ -36,7 +34,7 @@ checkbox[checked]::before {
|
||||
radio {
|
||||
-moz-binding: url("chrome://global/content/bindings/radio.xml#radio");
|
||||
-moz-box-align: center;
|
||||
margin: 7px 0;
|
||||
-moz-margin-start: 0;
|
||||
}
|
||||
|
||||
.radio-label-box {
|
||||
@ -45,8 +43,6 @@ radio {
|
||||
|
||||
radio:hover::before,
|
||||
radio[selected]::before {
|
||||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
-moz-margin-end: -17px;
|
||||
-moz-margin-start: 6px;
|
||||
}
|
||||
|
@ -701,6 +701,10 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
|
||||
-moz-image-region: rect(18px, 342px, 36px, 324px);
|
||||
}
|
||||
|
||||
#e10s-button@toolbarButtonPressed@ {
|
||||
-moz-image-region: rect(18px, 342px, 36px, 324px);
|
||||
}
|
||||
|
||||
#new-tab-button@toolbarButtonPressed@ {
|
||||
-moz-image-region: rect(18px, 360px, 36px, 342px);
|
||||
}
|
||||
@ -942,6 +946,18 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
|
||||
-moz-image-region: rect(36px, 684px, 72px, 648px);
|
||||
}
|
||||
|
||||
#e10s-button[cui-areatype="toolbar"] {
|
||||
-moz-image-region: rect(0, 684px, 36px, 648px);
|
||||
}
|
||||
|
||||
#e10s-button[cui-areatype="toolbar"]:hover:active:not([disabled="true"]) {
|
||||
-moz-image-region: rect(36px, 684px, 72px, 648px);
|
||||
}
|
||||
|
||||
#e10s-button > .toolbarbutton-icon {
|
||||
transform: scaleY(-1);
|
||||
}
|
||||
|
||||
#new-tab-button[cui-areatype="toolbar"] {
|
||||
-moz-image-region: rect(0, 720px, 36px, 684px);
|
||||
}
|
||||
@ -1190,6 +1206,11 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
|
||||
-moz-image-region: rect(0px, 1024px, 64px, 960px);
|
||||
}
|
||||
|
||||
#e10s-button[cui-areatype="menu-panel"],
|
||||
toolbarpaletteitem[place="palette"] > #e10s-button {
|
||||
-moz-image-region: rect(0px, 1024px, 64px, 960px);
|
||||
}
|
||||
|
||||
#new-tab-button[cui-areatype="menu-panel"],
|
||||
toolbarpaletteitem[place="palette"] > #new-tab-button {
|
||||
-moz-image-region: rect(0px, 1088px, 64px, 1024px);
|
||||
|
@ -12,7 +12,6 @@ menulist:not([editable="true"]) > .menulist-dropmarker {
|
||||
|
||||
checkbox:hover::before,
|
||||
checkbox[checked]::before {
|
||||
margin-bottom: -2px;
|
||||
-moz-margin-end: -20px;
|
||||
-moz-margin-start: 5px;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
% Note that zoom-reset-button is a bit different since it doesn't use an image and thus has the image with display: none.
|
||||
%define nestedButtons #zoom-out-button, #zoom-reset-button, #zoom-in-button, #cut-button, #copy-button, #paste-button
|
||||
%define primaryToolbarButtons #back-button, #forward-button, #home-button, #print-button, #downloads-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #fullscreen-button, #sync-button, #feed-button, #tabview-button, #webrtc-status-button, #social-share-button, #open-file-button, #find-button, #developer-button, #preferences-button, #privatebrowsing-button, #save-page-button, #switch-to-metro-button, #add-ons-button, #history-panelmenu, #nav-bar-overflow-button, #PanelUI-menu-button, #characterencoding-button, #email-link-button, #sidebar-button, @nestedButtons@
|
||||
%define primaryToolbarButtons #back-button, #forward-button, #home-button, #print-button, #downloads-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #fullscreen-button, #sync-button, #feed-button, #tabview-button, #webrtc-status-button, #social-share-button, #open-file-button, #find-button, #developer-button, #preferences-button, #privatebrowsing-button, #save-page-button, #switch-to-metro-button, #add-ons-button, #history-panelmenu, #nav-bar-overflow-button, #PanelUI-menu-button, #characterencoding-button, #email-link-button, #sidebar-button, @nestedButtons@, #e10s-button
|
||||
|
||||
%ifdef XP_MACOSX
|
||||
% Prior to 10.7 there wasn't a native fullscreen button so we use #restore-button to exit fullscreen
|
||||
|
@ -18,29 +18,35 @@ a {
|
||||
.message {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
-moz-margin-start: 6px;
|
||||
-moz-margin-end: 8px;
|
||||
width: calc(100% - 6px - 8px);
|
||||
padding: 0 7px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.message > .timestamp {
|
||||
flex: 0 0 auto;
|
||||
color: GrayText;
|
||||
margin: 4px 0;
|
||||
margin: 4px 6px 0 0;
|
||||
}
|
||||
|
||||
.message > .indent {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.message > .icon {
|
||||
background: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 0, 1, 0, 0) no-repeat;
|
||||
background-position: center 0.5em;
|
||||
background-position: center;
|
||||
flex: 0 0 auto;
|
||||
margin: 0 6px;
|
||||
margin: 3px 6px 0 0;
|
||||
padding: 0 4px;
|
||||
width: 8px;
|
||||
height: 1em;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.message > .message-body-wrapper {
|
||||
flex: 1 1 100%;
|
||||
margin-top: 4px;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
/* The red bubble that shows the number of times a message is repeated */
|
||||
@ -63,13 +69,12 @@ a {
|
||||
}
|
||||
|
||||
.message-location {
|
||||
-moz-margin-start: 6px;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-self: flex-start;
|
||||
justify-content: flex-end;
|
||||
width: 10em;
|
||||
margin: 3px 0;
|
||||
margin-top: 3px;
|
||||
color: -moz-nativehyperlinktext;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
@ -102,6 +107,11 @@ a {
|
||||
.message-flex-body > .message-body {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.message-flex-body > .message-location {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.jsterm-input-container {
|
||||
@ -122,10 +132,24 @@ a {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#output-container.hideTimestamps > .message {
|
||||
-moz-padding-start: 0;
|
||||
-moz-margin-start: 7px;
|
||||
width: calc(100% - 7px);
|
||||
}
|
||||
|
||||
#output-container.hideTimestamps > .message > .timestamp {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.theme-dark #output-container.hideTimestamps > .message > .indent {
|
||||
background-color: #14171a; /* .theme-body */
|
||||
}
|
||||
|
||||
.theme-light #output-container.hideTimestamps > .message > .indent {
|
||||
background-color: #fcfcfc; /* .theme-body */
|
||||
}
|
||||
|
||||
.filtered-by-type,
|
||||
.filtered-by-string {
|
||||
display: none;
|
||||
@ -170,11 +194,19 @@ a {
|
||||
}
|
||||
|
||||
.theme-dark .message[severity=error] {
|
||||
background-color: rgba(255, 100, 100, 0.3);
|
||||
background-color: rgba(235, 83, 104, 0.17);
|
||||
}
|
||||
|
||||
.message[category=network] > .icon {
|
||||
-moz-border-start: solid #000 6px;
|
||||
.theme-dark .console-string {
|
||||
color: #d99b28;
|
||||
}
|
||||
|
||||
.theme-light .console-string {
|
||||
color: hsl(24,85%,39%);
|
||||
}
|
||||
|
||||
.message[category=network] > .indent {
|
||||
-moz-border-end: solid #000 6px;
|
||||
}
|
||||
|
||||
.message[category=network][severity=error] > .icon {
|
||||
@ -219,8 +251,8 @@ a {
|
||||
border-color: #1BA2CC;
|
||||
}
|
||||
|
||||
.message[category=cssparser] > .icon {
|
||||
-moz-border-start: solid #00b6f0 6px;
|
||||
.message[category=cssparser] > .indent {
|
||||
-moz-border-end: solid #00b6f0 6px;
|
||||
}
|
||||
|
||||
.message[category=cssparser][severity=error] > .icon {
|
||||
@ -237,8 +269,8 @@ a {
|
||||
border-color: #E98A00;
|
||||
}
|
||||
|
||||
.message[category=exception] > .icon {
|
||||
-moz-border-start: solid #fb9500 6px;
|
||||
.message[category=exception] > .indent {
|
||||
-moz-border-end: solid #fb9500 6px;
|
||||
}
|
||||
|
||||
.message[category=exception][severity=error] > .icon {
|
||||
@ -255,8 +287,8 @@ a {
|
||||
border-color: #929292;
|
||||
}
|
||||
|
||||
.message[category=console] > .icon {
|
||||
-moz-border-start: solid #cbcbcb 6px;
|
||||
.message[category=console] > .indent {
|
||||
-moz-border-end: solid #cbcbcb 6px;
|
||||
}
|
||||
|
||||
.message[category=console][severity=error] > .icon,
|
||||
@ -273,9 +305,9 @@ a {
|
||||
}
|
||||
|
||||
/* Input and output styles */
|
||||
.message[category=input] > .icon,
|
||||
.message[category=output] > .icon {
|
||||
-moz-border-start: solid #808080 6px;
|
||||
.message[category=input] > .indent,
|
||||
.message[category=output] > .indent {
|
||||
-moz-border-end: solid #808080 6px;
|
||||
}
|
||||
|
||||
.message[category=input] > .icon {
|
||||
@ -329,8 +361,8 @@ a {
|
||||
|
||||
/* Security styles */
|
||||
|
||||
.message[category=security] > .icon {
|
||||
-moz-border-start: solid red 6px;
|
||||
.message[category=security] > .indent {
|
||||
-moz-border-end: solid red 6px;
|
||||
}
|
||||
|
||||
.webconsole-filter-button[category="security"] > .toolbarbutton-menubutton-button:before {
|
||||
@ -386,7 +418,7 @@ a {
|
||||
.message .theme-twisty {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 2px 3px 0 0;
|
||||
margin: 0 3px 0 0;
|
||||
}
|
||||
|
||||
.stacktrace li {
|
||||
|
@ -342,7 +342,8 @@ html|a.inline-link {
|
||||
/* Checkboxes and radio buttons */
|
||||
|
||||
checkbox {
|
||||
margin: 7px 0;
|
||||
-moz-margin-start: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.checkbox-check {
|
||||
@ -415,6 +416,10 @@ checkbox[checked]::before {
|
||||
-moz-padding-start: 0;
|
||||
}
|
||||
|
||||
radio {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
radio:hover::before,
|
||||
radio[selected]::before {
|
||||
position: absolute;
|
||||
@ -422,7 +427,6 @@ radio[selected]::before {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 1px;
|
||||
background-image: linear-gradient(rgba(76,177,255,0.25) 0%, rgba(23,146,229,0.25) 100%);
|
||||
}
|
||||
|
||||
@ -680,11 +684,6 @@ radio[selected]::before {
|
||||
}
|
||||
}
|
||||
|
||||
.indent {
|
||||
margin-top: 7px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
/* General Pane */
|
||||
|
||||
filefield {
|
||||
|
@ -83,6 +83,11 @@ toolbarpaletteitem[place="palette"] > #new-window-button {
|
||||
-moz-image-region: rect(0px, 512px, 32px, 480px);
|
||||
}
|
||||
|
||||
#e10s-button[cui-areatype="menu-panel"],
|
||||
toolbarpaletteitem[place="palette"] > #e10s-button {
|
||||
-moz-image-region: rect(0px, 512px, 32px, 480px);
|
||||
}
|
||||
|
||||
#new-tab-button[cui-areatype="menu-panel"],
|
||||
toolbarpaletteitem[place="palette"] > #new-tab-button {
|
||||
-moz-image-region: rect(0px, 544px, 32px, 512px);
|
||||
|
@ -85,6 +85,14 @@
|
||||
-moz-image-region: rect(0, 342px, 18px, 324px);
|
||||
}
|
||||
|
||||
#e10s-button[cui-areatype="toolbar"] {
|
||||
-moz-image-region: rect(0, 342px, 18px, 324px);
|
||||
}
|
||||
|
||||
#e10s-button > .toolbarbutton-icon {
|
||||
transform: scaleY(-1);
|
||||
}
|
||||
|
||||
#new-tab-button[cui-areatype="toolbar"] {
|
||||
-moz-image-region: rect(0, 360px, 18px, 342px);
|
||||
}
|
||||
|
@ -11,14 +11,13 @@ menulist:not([editable="true"]) > .menulist-dropmarker {
|
||||
|
||||
checkbox:hover::before,
|
||||
checkbox[checked]::before {
|
||||
margin-bottom: -1px;
|
||||
-moz-margin-end: -19px;
|
||||
-moz-margin-start: 4px;
|
||||
}
|
||||
|
||||
radio {
|
||||
-moz-binding: url("chrome://global/content/bindings/radio.xml#radio");
|
||||
margin: 7px 0;
|
||||
-moz-margin-start: 0;
|
||||
}
|
||||
|
||||
radio:hover::before,
|
||||
|
@ -6,6 +6,7 @@ package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.NativeEventListener;
|
||||
@ -208,6 +209,7 @@ public final class EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
@Deprecated
|
||||
public static void sendResponse(JSONObject message, Object response) {
|
||||
sendResponseHelper(STATUS_SUCCESS, message, response);
|
||||
|
@ -8,6 +8,7 @@ import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils.BitmapLoader;
|
||||
import org.mozilla.gecko.gfx.Layer;
|
||||
import org.mozilla.gecko.gfx.LayerView;
|
||||
import org.mozilla.gecko.gfx.LayerView.DrawListener;
|
||||
import org.mozilla.gecko.menu.GeckoMenu;
|
||||
import org.mozilla.gecko.menu.GeckoMenuItem;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
@ -40,6 +41,9 @@ class TextSelection extends Layer implements GeckoEventListener {
|
||||
private final TextSelectionHandle mEndHandle;
|
||||
private final EventDispatcher mEventDispatcher;
|
||||
|
||||
private final DrawListener mDrawListener;
|
||||
private boolean mDraggingHandles;
|
||||
|
||||
private float mViewLeft;
|
||||
private float mViewTop;
|
||||
private float mViewZoom;
|
||||
@ -74,6 +78,15 @@ class TextSelection extends Layer implements GeckoEventListener {
|
||||
mEndHandle = endHandle;
|
||||
mEventDispatcher = eventDispatcher;
|
||||
|
||||
mDrawListener = new DrawListener() {
|
||||
@Override
|
||||
public void drawFinished() {
|
||||
if (!mDraggingHandles) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("TextSelection:LayerReflow", ""));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Only register listeners if we have valid start/middle/end handles
|
||||
if (mStartHandle == null || mMiddleHandle == null || mEndHandle == null) {
|
||||
Log.e(LOGTAG, "Failed to initialize text selection because at least one handle is null");
|
||||
@ -82,6 +95,7 @@ class TextSelection extends Layer implements GeckoEventListener {
|
||||
registerEventListener("TextSelection:HideHandles");
|
||||
registerEventListener("TextSelection:PositionHandles");
|
||||
registerEventListener("TextSelection:Update");
|
||||
registerEventListener("TextSelection:DraggingHandle");
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,6 +104,7 @@ class TextSelection extends Layer implements GeckoEventListener {
|
||||
unregisterEventListener("TextSelection:HideHandles");
|
||||
unregisterEventListener("TextSelection:PositionHandles");
|
||||
unregisterEventListener("TextSelection:Update");
|
||||
unregisterEventListener("TextSelection:DraggingHandle");
|
||||
}
|
||||
|
||||
private TextSelectionHandle getHandle(String name) {
|
||||
@ -104,6 +119,11 @@ class TextSelection extends Layer implements GeckoEventListener {
|
||||
|
||||
@Override
|
||||
public void handleMessage(final String event, final JSONObject message) {
|
||||
if ("TextSelection:DraggingHandle".equals(event)) {
|
||||
mDraggingHandles = message.optBoolean("dragging", false);
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -118,15 +138,14 @@ class TextSelection extends Layer implements GeckoEventListener {
|
||||
mViewLeft = 0.0f;
|
||||
mViewTop = 0.0f;
|
||||
mViewZoom = 0.0f;
|
||||
|
||||
// Create text selection layer and add draw-listener for positioning on reflows
|
||||
LayerView layerView = GeckoAppShell.getLayerView();
|
||||
if (layerView != null) {
|
||||
layerView.addDrawListener(mDrawListener);
|
||||
layerView.addLayer(TextSelection.this);
|
||||
}
|
||||
|
||||
if (mActionModeTimerTask != null)
|
||||
mActionModeTimerTask.cancel();
|
||||
showActionMode(message.getJSONArray("actions"));
|
||||
|
||||
if (handles.length() > 1)
|
||||
GeckoAppShell.performHapticFeedback(true);
|
||||
} else if (event.equals("TextSelection:Update")) {
|
||||
@ -134,8 +153,10 @@ class TextSelection extends Layer implements GeckoEventListener {
|
||||
mActionModeTimerTask.cancel();
|
||||
showActionMode(message.getJSONArray("actions"));
|
||||
} else if (event.equals("TextSelection:HideHandles")) {
|
||||
// Remove draw-listener and text selection layer
|
||||
LayerView layerView = GeckoAppShell.getLayerView();
|
||||
if (layerView != null) {
|
||||
layerView.removeDrawListener(mDrawListener);
|
||||
layerView.removeLayer(TextSelection.this);
|
||||
}
|
||||
|
||||
|
@ -417,7 +417,6 @@ public class FaviconCache {
|
||||
if (element == null) {
|
||||
Log.w(LOGTAG, "Cannot compute dominant color of non-cached favicon. Cache fullness " +
|
||||
currentSize.get() + '/' + maxSizeBytes);
|
||||
finishRead();
|
||||
return 0xFFFFFF;
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,7 @@ public class StringHelper {
|
||||
public static final String ROBOCOP_SEARCH_TITLE = "Robocop Search Engine";
|
||||
public static final String ROBOCOP_TEXT_PAGE_TITLE = "Robocop Text Page";
|
||||
public static final String ROBOCOP_INPUT_TITLE = "Robocop Input";
|
||||
public static final String ROBOCOP_SELECTION_HANDLER_TITLE = "Automated Text Selection tests for Mobile";
|
||||
|
||||
// Settings menu strings
|
||||
// Section labels - ordered as found in the settings menu
|
||||
|
@ -81,6 +81,7 @@ skip-if = android_version == "10"
|
||||
[testPrefsObserver]
|
||||
[testPrivateBrowsing]
|
||||
[testPromptGridInput]
|
||||
[testSelectionHandler]
|
||||
# disabled on x86 only; bug 957185
|
||||
skip-if = processor == "x86"
|
||||
# [testReaderMode] # see bug 913254, 936224
|
||||
|
301
mobile/android/base/tests/roboextender/testSelectionHandler.html
Normal file
301
mobile/android/base/tests/roboextender/testSelectionHandler.html
Normal file
@ -0,0 +1,301 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Automated Text Selection tests for Mobile</title>
|
||||
<meta name="viewport" content="initial-scale=1.0"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
const DIV_POINT_TEXT = "Under";
|
||||
const INPUT_TEXT = "Text for select all in an <input>";
|
||||
const TEXTAREA_TEXT = "Text for select all in a <textarea>";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Start of all text selection tests, check initialization state.
|
||||
*
|
||||
*/
|
||||
function startTests() {
|
||||
testSelectAllDivs().
|
||||
then(testSelectDivAtPoint).
|
||||
then(testSelectInput).
|
||||
then(testSelectTextarea).
|
||||
then(testCloseSelection).
|
||||
then(finishTests, function(err) {
|
||||
ok(false, "Error in selection test " + err);
|
||||
finishTests();
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* "Select all" text selection tests, for <div> (non-editable) fields.
|
||||
*
|
||||
*/
|
||||
function testSelectAllDivs() {
|
||||
var sh = getSelectionHandler();
|
||||
var selDiv = document.getElementById("selDiv");
|
||||
var nonSelDiv = document.getElementById("nonSelDiv");
|
||||
|
||||
// Check the initial state of the selection handler, and selectable/non-selectable <div>s.
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active at start of testSelectAllDivs"),
|
||||
ok(sh.canSelect(selDiv), "Can select selectable <div>"),
|
||||
ok(!sh.canSelect(nonSelDiv), "Can't select non-selectable <div>"),
|
||||
|
||||
]).then(function() {
|
||||
// Select all on a non-editable text node selects all the text in the page.
|
||||
sh.startSelection(selDiv);
|
||||
var selection = sh._getSelection();
|
||||
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(), "Selection should be active now"),
|
||||
is(selection.anchorNode, document.documentElement, "Anchor Node should be start of document"),
|
||||
is(selection.anchorOffset, 0, "Anchor offset should be 0"),
|
||||
is(selection.focusNode, document.body.lastChild, "Focus node should be lastChild of document"),
|
||||
is(selection.focusOffset, document.body.lastChild.textContent.length, "Focus offset should be it's length"),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* "Select word-at-point" text selection test, for <div> (non-editable) field.
|
||||
* "collapseToStart" test closes selection (Bug 864589).
|
||||
*
|
||||
*/
|
||||
function testSelectDivAtPoint() {
|
||||
var sh = getSelectionHandler();
|
||||
var selDiv = document.getElementById("selDiv");
|
||||
|
||||
// Select word at point in <div>
|
||||
var rect = selDiv.getBoundingClientRect();
|
||||
sh.startSelection(selDiv, {
|
||||
mode: sh.SELECT_AT_POINT,
|
||||
x: rect.left + 1,
|
||||
y: rect.top + 1
|
||||
});
|
||||
var selection = sh._getSelection();
|
||||
|
||||
// Check the state of the selection handler after selecting at a point.
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(), "Selection should be active at start of testSelectDivAtPoint"),
|
||||
is(selection.toString(), DIV_POINT_TEXT, "The first word in the <div> was selected"),
|
||||
|
||||
]).then(function() {
|
||||
// Check the state of the selection handler after collapsing a selection.
|
||||
selection.collapseToStart();
|
||||
|
||||
return Promise.all([
|
||||
ok(selection.getRangeAt(0).collapsed, "Selection should be collapsed"),
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active"),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* "Select all" text selection test, for <input> (editable) field.
|
||||
*
|
||||
*/
|
||||
function testSelectInput() {
|
||||
var sh = getSelectionHandler();
|
||||
var inputNode = document.getElementById("inputNode");
|
||||
inputNode.value = INPUT_TEXT;
|
||||
|
||||
// Test that calling startSelection with an input selects all the text in the input.
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active at start of testSelectInput"),
|
||||
ok(sh.canSelect(inputNode), "Can select selectable <input>"),
|
||||
|
||||
]).then(function() {
|
||||
// Check the state of the selection handler after calling startSelection on it.
|
||||
sh.startSelection(inputNode);
|
||||
var selection = sh._getSelection();
|
||||
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(), "Selection should be active"),
|
||||
ok((sh._targetElement instanceof Ci.nsIDOMNSEditableElement), "Selected element is editable"),
|
||||
is(selection.toString(), INPUT_TEXT, "All text in the <input> was selected"),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* "Select all" text selection test, for <textarea> (editable) field.
|
||||
*
|
||||
*/
|
||||
|
||||
function testSelectTextarea() {
|
||||
var sh = getSelectionHandler();
|
||||
var textareaNode = document.getElementById("textareaNode");
|
||||
textareaNode.value = TEXTAREA_TEXT;
|
||||
|
||||
// Change (still-active) selection from previous <input> field to <textarea>
|
||||
sh.startSelection(textareaNode);
|
||||
var selection = sh._getSelection();
|
||||
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(), "Selection should be active at start of testSelectTextarea"),
|
||||
ok((sh._targetElement instanceof Ci.nsIDOMHTMLTextAreaElement), "Selected element is editable, and a <textarea>"),
|
||||
is(selection.toString(), TEXTAREA_TEXT, "All text in the <textarea> was selected"),
|
||||
|
||||
]).then(function() {
|
||||
// Collpase the selection to close it again.
|
||||
selection.collapseToStart();
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active"),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Various text selection tests to end active selections, including:
|
||||
* 1.) Clicking outside the selection.
|
||||
* 2.) SelectionEnd or Tab:Selected messages from java.
|
||||
*
|
||||
*/
|
||||
function testCloseSelection() {
|
||||
var sh = getSelectionHandler();
|
||||
var inputNode = document.getElementById("inputNode");
|
||||
inputNode.value = INPUT_TEXT;
|
||||
|
||||
// Check the initial state of the selection handler.
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active at start of testCloseSelection"),
|
||||
|
||||
]).then(function() {
|
||||
// Start by selecting all in an <input>.
|
||||
sh.startSelection(inputNode);
|
||||
return is(sh._activeType, sh.TYPE_SELECTION, "Selection should be active in <input> before Gesture:SingleTap");
|
||||
|
||||
}).then(function() {
|
||||
// Tap outside <input> to close active selection.
|
||||
sh.observe(null, "Gesture:SingleTap", JSON.stringify({
|
||||
x: 1,
|
||||
y: 1
|
||||
}));
|
||||
return ok(!sh.isSelectionActive(), "Gesture:SingleTap outside <input> should close active selection");
|
||||
|
||||
// Various other ways to close an active selection.
|
||||
}).then(function() {
|
||||
sh.startSelection(inputNode);
|
||||
sh.observe(null, "TextSelection:End", {});
|
||||
return ok(!sh.isSelectionActive(), "TextSelection:End should close active selection");
|
||||
|
||||
}).then(function() {
|
||||
sh.startSelection(inputNode);
|
||||
sh.observe(null, "Tab:Selected", {});
|
||||
return ok(!sh.isSelectionActive(), "Tab:Selected should close active selection");
|
||||
|
||||
}).then(function() {
|
||||
sh.startSelection(inputNode);
|
||||
sh.handleEvent({ type: "pagehide" });
|
||||
return ok(!sh.isSelectionActive(), "pagehide should close active selection");
|
||||
|
||||
}).then(function() {
|
||||
sh.startSelection(inputNode);
|
||||
sh.handleEvent({ type: "blur" });
|
||||
return ok(!sh.isSelectionActive(), "blur should close active selection");
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* After finish of all selection tests, wrap up and go home.
|
||||
*
|
||||
*/
|
||||
function finishTests() {
|
||||
sendMessageToJava({
|
||||
type: "Robocop:testSelectionHandler",
|
||||
result: true,
|
||||
msg: "Done!",
|
||||
done: true
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================== Utility functions ======================
|
||||
*
|
||||
* Common functions available to all tests.
|
||||
*
|
||||
*/
|
||||
function getSelectionHandler() {
|
||||
return (!this._selectionHandler) ?
|
||||
this._selectionHandler = Services.wm.getMostRecentWindow("navigator:browser").SelectionHandler :
|
||||
this._selectionHandler;
|
||||
}
|
||||
|
||||
function ok(one, msg) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
sendMessageToJava({
|
||||
type: "Robocop:testSelectionHandler",
|
||||
result: one,
|
||||
msg: msg
|
||||
},
|
||||
function (res, err) {
|
||||
(err) ? reject(err) : resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function is(one, two, msg) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
sendMessageToJava({
|
||||
type: "Robocop:testSelectionHandler",
|
||||
result: one === two,
|
||||
msg: msg + " : " + one + " === " + two
|
||||
},
|
||||
function (res, err) {
|
||||
(err) ? reject(err) : resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Page definition for all tests.
|
||||
*
|
||||
*/
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body onload="startTests();">
|
||||
|
||||
<div id="selDiv">Under sufficiently extreme conditions, quarks may become
|
||||
deconfined and exist as free particles. In the course of asymptotic freedom,
|
||||
the strong interaction becomes weaker at higher temperatures. Eventually,
|
||||
color confinement would be lost and an extremely hot plasma of freely moving
|
||||
quarks and gluons would be formed. This theoretical phase of matter is called
|
||||
quark-gluon plasma.[81] The exact conditions needed to give rise to this state
|
||||
are unknown and have been the subject of a great deal of speculation and
|
||||
experimentation. A recent estimate puts the needed temperature at
|
||||
(1.90±0.02)×1012 Kelvin. While a state of entirely free quarks and gluons has
|
||||
never been achieved (despite numerous attempts by CERN in the 1980s and 1990s),
|
||||
recent experiments at the Relativistic Heavy Ion Collider have yielded evidence
|
||||
for liquid-like quark matter exhibiting "nearly perfect" fluid motion.</div><br>
|
||||
|
||||
<div id="nonSelDiv" style="-moz-user-select: none;">Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit. Proin in blandit magna, non porttitor augue.
|
||||
Nam in neque sagittis, varius augue at, ornare velit. Vestibulum eget nisl
|
||||
congue odio molestie scelerisque. Pellentesque ut augue orci. In hac habitasse
|
||||
platea dictumst. Sed placerat tellus quis lacus condimentum, quis luctus elit
|
||||
pellentesque. Mauris cursus neque diam, sit amet gravida quam porta ac.
|
||||
Aliquam aliquam feugiat vestibulum. Proin commodo nulla ligula, in bibendum
|
||||
massa euismod a. Ut ac lobortis dui. Ut id augue id arcu ornare suscipit eu
|
||||
ornare lorem. Pellentesque nec dictum ante. Nam quis ligula ultricies, auctor
|
||||
nunc vel, fringilla turpis. Nulla lacinia, leo ut egestas hendrerit, risus
|
||||
ligula interdum enim, vel varius libero sem ut ligula.</div><br>
|
||||
|
||||
<input id="inputNode" type="text"><br>
|
||||
|
||||
<textarea id="textareaNode"></textarea><br>
|
||||
|
||||
</body>
|
||||
</html>
|
46
mobile/android/base/tests/testSelectionHandler.java
Normal file
46
mobile/android/base/tests/testSelectionHandler.java
Normal file
@ -0,0 +1,46 @@
|
||||
package org.mozilla.gecko.tests;
|
||||
|
||||
import org.mozilla.gecko.Actions;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.tests.helpers.GeckoHelper;
|
||||
import org.mozilla.gecko.tests.helpers.NavigationHelper;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
public class testSelectionHandler extends UITest {
|
||||
|
||||
public void testSelectionHandler() {
|
||||
GeckoHelper.blockForReady();
|
||||
|
||||
Actions.EventExpecter robocopTestExpecter = getActions().expectGeckoEvent("Robocop:testSelectionHandler");
|
||||
NavigationHelper.enterAndLoadUrl("chrome://roboextender/content/testSelectionHandler.html");
|
||||
mToolbar.assertTitle(StringHelper.ROBOCOP_SELECTION_HANDLER_TITLE);
|
||||
|
||||
while (!test(robocopTestExpecter)) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
robocopTestExpecter.unregisterListener();
|
||||
}
|
||||
|
||||
private boolean test(Actions.EventExpecter expecter) {
|
||||
final JSONObject eventData;
|
||||
try {
|
||||
eventData = new JSONObject(expecter.blockForEventData());
|
||||
} catch(Exception ex) {
|
||||
// Log and ignore
|
||||
getAsserter().ok(false, "JS Test", "Error decoding data " + ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eventData.has("result")) {
|
||||
getAsserter().ok(eventData.optBoolean("result"), "JS Test", eventData.optString("msg"));
|
||||
}
|
||||
|
||||
EventDispatcher.sendResponse(eventData, new JSONObject());
|
||||
return eventData.optBoolean("done", false);
|
||||
}
|
||||
}
|
@ -22,6 +22,10 @@ var SelectionHandler = {
|
||||
_activeType: 0, // TYPE_NONE
|
||||
_draggingHandles: false, // True while user drags text selection handles
|
||||
_ignoreCompositionChanges: false, // Persist caret during IME composition updates
|
||||
_prevHandlePositions: [], // Avoid issuing duplicate "TextSelection:Position" messages
|
||||
|
||||
// TargetElement changes (text <--> no text) trigger actionbar UI update
|
||||
_prevTargetElementHasText: null,
|
||||
|
||||
// The window that holds the selection (can be a sub-frame)
|
||||
get _contentWindow() {
|
||||
@ -59,6 +63,7 @@ var SelectionHandler = {
|
||||
Services.obs.addObserver(this, "TextSelection:Position", false);
|
||||
Services.obs.addObserver(this, "TextSelection:End", false);
|
||||
Services.obs.addObserver(this, "TextSelection:Action", false);
|
||||
Services.obs.addObserver(this, "TextSelection:LayerReflow", false);
|
||||
|
||||
BrowserApp.deck.addEventListener("pagehide", this, false);
|
||||
BrowserApp.deck.addEventListener("blur", this, true);
|
||||
@ -73,6 +78,7 @@ var SelectionHandler = {
|
||||
Services.obs.removeObserver(this, "TextSelection:Position");
|
||||
Services.obs.removeObserver(this, "TextSelection:End");
|
||||
Services.obs.removeObserver(this, "TextSelection:Action");
|
||||
Services.obs.removeObserver(this, "TextSelection:LayerReflow");
|
||||
|
||||
BrowserApp.deck.removeEventListener("pagehide", this, false);
|
||||
BrowserApp.deck.removeEventListener("blur", this, true);
|
||||
@ -81,6 +87,18 @@ var SelectionHandler = {
|
||||
|
||||
observe: function sh_observe(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
// Update handle/caret position on page reflow (keyboard open/close,
|
||||
// dynamic DOM changes, orientation updates, etc).
|
||||
case "TextSelection:LayerReflow": {
|
||||
if (this._activeType == this.TYPE_SELECTION) {
|
||||
this._updateCacheForSelection();
|
||||
}
|
||||
if (this._activeType != this.TYPE_NONE) {
|
||||
this._positionHandlesOnChange();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Update caret position on keyboard activity
|
||||
case "TextSelection:UpdateCaretPos":
|
||||
// Generated by IME close, autoCorrection / styling
|
||||
@ -125,6 +143,8 @@ var SelectionHandler = {
|
||||
this._moveSelection(data.handleType == this.HANDLE_TYPE_START, data.x, data.y);
|
||||
|
||||
} else if (this._activeType == this.TYPE_CURSOR) {
|
||||
this._startDraggingHandles();
|
||||
|
||||
// Ignore IMM composition notifications when caret movement starts
|
||||
this._ignoreCompositionChanges = true;
|
||||
|
||||
@ -160,10 +180,13 @@ var SelectionHandler = {
|
||||
|
||||
this._stopDraggingHandles();
|
||||
this._positionHandles();
|
||||
// Changes to handle position can affect selection context and actionbar display
|
||||
this._updateMenu();
|
||||
|
||||
} else if (this._activeType == this.TYPE_CURSOR) {
|
||||
// Act on IMM composition notifications after caret movement ends
|
||||
this._ignoreCompositionChanges = false;
|
||||
this._stopDraggingHandles();
|
||||
this._positionHandles();
|
||||
|
||||
} else {
|
||||
@ -205,7 +228,7 @@ var SelectionHandler = {
|
||||
switch (aEvent.type) {
|
||||
case "scroll":
|
||||
// Maintain position when top-level document is scrolled
|
||||
this._positionHandles();
|
||||
this._positionHandlesOnChange();
|
||||
break;
|
||||
|
||||
case "pagehide":
|
||||
@ -354,8 +377,13 @@ var SelectionHandler = {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine position and show handles, open actionbar
|
||||
this._positionHandles(positions);
|
||||
this._sendMessage("TextSelection:ShowHandles", [this.HANDLE_TYPE_START, this.HANDLE_TYPE_END]);
|
||||
sendMessageToJava({
|
||||
type: "TextSelection:ShowHandles",
|
||||
handles: [this.HANDLE_TYPE_START, this.HANDLE_TYPE_END]
|
||||
});
|
||||
this._updateMenu();
|
||||
return true;
|
||||
},
|
||||
|
||||
@ -433,7 +461,31 @@ var SelectionHandler = {
|
||||
return obj[name];
|
||||
},
|
||||
|
||||
_sendMessage: function(msgType, handles) {
|
||||
addAction: function(action) {
|
||||
if (!action.id)
|
||||
action.id = uuidgen.generateUUID().toString()
|
||||
|
||||
if (this.actions[action.id])
|
||||
throw "Action with id " + action.id + " already added";
|
||||
|
||||
// Update actions list and actionbar UI if active.
|
||||
this.actions[action.id] = action;
|
||||
this._updateMenu();
|
||||
return action.id;
|
||||
},
|
||||
|
||||
removeAction: function(id) {
|
||||
// Update actions list and actionbar UI if active.
|
||||
delete this.actions[id];
|
||||
this._updateMenu();
|
||||
},
|
||||
|
||||
_updateMenu: function() {
|
||||
if (this._activeType == this.TYPE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update actionbar UI.
|
||||
let actions = [];
|
||||
for (let type in this.actions) {
|
||||
let action = this.actions[type];
|
||||
@ -452,31 +504,11 @@ var SelectionHandler = {
|
||||
actions.sort((a, b) => b.order - a.order);
|
||||
|
||||
sendMessageToJava({
|
||||
type: msgType,
|
||||
handles: handles,
|
||||
actions: actions,
|
||||
type: "TextSelection:Update",
|
||||
actions: actions
|
||||
});
|
||||
},
|
||||
|
||||
_updateMenu: function() {
|
||||
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];
|
||||
},
|
||||
|
||||
/*
|
||||
* Actionbar methods.
|
||||
*/
|
||||
@ -632,9 +664,14 @@ var SelectionHandler = {
|
||||
BrowserApp.deck.addEventListener("compositionend", this, false);
|
||||
|
||||
this._activeType = this.TYPE_CURSOR;
|
||||
this._positionHandles();
|
||||
|
||||
this._sendMessage("TextSelection:ShowHandles", [this.HANDLE_TYPE_MIDDLE]);
|
||||
// Determine position and show caret, open actionbar
|
||||
this._positionHandles();
|
||||
sendMessageToJava({
|
||||
type: "TextSelection:ShowHandles",
|
||||
handles: [this.HANDLE_TYPE_MIDDLE]
|
||||
});
|
||||
this._updateMenu();
|
||||
},
|
||||
|
||||
// Target initialization for both TYPE_CURSOR and TYPE_SELECTION
|
||||
@ -905,6 +942,7 @@ var SelectionHandler = {
|
||||
|
||||
_deactivate: function sh_deactivate() {
|
||||
this._stopDraggingHandles();
|
||||
// Hide handle/caret, close actionbar
|
||||
sendMessageToJava({ type: "TextSelection:HideHandles" });
|
||||
|
||||
this._removeObservers();
|
||||
@ -922,6 +960,8 @@ var SelectionHandler = {
|
||||
this._isRTL = false;
|
||||
this._cache = null;
|
||||
this._ignoreCompositionChanges = false;
|
||||
this._prevHandlePositions = [];
|
||||
this._prevTargetElementHasText = null;
|
||||
|
||||
this._activeType = this.TYPE_NONE;
|
||||
},
|
||||
@ -1026,6 +1066,32 @@ var SelectionHandler = {
|
||||
}
|
||||
},
|
||||
|
||||
// Position handles, but avoid superfluous re-positioning (helps during
|
||||
// "TextSelection:LayerReflow", "scroll" of top-level document, etc).
|
||||
_positionHandlesOnChange: function() {
|
||||
// Helper function to compare position messages
|
||||
let samePositions = function(aPrev, aCurr) {
|
||||
if (aPrev.length != aCurr.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < aPrev.length; i++) {
|
||||
if (aPrev[i].left != aCurr[i].left ||
|
||||
aPrev[i].top != aCurr[i].top ||
|
||||
aPrev[i].hidden != aCurr[i].hidden) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
let positions = this._getHandlePositions(this._getScrollPos());
|
||||
if (!samePositions(this._prevHandlePositions, positions)) {
|
||||
this._positionHandles(positions);
|
||||
}
|
||||
},
|
||||
|
||||
// Position handles, allow for re-position, in case user drags handle
|
||||
// to invalid position, then releases, we can put it back where it started
|
||||
// positions is an array of objects with data about handle positions,
|
||||
// which we get from _getHandlePositions.
|
||||
_positionHandles: function sh_positionHandles(positions) {
|
||||
@ -1037,7 +1103,14 @@ var SelectionHandler = {
|
||||
positions: positions,
|
||||
rtl: this._isRTL
|
||||
});
|
||||
this._updateMenu();
|
||||
this._prevHandlePositions = positions;
|
||||
|
||||
// Text state transitions (text <--> no text) will affect selection context and actionbar display
|
||||
let currTargetElementHasText = (this._targetElement.textLength > 0);
|
||||
if (currTargetElementHasText != this._prevTargetElementHasText) {
|
||||
this._prevTargetElementHasText = currTargetElementHasText;
|
||||
this._updateMenu();
|
||||
}
|
||||
},
|
||||
|
||||
subdocumentScrolled: function sh_subdocumentScrolled(aElement) {
|
||||
|
@ -238,6 +238,8 @@ let HomePanels = (function () {
|
||||
},
|
||||
|
||||
"HomePanels:Installed": function handlePanelsInstalled(id) {
|
||||
_assertPanelExists(id);
|
||||
|
||||
let options = _registeredPanels[id]();
|
||||
if (!options.oninstall) {
|
||||
return;
|
||||
@ -249,6 +251,8 @@ let HomePanels = (function () {
|
||||
},
|
||||
|
||||
"HomePanels:Uninstalled": function handlePanelsUninstalled(id) {
|
||||
_assertPanelExists(id);
|
||||
|
||||
let options = _registeredPanels[id]();
|
||||
if (!options.onuninstall) {
|
||||
return;
|
||||
|
@ -275,6 +275,24 @@ DevToolsLoader.prototype = {
|
||||
return this.require.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Define a getter property on the given object that requires the given
|
||||
* module. This enables delaying importing modules until the module is
|
||||
* actually used.
|
||||
*
|
||||
* @param Object obj
|
||||
* The object to define the property on.
|
||||
* @param String property
|
||||
* The property name.
|
||||
* @param String module
|
||||
* The module path.
|
||||
*/
|
||||
lazyRequireGetter: function (obj, property, module) {
|
||||
Object.defineProperty(obj, property, {
|
||||
get: () => this.require(module)
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a URI to the loader.
|
||||
* @param string id
|
||||
|
@ -4,6 +4,7 @@ const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
|
||||
|
||||
// Register a console listener, so console messages don't just disappear
|
||||
|
@ -1,9 +1,6 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const { DevToolsLoader } =
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
|
||||
/**
|
||||
* Ensure that each instance of the Dev Tools loader contains its own loader
|
||||
* instance, and also returns unique objects. This ensures there is no sharing
|
||||
|
@ -1,8 +1,6 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const { DevToolsLoader } =
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
Cu.import("resource://gre/modules/jsdebugger.jsm");
|
||||
addDebuggerToGlobal(this);
|
||||
|
||||
|
14
toolkit/devtools/tests/unit/test_require_lazy.js
Normal file
14
toolkit/devtools/tests/unit/test_require_lazy.js
Normal file
@ -0,0 +1,14 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test devtools.lazyRequireGetter
|
||||
|
||||
function run_test() {
|
||||
const o = {};
|
||||
devtools.lazyRequireGetter(o, "asyncUtils", "devtools/async-utils");
|
||||
const asyncUtils = devtools.require("devtools/async-utils");
|
||||
// XXX: do_check_eq only works on primitive types, so we have this
|
||||
// do_check_true of an equality expression.
|
||||
do_check_true(o.asyncUtils === asyncUtils);
|
||||
}
|
@ -7,3 +7,4 @@ tail =
|
||||
[test_safeErrorString.js]
|
||||
[test_defineLazyPrototypeGetter.js]
|
||||
[test_async-utils.js]
|
||||
[test_require_lazy.js]
|
@ -98,11 +98,17 @@ details.notification.uninstall=%1$S will be uninstalled after you restart %2$S.
|
||||
#LOCALIZATION NOTE (details.notification.upgrade) %1$S is the add-on name, %2$S is brand name
|
||||
details.notification.upgrade=%1$S will be updated after you restart %2$S.
|
||||
|
||||
#LOCALIZATION NOTE (details.experiment.time.daysRemaining) #1 is the number of days from now that the experiment will remain active (detail view).
|
||||
# LOCALIZATION NOTE (details.experiment.time.daysRemaining):
|
||||
# Semicolon-separated list of plural forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# #1 is the number of days from now that the experiment will remain active (detail view).
|
||||
details.experiment.time.daysRemaining=#1 day remaining;#1 days remaining
|
||||
#LOCALIZATION NOTE (details.experiment.time.endsToday) The experiment will end in less than a day (detail view).
|
||||
details.experiment.time.endsToday=Less than a day remaining
|
||||
#LOCALIZATION NOTE (details.experiment.time.daysPassed) #1 is the number of days since the experiment ran (detail view).
|
||||
# LOCALIZATION NOTE (details.experiment.time.daysPassed):
|
||||
# Semicolon-separated list of plural forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# #1 is the number of days since the experiment ran (detail view).
|
||||
details.experiment.time.daysPassed=#1 day ago;#1 days ago
|
||||
#LOCALIZATION NOTE (details.experiment.time.endedToday) The experiment ended less than a day ago (detail view).
|
||||
details.experiment.time.endedToday=Less than a day ago
|
||||
@ -111,11 +117,17 @@ details.experiment.state.active=Active
|
||||
#LOCALIZATION NOTE (details.experiment.state.complete) This experiment is complete (it was previously active) (detail view).
|
||||
details.experiment.state.complete=Complete
|
||||
|
||||
#LOCALIZATION NOTE (experiment.time.daysRemaining) #1 is the number of days from now that the experiment will remain active (list view item).
|
||||
# LOCALIZATION NOTE (experiment.time.daysRemaining):
|
||||
# Semicolon-separated list of plural forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# #1 is the number of days from now that the experiment will remain active (list view item).
|
||||
experiment.time.daysRemaining=#1 day remaining;#1 days remaining
|
||||
#LOCALIZATION NOTE (experiment.time.endsToday) The experiment will end in less than a day (list view item).
|
||||
experiment.time.endsToday=Less than a day remaining
|
||||
#LOCALIZATION NOTE (experiment.time.daysPassed) #1 is the number of days since the experiment ran (list view item).
|
||||
# LOCALIZATION NOTE (experiment.time.daysPassed):
|
||||
# Semicolon-separated list of plural forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# #1 is the number of days since the experiment ran (list view item).
|
||||
experiment.time.daysPassed=#1 day ago;#1 days ago
|
||||
#LOCALIZATION NOTE (experiment.time.endedToday) The experiment ended less than a day ago (list view item).
|
||||
experiment.time.endedToday=Less than a day ago
|
||||
|
Loading…
Reference in New Issue
Block a user