Merge fx-team to m-c.

This commit is contained in:
Ryan VanderMeulen 2014-04-26 20:17:07 -04:00
commit 7b2d394944
92 changed files with 1976 additions and 1380 deletions

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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) {

View File

@ -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
});
}

View 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;

View File

@ -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;
/**

View File

@ -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);
}
}
};

View File

@ -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;

View File

@ -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 => {

View File

@ -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;

View File

@ -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);
}
}
})
};

View File

@ -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;
});

View File

@ -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');
}

View File

@ -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);

View File

@ -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)
}
}
});
}));
}

View File

@ -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;

View 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;

View File

@ -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;
});

View File

@ -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) {

View File

@ -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) {

View 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);

View File

@ -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)

View File

@ -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;

View File

@ -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;
}

View File

@ -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)

View File

@ -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);

View File

@ -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);

View File

@ -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");

View File

@ -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);

View File

@ -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");
};

View File

@ -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;
}
}
};

View File

@ -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);

View File

@ -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);
});
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -944,7 +944,6 @@
collapsed="true"
customizable="true">
<toolbaritem id="personal-bookmarks"
flex="1"
title="&bookmarksToolbarItem.label;"
cui-areatype="toolbar"
removable="true">

View File

@ -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";

View File

@ -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 {

View File

@ -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,

View File

@ -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;" />

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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);
}
});

View File

@ -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;
});

View File

@ -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);
});

View File

@ -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();
});

View File

@ -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
});

View File

@ -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);
}
});

View File

@ -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);
}
});

View File

@ -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;
});

View File

@ -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);
}
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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

View File

@ -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();

View File

@ -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 };
}

View File

@ -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");

View File

@ -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());

View File

@ -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;
}

View File

@ -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.

View File

@ -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">

View File

@ -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

View File

@ -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 youd find it interesting and because it supports Mozillas mission.">
<!ENTITY newtab.sponsored.trial.message "This Trial Sponsor site was suggested because we hoped youd find it interesting and because it supports Mozillas mission.">
<!ENTITY newtab.panel.link.text "Learn more…">

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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);

View File

@ -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);
}

View File

@ -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,

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View 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>

View 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);
}
}

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View 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);
}

View File

@ -7,3 +7,4 @@ tail =
[test_safeErrorString.js]
[test_defineLazyPrototypeGetter.js]
[test_async-utils.js]
[test_require_lazy.js]

View File

@ -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