Merge m-c to m-i

This commit is contained in:
Phil Ringnalda 2013-12-07 08:25:43 -08:00
commit a2caea7be7
38 changed files with 2302 additions and 688 deletions

View File

@ -35,13 +35,13 @@ geolocation API in Firefox.
## Using Geolocation in an Add-on ##
Suppose we want to use the
[geolocation API built into Firefox](https://developer.mozilla.org/en/using_geolocation).
[geolocation API built into Firefox](https://developer.mozilla.org/en-US/docs/WebAPI/Using_geolocation).
The SDK doesn't provide an API to access geolocation, but we can
[access the underlying XPCOM API using `require("chrome")`](dev-guide/guides/xul-migration.html#xpcom).
The following add-on adds a [button to the toolbar](dev-guide/tutorials/adding-toolbar-button.html):
when the user clicks the button, it loads the
[XPCOM nsIDOMGeoGeolocation](https://developer.mozilla.org/en/XPCOM_Interface_Reference/NsIDOMGeoGeolocation)
[XPCOM nsIDOMGeoGeolocation](https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/NsIDOMGeoGeolocation)
object, and retrieves the user's current position:
var {Cc, Ci} = require("chrome");
@ -88,7 +88,7 @@ info: longitude: 93.0785269
</pre>
So far, so good. But the geolocation guide on MDN tells us that we must
[ask the user for permission](https://developer.mozilla.org/en/using_geolocation#Prompting_for_permission)
[ask the user for permission](https://developer.mozilla.org/en-US/docs/WebAPI/Using_geolocation#Prompting_for_permission)
before using the API.
So we'll extend the add-on to include an adapted version of the code in
@ -384,4 +384,4 @@ to add your name as the author, choose a distribution license, and so on.
To see some of the modules people have already developed, see the page of
[community-developed modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules).
To learn how to use third-party modules in your own code, see the
[tutorial on adding menu items](dev-guide/tutorials/adding-menus.html).
[tutorial on adding menu items](dev-guide/tutorials/adding-menus.html).

View File

@ -13,15 +13,17 @@ When the method is invoked on an instance of the object, the original
function is called. It is passed the object instance (i.e. `this`) as
the first parameter, followed by any parameters passed into the method.
let { method } = require("sdk/lang/functional");
let myNumber = {
const { method } = require("sdk/lang/functional");
const times = (target, x) => target.number *= x;
const add = (target, x) => target.number += x;
const myNumber = {
times: method(times),
add: method(add),
number: 0
};
function times (target, x) { return target.number *= x; }
function add (target, x) { return target.number += x; }
console.log(myNumber.number); // 0
myNumber.add(10); // 10
@ -44,8 +46,8 @@ wait (i.e. `setTimeout(function () { ... }, 0)`), except that the wrapped functi
may be reused and does not need to be repeated each time. This also enables you
to use these functions as event listeners.
let { defer } = require("sdk/lang/functional");
let fn = defer(function myEvent (event, value) {
const { defer } = require("sdk/lang/functional");
const fn = defer((event, value) => {
console.log(event + " : " + value);
});
@ -74,16 +76,11 @@ An alias for [defer](modules/sdk/lang/functional.html#defer(fn)).
Invokes `callee`, passing `params` as an argument and `self` as `this`.
Returns the value that is returned by `callee`.
let { invoke } = require("sdk/lang/functional");
const { invoke } = require("sdk/lang/functional");
const sum = (...args) => args.reduce((a, b) => a + b);
invoke(sum, [1,2,3,4,5], null); // 15
function sum () {
return Array.slice(arguments).reduce(function (a, b) {
return a + b;
});
}
@param callee {function}
Function to invoke.
@param params {Array}
@ -98,9 +95,9 @@ Returns the value that is returned by `callee`.
@function
Takes a function and bind values to one or more arguments, returning a new function of smaller arity.
let { partial } = require("sdk/lang/functional");
let add = function add (x, y) { return x + y; }
let addOne = partial(add, 1);
const { partial } = require("sdk/lang/functional");
const add = (x, y) => x + y;
const addOne = partial(add, 1);
addOne(5); // 6
addOne(10); // 11
@ -122,14 +119,16 @@ Returns the [composition](http://en.wikipedia.org/wiki/Function_composition_(com
return value of the function that follows. In math terms, composing the functions
`f()`, `g()`, and `h()` produces `f(g(h()))`.
let { compose } = require("sdk/lang/functional");
const { compose } = require("sdk/lang/functional");
let welcome = compose(exclaim, greet);
const square = x => x * x;
const increment = x => x + 1;
welcome('moe'); // "hi: moe!";
const f1 = compose(increment, square);
f1(5); // => 26
function greet (name) { return "hi: " + name; }
function exclaim (statement) { return statement + "!"; }
const f2 = compose(square, increment);
f2(5); // => 36
@param fn... {function}
Takes a variable number of functions as arguments and composes them from right to left.
@ -144,16 +143,14 @@ Returns the first function passed as an argument to the second,
allowing you to adjust arguments, run code before and after, and
conditionally execute the original function.
let { wrap } = require("sdk/lang/functional");
const { wrap } = require("sdk/lang/functional");
let wrappedHello = wrap(hello, function (fn, name) {
return "before, " + fn(name) + "after";
});
const hello = name => "hello: " + name;
const wrappedHello = wrap(hello, (fn, name) =>
"before, " + fn(name) + "after");
wrappedHello("moe"); // "before, hello: moe, after"
function hello (name) { return "hello: " + name; }
@param fn {function}
The function to be passed into the `wrapper` function.
@ -170,8 +167,8 @@ conditionally execute the original function.
@function
Returns the same value that is used as the argument. In math: f(x) = x.
let { identity } = require("sdk/lang/functional");
let x = 5;
const { identity } = require("sdk/lang/functional");
const x = 5;
identity(x); // 5
@param value {mixed}
@ -190,15 +187,15 @@ storing the result, based on the arguments to the original function. The
default `hashFunction` just uses the first argument to the memoized function as
the key.
let { memoize } = require("sdk/lang/functional");
const { memoize } = require("sdk/lang/functional");
let memoizedFn = memoize(primeFactorization);
const memoizedFn = memoize(primeFactorization);
memoizedFn(50); // Returns [2, 5, 5], had to compute
memoizedFn(100); // Returns [2, 2, 5, 5], had to compute
memoizedFn(50); // Returns [2, 5, 5] again, but pulled from cache
function primeFactorization (x) {
const primeFactorization = x => {
// Some tricky stuff
}
@ -209,16 +206,14 @@ the key.
// function will just parse the last name, as our naive
// implementation assumes that they will share the same lineage
let getLineage = memoize(function (name) {
const getLineage = memoize(name => {
// computes lineage
return data;
}, hasher);
// Hashing function takes a string of first and last name
// and returns the last name.
function hasher (input) {
return input.split(" ")[1];
}
const hasher = input => input.split(" ")[1];
getLineage("homer simpson"); // Computes and returns information for "simpson"
getLineage("lisa simpson"); // Returns cached for "simpson"
@ -240,12 +235,11 @@ Much like `setTimeout`, `delay` invokes a function after waiting a set number of
milliseconds. If you pass additional, optional, arguments, they will be forwarded
on to the function when it is invoked.
let { delay } = require("sdk/lang/functional");
const { delay } = require("sdk/lang/functional");
const printAdd = (a, b) console.log(a + "+" + b + "=" + (a+b));
delay(printAdd, 2000, 5, 10);
// Prints "5+10=15" in two seconds (2000ms)
function printAdd (a, b) { console.log(a + "+" + b + "=" + (a+b)); }
@param fn {function}
A function to be delayed.
@ -264,8 +258,8 @@ Repeated calls to the modified function will have no effect, returning
the value from the original call. Useful for initialization functions, instead
of having to set a boolean flag and checking it later.
let { once } = require("sdk/lang/functional");
let setup = once(function (env) {
const { once } = require("sdk/lang/functional");
const setup = once(env => {
// initializing important things
console.log("successfully initialized " + env);
return 1; // Assume success and return 1
@ -273,7 +267,7 @@ of having to set a boolean flag and checking it later.
setup('dev'); // returns 1
// prints "successfully initialized dev"
// Future attempts to call this function just return the cached
// value that was returned previously
setup('production'); // Returns 1
@ -286,16 +280,156 @@ of having to set a boolean flag and checking it later.
The wrapped `fn` that can only be executed once.
</api>
<api name="chain">
<api name="cache">
@function
An alias for [once](modules/sdk/lang/functional.html#once(fn)).
</api>
<api name="complement">
@function
Takes a `f` function and returns a function that takes the same
arguments as `f`, has the same effects, if any, and returns the
opposite truth value.
const { complement } = require("sdk/lang/functional");
let isOdd = x => Boolean(x % 2);
isOdd(1) // => true
isOdd(2) // => false
let isEven = complement(isOdd);
isEven(1) // => false
isEven(2) // => true
@param lambda {function}
The function to compose from
@returns {boolean}
`!lambda(...)`
</api>
<api name="constant">
@function
Constructs function that returns `x` no matter what is it
invoked with.
const { constant } = require("sdk/lang/functional");
const one = constant(1);
one(); // => 1
one(2); // => 1
one.apply({}, 3); // => 1
@param x {object}
Value that will be returned by composed function
@returns {function}
</api>
<api name="apply">
@function
Apply function that behaves like `apply` in other functional
languages:
const { apply } = require("sdk/lang/functional");
const dashify = (...args) => args.join("-");
apply(dashify, 1, [2, 3]); // => "1-2-3"
apply(dashify, "a"); // => "a"
apply(dashify, ["a", "b"]); // => "a-b"
apply(dashify, ["a", "b"], "c"); // => "a,b-c"
apply(dashify, [1, 2], [3, 4]); // => "1,2-3-4"
@param f {function}
function to be invoked
</api>
<api name="flip">
@function
Returns function identical to given `f` but with flipped order
of arguments.
const { flip } = require("sdk/lang/functional");
const append = (left, right) => left + " " + right;
const prepend = flip(append);
append("hello", "world") // => "hello world"
prepend("hello", "world") // => "world hello"
@param f {function}
function whose arguments should to be flipped
@returns {function}
function with flipped arguments
</api>
<api name="when">
@function
Takes `p` predicate, `consequent` function and an optional
`alternate` function and composes function that returns
application of arguments over `consequent` if application over
`p` is `true` otherwise returns application over `alternate`.
If `alternate` is not a function returns `undefined`.
const { when } = require("sdk/lang/functional");
function Point(x, y) {
this.x = x
this.y = y
}
const isPoint = x => x instanceof Point;
const inc = when(isPoint, ({x, y}) => new Point(x + 1, y + 1));
inc({}); // => undefined
inc(new Point(0, 0)); // => { x: 1, y: 1 }
const axis = when(isPoint,
({ x, y }) => [x, y],
_ => [0, 0]);
axis(new Point(1, 4)); // => [1, 4]
axis({ foo: "bar" }); // => [0, 0]
@param p {function}
predicate function whose return value determines to which
function be delegated control.
@param consequent {function}
function to which arguments are applied if `predicate` returned
`true`.
@param alternate {function}
function to which arguments are applied if `predicate` returned
`false`.
@returns {object|string|number|function}
Return value from `consequent` if `p` returned `true` or return
value from `alternate` if `p` returned `false`. If `alternate`
is not provided and `p` returned `false` then `undefined` is
returned.
</api>
<api name="chainable">
@function
Creates a version of the input function that will return `this`.
let { chain } = require("sdk/lang/functional");
const { chainable } = require("sdk/lang/functional");
function Person (age) { this.age = age; }
Person.prototype.happyBirthday = chain(function () this.age++);
Person.prototype.happyBirthday = chainable(function() {
return this.age++
});
let person = new Person(30);
const person = new Person(30);
person
.happyBirthday()
@ -311,7 +445,126 @@ Creates a version of the input function that will return `this`.
The wrapped function that executes `fn` and returns `this`.
</api>
<api name="cache">
<api name="field">
@function
An alias for [once](modules/sdk/lang/functional.html#once(fn)).
Takes field `name` and `target` and returns value of that field.
If `target` is `null` or `undefined` it would be returned back
instead of attempt to access it's field. Function is implicitly
curried, this allows accessor function generation by calling it
with only `name` argument.
const { field } = require("sdk/lang/functional");
field("x", { x: 1, y: 2}); // => 1
field("x")({ x: 1 }); // => 1
field("x", { y: 2 }); // => undefiend
const getX = field("x");
getX({ x: 1 }); // => 1
getX({ y: 1 }); // => undefined
getX(null); // => null
@param name {string}
Name of the field to be returned
@param target {object}
Target to get a field by the given `name` from
@returns {object|function|string|number|boolean}
Field value
</api>
<api name="query">
@function
Takes `.` delimited string representing `path` to a nested field
and a `target` to get it from. For convinience function is
implicitly curried, there for accessors can be created by invoking
it with just a `path` argument.
const { query } = require("sdk/lang/functional");
query("x", { x: 1, y: 2}); // => 1
query("top.x", { x: 1 }); // => undefiend
query("top.x", { top: { x: 2 } }); // => 2
const topX = query("top.x");
topX({ top: { x: 1 } }); // => 1
topX({ y: 1 }); // => undefined
topX(null); // => null
@param path {string}
`.` delimited path to a field
@param target {object}
Target to get a field by the given `name` from
@returns {object|function|string|number|boolean}
Field value
</api>
<api name="isInstance">
@function
Takes `Type` (constructor function) and a `value` and returns
`true` if `value` is instance of the given `Type`. Function is
implicitly curried this allows predicate generation by calling
function with just first argument.
const { isInstance } = require("sdk/lang/functional");
function X() {}
function Y() {}
let isX = isInstance(X);
isInstance(X, new X); // true
isInstance(X)(new X); // true
isInstance(X, new Y); // false
isInstance(X)(new Y); // false
isX(new X); // true
isX(new Y); // false
@param Type {function}
Type (constructor function)
@param instance {object}
Instance to test
@returns {boolean}
</api>
<api name="is">
@function
Functions takes `expected` and `actual` values and returns `true` if
`expected === actual`. If invoked with just one argument returns pratially
applied function, which can be invoked to provide a second argument, this
is handy with `map`, `filter` and other high order functions:
const { is } = require("sdk/util/oops");
[ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ]
@param expected {object|string|number|boolean}
@param actual {object|string|number|boolean}
@returns {boolean}
</api>
<api name="isnt">
@function
Functions takes `expected` and `actual` values and returns `true` if
`expected !== actual`. If invoked with just one argument returns pratially
applied function, which can be invoked with a second argument, which is
handy with `map`, `filter` and other high order functions:
const { isnt } = require("sdk/util/oops");
[ 1, 0, 1, 0, 1 ].map(isnt(0)) // => [ true, false, true, false, true ]
@param expected {object|string|number|boolean}
@param actual {object|string|number|boolean}
@returns {boolean}
</api>

View File

@ -9,7 +9,7 @@ module.metadata = {
};
const { on, once, off, setListeners } = require('./core');
const { method, chain } = require('../lang/functional');
const { method, chainable } = require('../lang/functional');
const { Class } = require('../core/heritage');
/**
@ -43,7 +43,7 @@ const EventTarget = Class({
* console.log('data received: ' + data)
* })
*/
on: chain(method(on)),
on: chainable(method(on)),
/**
* Registers an event `listener` that is called once the next time an event
* of the specified `type` is emitted.
@ -52,7 +52,7 @@ const EventTarget = Class({
* @param {Function} listener
* The listener function that processes the event.
*/
once: chain(method(once)),
once: chainable(method(once)),
/**
* Removes an event `listener` for the given event `type`.
* @param {String} type

View File

@ -2,7 +2,7 @@
* 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/. */
// Disclaimer: Most of the functions in this module implement APIs from
// Disclaimer: Some of the functions in this module implement APIs from
// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
// those goes to him.
@ -12,19 +12,33 @@ module.metadata = {
"stability": "unstable"
};
const { setImmediate, setTimeout } = require("../timers");
const { deprecateFunction } = require("../util/deprecate");
const { setImmediate, setTimeout } = require("../timers");
const arity = f => f.arity || f.length;
const name = f => f.displayName || f.name;
const derive = (f, source) => {
f.displayName = name(source);
f.arity = arity(source);
return f;
};
/**
* Takes `lambda` function and returns a method. When returned method is
* invoked it calls wrapped `lambda` and passes `this` as a first argument
* and given argument as rest.
* Takes variadic numeber of functions and returns composed one.
* Returned function pushes `this` pseudo-variable to the head
* of the passed arguments and invokes all the functions from
* left to right passing same arguments to them. Composite function
* returns return value of the right most funciton.
*/
function method(lambda) {
return function method() {
return lambda.apply(null, [this].concat(Array.slice(arguments)));
}
}
const method = (...lambdas) => {
return function method(...args) {
args.unshift(this);
return lambdas.reduce((_, lambda) => lambda.apply(this, args),
void(0));
};
};
exports.method = method;
/**
@ -34,24 +48,13 @@ exports.method = method;
* function is reused, instead of creating a new one each time. This also allows
* to use this functions as event listeners.
*/
function defer(f) {
return function deferred() setImmediate(invoke, f, arguments, this);
}
const defer = f => derive(function(...args) {
setImmediate(invoke, f, args, this);
}, f);
exports.defer = defer;
// Exporting `remit` alias as `defer` may conflict with promises.
exports.remit = defer;
/*
* Takes a funtion and returns a wrapped function that returns `this`
*/
function chain(f) {
return function chainable(...args) {
f.apply(this, args);
return this;
};
}
exports.chain = chain;
/**
* Invokes `callee` by passing `params` as an arguments and `self` as `this`
* pseudo-variable. Returns value that is returned by a callee.
@ -62,7 +65,7 @@ exports.chain = chain;
* @param {Object} self
* Object to be passed as a `this` pseudo variable.
*/
function invoke(callee, params, self) callee.apply(self, params);
const invoke = (callee, params, self) => callee.apply(self, params);
exports.invoke = invoke;
/**
@ -74,14 +77,16 @@ exports.invoke = invoke;
*
* @returns The new function with binded values
*/
function partial(fn) {
if (typeof fn !== "function")
throw new TypeError(String(fn) + " is not a function");
const partial = (f, ...curried) => {
if (typeof(f) !== "function")
throw new TypeError(String(f) + " is not a function");
let args = Array.slice(arguments, 1);
return function() fn.apply(this, args.concat(Array.slice(arguments)));
}
let fn = derive(function(...args) {
return f.apply(this, curried.concat(args));
}, f);
fn.arity = arity(f) - curried.length;
return fn;
};
exports.partial = partial;
/**
@ -98,12 +103,11 @@ exports.partial = partial;
* console.log(sum(2, 2)) // 4
* console.log(sum(2)(4)) // 6
*/
var curry = new function() {
function currier(fn, arity, params) {
const curry = new function() {
const currier = (fn, arity, params) => {
// Function either continues to curry arguments or executes function
// if desired arguments have being collected.
return function curried() {
var input = Array.slice(arguments);
const curried = function(...input) {
// Prepend all curried arguments to the given arguments.
if (params) input.unshift.apply(input, params);
// If expected number of arguments has being collected invoke fn,
@ -111,11 +115,12 @@ var curry = new function() {
return (input.length >= arity) ? fn.apply(this, input) :
currier(fn, arity, input);
};
}
curried.arity = arity - (params ? params.length : 0);
return function curry(fn) {
return currier(fn, fn.length);
}
return curried;
};
return fn => currier(fn, arity(fn));
};
exports.curry = curry;
@ -131,12 +136,12 @@ exports.curry = curry;
*
* welcome('moe'); // => 'hi: moe!'
*/
function compose() {
let lambdas = Array.slice(arguments);
return function composed() {
let args = Array.slice(arguments), index = lambdas.length;
function compose(...lambdas) {
return function composed(...args) {
let index = lambdas.length;
while (0 <= --index)
args = [ lambdas[index].apply(this, args) ];
args = [lambdas[index].apply(this, args)];
return args[0];
};
}
@ -155,16 +160,15 @@ exports.compose = compose;
*
* hello(); // => 'before, hello: moe, after'
*/
function wrap(f, wrapper) {
return function wrapped()
wrapper.apply(this, [ f ].concat(Array.slice(arguments)))
};
const wrap = (f, wrapper) => derive(function wrapped(...args) {
return wrapper.apply(this, [f].concat(args));
}, f);
exports.wrap = wrap;
/**
* Returns the same value that is used as the argument. In math: f(x) = x
*/
function identity(value) value
const identity = value => value;
exports.identity = identity;
/**
@ -174,14 +178,25 @@ exports.identity = identity;
* the arguments to the original function. The default hashFunction just uses
* the first argument to the memoized function as the key.
*/
function memoize(f, hasher) {
const memoize = (f, hasher) => {
let memo = Object.create(null);
let cache = new WeakMap();
hasher = hasher || identity;
return function memoizer() {
let key = hasher.apply(this, arguments);
return key in memo ? memo[key] : (memo[key] = f.apply(this, arguments));
};
}
return derive(function memoizer(...args) {
const key = hasher.apply(this, args);
const type = typeof(key);
if (key && (type === "object" || type === "function")) {
if (!cache.has(key))
cache.set(key, f.apply(this, args));
return cache.get(key);
}
else {
if (!(key in memo))
memo[key] = f.apply(this, args);
return memo[key];
}
}, f);
};
exports.memoize = memoize;
/**
@ -189,9 +204,8 @@ exports.memoize = memoize;
* the optional arguments, they will be forwarded on to the function when it is
* invoked.
*/
function delay(f, ms) {
let args = Array.slice(arguments, 2);
setTimeout(function(context) { return f.apply(context, args); }, ms, this);
const delay = function delay(f, ms, ...args) {
setTimeout(() => f.apply(this, args), ms);
};
exports.delay = delay;
@ -201,10 +215,116 @@ exports.delay = delay;
* the original call. Useful for initialization functions, instead of having to
* set a boolean flag and then check it later.
*/
function once(f) {
const once = f => {
let ran = false, cache;
return function() ran ? cache : (ran = true, cache = f.apply(this, arguments))
return derive(function(...args) {
return ran ? cache : (ran = true, cache = f.apply(this, args));
}, f);
};
exports.once = once;
// export cache as once will may be conflicting with event once a lot.
exports.cache = once;
// Takes a `f` function and returns a function that takes the same
// arguments as `f`, has the same effects, if any, and returns the
// opposite truth value.
const complement = f => derive(function(...args) {
return args.length < arity(f) ? complement(partial(f, ...args)) :
!f.apply(this, args);
}, f);
exports.complement = complement;
// Constructs function that returns `x` no matter what is it
// invoked with.
const constant = x => _ => x;
exports.constant = constant;
// Takes `p` predicate, `consequent` function and an optional
// `alternate` function and composes function that returns
// application of arguments over `consequent` if application over
// `p` is `true` otherwise returns application over `alternate`.
// If `alternate` is not a function returns `undefined`.
const when = (p, consequent, alternate) => {
if (typeof(alternate) !== "function" && alternate !== void(0))
throw TypeError("alternate must be a function");
if (typeof(consequent) !== "function")
throw TypeError("consequent must be a function");
return function(...args) {
return p.apply(this, args) ?
consequent.apply(this, args) :
alternate && alternate.apply(this, args);
};
};
exports.when = when;
// Apply function that behaves as `apply` does in lisp:
// apply(f, x, [y, z]) => f.apply(f, [x, y, z])
// apply(f, x) => f.apply(f, [x])
const apply = (f, ...rest) => f.apply(f, rest.concat(rest.pop()));
exports.apply = apply;
// Returns function identical to given `f` but with flipped order
// of arguments.
const flip = f => derive(function(...args) {
return f.apply(this, args.reverse());
}, f);
exports.flip = flip;
// Takes field `name` and `target` and returns value of that field.
// If `target` is `null` or `undefined` it would be returned back
// instead of attempt to access it's field. Function is implicitly
// curried, this allows accessor function generation by calling it
// with only `name` argument.
const field = curry((name, target) =>
// Note: Permisive `==` is intentional.
target == null ? target : target[name]);
exports.field = field;
// Takes `.` delimited string representing `path` to a nested field
// and a `target` to get it from. For convinience function is
// implicitly curried, there for accessors can be created by invoking
// it with just a `path` argument.
const query = curry((path, target) => {
const names = path.split(".");
const count = names.length;
let index = 0;
let result = target;
// Note: Permisive `!=` is intentional.
while (result != null && index < count) {
result = result[names[index]];
index = index + 1;
}
return result;
});
exports.query = query;
// Takes `Type` (constructor function) and a `value` and returns
// `true` if `value` is instance of the given `Type`. Function is
// implicitly curried this allows predicate generation by calling
// function with just first argument.
const isInstance = curry((Type, value) => value instanceof Type);
exports.isInstance = isInstance;
/*
* Takes a funtion and returns a wrapped function that returns `this`
*/
const chainable = f => derive(function(...args) {
f.apply(this, args);
return this;
}, f);
exports.chainable = chainable;
exports.chain =
deprecateFunction(chainable, "Function `chain` was renamed to `chainable`");
// Functions takes `expected` and `actual` values and returns `true` if
// `expected === actual`. Returns curried function if called with less then
// two arguments.
//
// [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ]
const is = curry((expected, actual) => actual === expected);
exports.is = is;
const isnt = complement(is);
exports.isnt = isnt;

View File

@ -1,21 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { setTimeout } = require('sdk/timers');
const utils = require('sdk/lang/functional');
const { invoke, defer, partial, compose, memoize, once, delay, wrap, curry, chain } = utils;
const { invoke, defer, partial, compose, memoize, once, is, isnt,
delay, wrap, curry, chainable, field, query, isInstance } = utils;
const { LoaderWithHookedConsole } = require('sdk/test/loader');
exports['test forwardApply'] = function(assert) {
function sum(b, c) this.a + b + c
function sum(b, c) { return this.a + b + c; }
assert.equal(invoke(sum, [2, 3], { a: 1 }), 6,
'passed arguments and pseoude-variable are used');
assert.equal(invoke(sum.bind({ a: 2 }), [2, 3], { a: 1 }), 7,
'bounded `this` pseoudo variable is used');
}
};
exports['test deferred function'] = function(assert, done) {
let nextTurn = false;
@ -26,13 +27,13 @@ exports['test deferred function'] = function(assert, done) {
done();
}
let fixture = { a: 1, method: defer(sum) }
let fixture = { a: 1, method: defer(sum) };
fixture.method(2, 3);
nextTurn = true;
};
exports['test partial function'] = function(assert) {
function sum(b, c) this.a + b + c;
function sum(b, c) { return this.a + b + c; }
let foo = { a : 5 };
@ -68,11 +69,11 @@ exports['test compose'] = function(assert) {
let target = {
name: 'Joe',
greet: compose(function exclaim(sentence) {
return sentence + '!'
return sentence + '!';
}, function(title) {
return 'hi : ' + title + ' ' + this.name;
})
}
};
assert.equal(target.greet('Mr'), 'hi : Mr Joe!',
'this can be passed in');
@ -106,7 +107,7 @@ exports['test wrap'] = function(assert) {
assert.equal(target.hi(), 'Hello Matteo', 'works with this');
function noop() { };
function noop() { }
let wrapped = wrap(noop, function(f) {
return Array.slice(arguments);
});
@ -117,7 +118,7 @@ exports['test wrap'] = function(assert) {
};
exports['test memoize'] = function(assert) {
function fib(n) n < 2 ? n : fib(n - 1) + fib(n - 2)
const fib = n => n < 2 ? n : fib(n - 1) + fib(n - 2);
let fibnitro = memoize(fib);
assert.equal(fib(10), 55,
@ -125,7 +126,7 @@ exports['test memoize'] = function(assert) {
assert.equal(fibnitro(10), 55,
'a memoized version of fibonacci produces identical results');
function o(key, value) { return value; };
function o(key, value) { return value; }
let oo = memoize(o), v1 = {}, v2 = {};
@ -136,12 +137,12 @@ exports['test memoize'] = function(assert) {
assert.notEqual(oo(1), oo(2), 'values do not override');
assert.equal(o(3, v2), oo(2, 3), 'returns same value as un-memoized');
let get = memoize(function(attribute) this[attribute])
let target = { name: 'Bob', get: get }
let get = memoize(function(attribute) { return this[attribute]; });
let target = { name: 'Bob', get: get };
assert.equal(target.get('name'), 'Bob', 'has correct `this`');
assert.equal(target.get.call({ name: 'Jack' }, 'name'), 'Bob',
'name is memoized')
'name is memoized');
assert.equal(get('name'), 'Bob', 'once memoized can be called without this');
};
@ -155,13 +156,13 @@ exports['test delay'] = function(assert, done) {
};
exports['test delay with this'] = function(assert, done) {
let context = {}
let context = {};
delay.call(context, function(name) {
assert.equal(this, context, 'this was passed in');
assert.equal(name, 'Tom', 'argument was passed in');
done();
}, 10, 'Tom');
}
};
exports['test once'] = function(assert) {
let n = 0;
@ -172,7 +173,12 @@ exports['test once'] = function(assert) {
assert.equal(n, 1, 'only incremented once');
let target = { state: 0, update: once(function() this.state ++ ) };
let target = {
state: 0,
update: once(function() {
return this.state ++;
})
};
target.update();
target.update();
@ -182,7 +188,7 @@ exports['test once'] = function(assert) {
exports['test once with argument'] = function(assert) {
let n = 0;
let increment = once(function(a) n++);
let increment = once(a => n++);
increment();
increment('foo');
@ -195,11 +201,121 @@ exports['test once with argument'] = function(assert) {
assert.equal(n, 1, 'only incremented once');
};
exports['test chain'] = function (assert) {
exports['test complement'] = assert => {
let { complement } = require("sdk/lang/functional");
let isOdd = x => Boolean(x % 2);
assert.equal(isOdd(1), true);
assert.equal(isOdd(2), false);
let isEven = complement(isOdd);
assert.equal(isEven(1), false);
assert.equal(isEven(2), true);
let foo = {};
let isFoo = function() { return this === foo; };
let insntFoo = complement(isFoo);
assert.equal(insntFoo.call(foo), false);
assert.equal(insntFoo.call({}), true);
};
exports['test constant'] = assert => {
let { constant } = require("sdk/lang/functional");
let one = constant(1);
assert.equal(one(1), 1);
assert.equal(one(2), 1);
};
exports['test apply'] = assert => {
let { apply } = require("sdk/lang/functional");
let dashify = (...args) => args.join("-");
assert.equal(apply(dashify, 1, [2, 3]), "1-2-3");
assert.equal(apply(dashify, "a"), "a");
assert.equal(apply(dashify, ["a", "b"]), "a-b");
assert.equal(apply(dashify, ["a", "b"], "c"), "a,b-c");
assert.equal(apply(dashify, [1, 2], [3, 4]), "1,2-3-4");
};
exports['test flip'] = assert => {
let { flip } = require("sdk/lang/functional");
let append = (left, right) => left + " " + right;
let prepend = flip(append);
assert.equal(append("hello", "world"), "hello world");
assert.equal(prepend("hello", "world"), "world hello");
let wrap = function(left, right) {
return left + " " + this + " " + right;
};
let invertWrap = flip(wrap);
assert.equal(wrap.call("@", "hello", "world"), "hello @ world");
assert.equal(invertWrap.call("@", "hello", "world"), "world @ hello");
let reverse = flip((...args) => args);
assert.deepEqual(reverse(1, 2, 3, 4), [4, 3, 2, 1]);
assert.deepEqual(reverse(1), [1]);
assert.deepEqual(reverse(), []);
// currying still works
let prependr = curry(prepend);
assert.equal(prependr("hello", "world"), "world hello");
assert.equal(prependr("hello")("world"), "world hello");
};
exports["test when"] = assert => {
let { when } = require("sdk/lang/functional");
let areNums = (...xs) => xs.every(x => typeof(x) === "number");
let sum = when(areNums, (...xs) => xs.reduce((y, x) => x + y, 0));
assert.equal(sum(1, 2, 3), 6);
assert.equal(sum(1, 2, "3"), undefined);
let multiply = when(areNums,
(...xs) => xs.reduce((y, x) => x * y, 1),
(...xs) => xs);
assert.equal(multiply(2), 2);
assert.equal(multiply(2, 3), 6);
assert.deepEqual(multiply(2, "4"), [2, "4"]);
function Point(x, y) {
this.x = x;
this.y = y;
}
let isPoint = x => x instanceof Point;
let inc = when(isPoint, ({x, y}) => new Point(x + 1, y + 1));
assert.equal(inc({}), undefined);
assert.deepEqual(inc(new Point(0, 0)), { x: 1, y: 1 });
let axis = when(isPoint,
({ x, y }) => [x, y],
_ => [0, 0]);
assert.deepEqual(axis(new Point(1, 4)), [1, 4]);
assert.deepEqual(axis({ foo: "bar" }), [0, 0]);
};
exports["test chainable"] = function(assert) {
let Player = function () { this.volume = 5; };
Player.prototype = {
setBand: chain(function (band) this.band = band),
incVolume: chain(function () this.volume++)
setBand: chainable(function (band) { return (this.band = band); }),
incVolume: chainable(function () { return this.volume++; })
};
let player = new Player();
player
@ -210,4 +326,97 @@ exports['test chain'] = function (assert) {
assert.equal(player.volume, 11, 'accepts no arguments in chain');
};
exports["test field"] = assert => {
let Num = field("constructor", 0);
assert.equal(Num.name, Number.name);
assert.ok(typeof(Num), "function");
let x = field("x");
[
[field("foo", { foo: 1 }), 1],
[field("foo")({ foo: 1 }), 1],
[field("bar", {}), undefined],
[field("bar")({}), undefined],
[field("hey", undefined), undefined],
[field("hey")(undefined), undefined],
[field("how", null), null],
[field("how")(null), null],
[x(1), undefined],
[x(undefined), undefined],
[x(null), null],
[x({ x: 1 }), 1],
[x({ x: 2 }), 2],
].forEach(([actual, expected]) => assert.equal(actual, expected));
};
exports["test query"] = assert => {
let Num = query("constructor", 0);
assert.equal(Num.name, Number.name);
assert.ok(typeof(Num), "function");
let x = query("x");
let xy = query("x.y");
[
[query("foo", { foo: 1 }), 1],
[query("foo")({ foo: 1 }), 1],
[query("foo.bar", { foo: { bar: 2 } }), 2],
[query("foo.bar")({ foo: { bar: 2 } }), 2],
[query("foo.bar", { foo: 1 }), undefined],
[query("foo.bar")({ foo: 1 }), undefined],
[x(1), undefined],
[x(undefined), undefined],
[x(null), null],
[x({ x: 1 }), 1],
[x({ x: 2 }), 2],
[xy(1), undefined],
[xy(undefined), undefined],
[xy(null), null],
[xy({ x: 1 }), undefined],
[xy({ x: 2 }), undefined],
[xy({ x: { y: 1 } }), 1],
[xy({ x: { y: 2 } }), 2]
].forEach(([actual, expected]) => assert.equal(actual, expected));
};
exports["test isInstance"] = assert => {
function X() {}
function Y() {}
let isX = isInstance(X);
[
isInstance(X, new X()),
isInstance(X)(new X()),
!isInstance(X, new Y()),
!isInstance(X)(new Y()),
isX(new X()),
!isX(new Y())
].forEach(x => assert.ok(x));
};
exports["test is"] = assert => {
assert.deepEqual([ 1, 0, 1, 0, 1 ].map(is(1)),
[ true, false, true, false, true ],
"is can be partially applied");
assert.ok(is(1, 1));
assert.ok(!is({}, {}));
assert.ok(is()(1)()(1), "is is curried");
assert.ok(!is()(1)()(2));
};
exports["test isnt"] = assert => {
assert.deepEqual([ 1, 0, 1, 0, 1 ].map(isnt(0)),
[ true, false, true, false, true ],
"is can be partially applied");
assert.ok(!isnt(1, 1));
assert.ok(isnt({}, {}));
assert.ok(!isnt()(1)()(1));
assert.ok(isnt()(1)()(2));
};
require('test').run(exports);

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1385765544000">
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1386360478000">
<emItems>
<emItem blockID="i454" id="sqlmoz@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
@ -87,6 +87,10 @@
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i492" id="{af95cc15-3b9b-45ae-8d9b-98d08eda3111}">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i24" id="{6E19037A-12E3-4295-8915-ED48BC341614}">
<versionRange minVersion="0.1" maxVersion="1.3.328.4" severity="1">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
@ -114,6 +118,10 @@
<versionRange minVersion="0.1" maxVersion="5.2.0.7164" severity="1">
</versionRange>
</emItem>
<emItem blockID="i498" id="hoverst@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i430" id="1chtw@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
@ -153,10 +161,22 @@
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i500" id="{2aab351c-ad56-444c-b935-38bffe18ad26}">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i494" id="/^({e9df9360-97f8-4690-afe6-996c80790da4}|{687578b9-7132-4a7a-80e4-30ee31099e03}|{46a3135d-3683-48cf-b94c-82655cbc0e8a}|{49c795c2-604a-4d18-aeb1-b3eba27e5ea2}|{7473b6bd-4691-4744-a82b-7854eb3d70b6}|{96f454ea-9d38-474f-b504-56193e00c1a5})$/">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i467" id="plugin@analytic-s.com">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i496" id="{ACAA314B-EEBA-48e4-AD47-84E31C44796C}">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i360" id="ytd@mybrowserbar.com">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
@ -216,6 +236,10 @@
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i499" id="{babb9931-ad56-444c-b935-38bffe18ad26}">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i111" os="WINNT" id="{C3949AC2-4B17-43ee-B4F1-D26B9D42404D}">
<versionRange minVersion="0" maxVersion="15.0.5" severity="1">
</versionRange>
@ -256,8 +280,10 @@
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i487" id="{df6bb2ec-333b-4267-8c4f-3f27dc8c6e07}">
<emItem blockID="i502" id="{df6bb2ec-333b-4267-8c4f-3f27dc8c6e07}">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i142" id="{a3a5c777-f583-4fef-9380-ab4add1bc2a8}">
@ -385,6 +411,10 @@
</emItem>
<emItem blockID="i44" id="sigma@labs.mozilla">
</emItem>
<emItem blockID="i501" id="xivars@aol.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i482" id="brasilescapeeight@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
@ -437,6 +467,10 @@
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i495" id="kallow@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i483" id="brasilescapefive@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
@ -455,6 +489,10 @@
<versionRange minVersion="1.0" maxVersion="1.0">
</versionRange>
</emItem>
<emItem blockID="i504" id="aytac@abc.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i92" id="play5@vide04flash.com">
<versionRange minVersion="0" maxVersion="*">
</versionRange>
@ -497,6 +535,10 @@
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i493" id="12x3q@3244516.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i344" id="lrcsTube@hansanddeta.com">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
@ -682,6 +724,10 @@
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i503" id="{9CE11043-9A15-4207-A565-0C94C42D590D}">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i431" id="chinaescapeone@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
@ -791,6 +837,10 @@
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i497" id="{872b5b88-9db5-4310-bdd0-ac189557e5f5}">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i68" id="flashupdate@adobe.com">
<versionRange minVersion="0" maxVersion="*">
</versionRange>

View File

@ -1093,7 +1093,7 @@ pref("devtools.toolbox.footer.height", 250);
pref("devtools.toolbox.sidebar.width", 500);
pref("devtools.toolbox.host", "bottom");
pref("devtools.toolbox.selectedTool", "webconsole");
pref("devtools.toolbox.toolbarSpec", '["paintflashing toggle","tilt toggle","scratchpad","resize toggle"]');
pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","tilt toggle","scratchpad","resize toggle"]');
pref("devtools.toolbox.sideEnabled", true);
pref("devtools.toolbox.zoomValue", "1");

View File

@ -2272,3 +2272,68 @@ gcli.addCommand({
}
});
}(this));
/* CmdSplitConsole ------------------------------------------------------- */
(function(module) {
/**
* 'splitconsole' command (hidden)
*/
gcli.addCommand({
name: 'splitconsole',
hidden: true,
buttonId: "command-button-splitconsole",
buttonClass: "command-button",
tooltipText: gcli.lookup("splitconsoleTooltip"),
state: {
isChecked: function(aTarget) {
let toolbox = gDevTools.getToolbox(aTarget);
return toolbox &&
toolbox.splitConsole;
},
onChange: function(aTarget, aChangeHandler) {
eventEmitter.on("changed", aChangeHandler);
},
offChange: function(aTarget, aChangeHandler) {
eventEmitter.off("changed", aChangeHandler);
},
},
exec: function(args, context) {
toggleSplitConsole(context);
}
});
function toggleSplitConsole(context) {
let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
if (!toolbox) {
gDevTools.showToolbox(target, "inspector").then((toolbox) => {
toolbox.toggleSplitConsole();
});
} else {
toolbox.toggleSplitConsole();
}
}
let eventEmitter = new EventEmitter();
function fireChange(tab) {
eventEmitter.emit("changed", tab);
}
gDevTools.on("toolbox-ready", (e, toolbox) => {
if (!toolbox.target) {
return;
}
let fireChangeForTab = fireChange.bind(this, toolbox.target.tab);
toolbox.on("split-console", fireChangeForTab);
toolbox.on("select", fireChangeForTab);
toolbox.once("destroyed", () => {
toolbox.off("split-console", fireChangeForTab);
toolbox.off("select", fireChangeForTab);
});
});
}(this));

View File

@ -182,6 +182,13 @@ Toolbox.prototype = {
return parseFloat(Services.prefs.getCharPref(ZOOM_PREF));
},
/**
* Get the toggled state of the split console
*/
get splitConsole() {
return this._splitConsole;
},
/**
* Open the toolbox
*/

View File

@ -63,22 +63,24 @@ function test()
function getCurrentUIState()
{
let win = toolbox.doc.defaultView;
let deck = toolbox.doc.getElementById("toolbox-deck");
let webconsolePanel = toolbox.doc.getElementById("toolbox-panel-webconsole");
let splitter = toolbox.doc.getElementById("toolbox-console-splitter");
let deck = toolbox.doc.querySelector("#toolbox-deck");
let webconsolePanel = toolbox.doc.querySelector("#toolbox-panel-webconsole");
let splitter = toolbox.doc.querySelector("#toolbox-console-splitter");
let containerHeight = parseFloat(win.getComputedStyle(deck.parentNode).getPropertyValue("height"));
let deckHeight = parseFloat(win.getComputedStyle(deck).getPropertyValue("height"));
let webconsoleHeight = parseFloat(win.getComputedStyle(webconsolePanel).getPropertyValue("height"));
let splitterVisibility = !splitter.getAttribute("hidden");
let openedConsolePanel = toolbox.currentToolId === "webconsole";
let cmdButton = toolbox.doc.querySelector("#command-button-splitconsole");
return {
deckHeight: deckHeight,
containerHeight: containerHeight,
webconsoleHeight: webconsoleHeight,
splitterVisibility: splitterVisibility,
openedConsolePanel: openedConsolePanel
openedConsolePanel: openedConsolePanel,
buttonSelected: cmdButton.hasAttribute("checked")
};
}
@ -97,6 +99,7 @@ function test()
ok (currentUIState.deckHeight > 0, "Deck has a height > 0 when console is split");
ok (currentUIState.webconsoleHeight > 0, "Web console has a height > 0 when console is split");
ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
ok (currentUIState.buttonSelected, "The command button is selected");
openPanel("webconsole").then(() => {
@ -106,6 +109,7 @@ function test()
is (currentUIState.deckHeight, 0, "Deck has a height == 0 when console is opened.");
is (currentUIState.webconsoleHeight, currentUIState.containerHeight, "Web console is full height.");
ok (currentUIState.openedConsolePanel, "The console panel is the current tool");
ok (currentUIState.buttonSelected, "The command button is still selected.");
// Make sure splitting console does nothing while webconsole is opened
toolbox.toggleSplitConsole();
@ -116,6 +120,7 @@ function test()
is (currentUIState.deckHeight, 0, "Deck has a height == 0 when console is opened.");
is (currentUIState.webconsoleHeight, currentUIState.containerHeight, "Web console is full height.");
ok (currentUIState.openedConsolePanel, "The console panel is the current tool");
ok (currentUIState.buttonSelected, "The command button is still selected.");
// Make sure that split state is saved after opening another panel
openPanel("inspector").then(() => {
@ -124,6 +129,7 @@ function test()
ok (currentUIState.deckHeight > 0, "Deck has a height > 0 when console is split");
ok (currentUIState.webconsoleHeight > 0, "Web console has a height > 0 when console is split");
ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
ok (currentUIState.buttonSelected, "The command button is still selected.");
toolbox.toggleSplitConsole();
deferred.resolve();
@ -163,6 +169,7 @@ function test()
is (currentUIState.deckHeight, currentUIState.containerHeight, "Deck has a height > 0 by default");
is (currentUIState.webconsoleHeight, 0, "Web console is collapsed by default");
ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
ok (!currentUIState.buttonSelected, "The command button is not selected.");
toolbox.toggleSplitConsole();
@ -175,6 +182,7 @@ function test()
currentUIState.containerHeight,
"Everything adds up to container height");
ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
ok (currentUIState.buttonSelected, "The command button is selected.");
toolbox.toggleSplitConsole();
@ -184,9 +192,9 @@ function test()
is (currentUIState.deckHeight, currentUIState.containerHeight, "Deck has a height > 0 after toggling");
is (currentUIState.webconsoleHeight, 0, "Web console is collapsed after toggling");
ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
ok (!currentUIState.buttonSelected, "The command button is not selected.");
}
function testBottomHost()
{
checkHostType(Toolbox.HostType.BOTTOM);

View File

@ -1,4 +1,4 @@
This is the pdf.js project output, https://github.com/mozilla/pdf.js
Current extension version is: 0.8.681
Current extension version is: 0.8.759

View File

@ -16,7 +16,7 @@
*/
/* jshint esnext:true */
/* globals Components, Services, XPCOMUtils, NetUtil, PrivateBrowsingUtils,
dump, NetworkManager, PdfJsTelemetry */
dump, NetworkManager, PdfJsTelemetry, DEFAULT_PREFERENCES */
'use strict';
@ -33,12 +33,16 @@ const PDF_CONTENT_TYPE = 'application/pdf';
const PREF_PREFIX = 'pdfjs';
const PDF_VIEWER_WEB_PAGE = 'resource://pdf.js/web/viewer.html';
const MAX_DATABASE_LENGTH = 4096;
const MAX_STRING_PREF_LENGTH = 4096;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/NetUtil.jsm');
Cu.import('resource://pdf.js/network.js');
// Load the default preferences.
Cu.import('resource://pdf.js/default_preferences.js');
XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
'resource://gre/modules/PrivateBrowsingUtils.jsm');
@ -58,6 +62,10 @@ function getChromeWindow(domWindow) {
return containingBrowser.ownerDocument.defaultView;
}
function setBoolPref(pref, value) {
Services.prefs.setBoolPref(pref, value);
}
function getBoolPref(pref, def) {
try {
return Services.prefs.getBoolPref(pref);
@ -66,6 +74,10 @@ function getBoolPref(pref, def) {
}
}
function setIntPref(pref, value) {
Services.prefs.setIntPref(pref, value);
}
function getIntPref(pref, def) {
try {
return Services.prefs.getIntPref(pref);
@ -431,6 +443,62 @@ ChromeActions.prototype = {
}
getChromeWindow(this.domWindow).gFindBar
.updateControlState(result, findPrevious);
},
setPreferences: function(prefs) {
var prefValue, defaultValue, prefName, prefType, defaultType;
for (var key in DEFAULT_PREFERENCES) {
prefValue = prefs[key];
defaultValue = DEFAULT_PREFERENCES[key];
prefName = (PREF_PREFIX + '.' + key);
if (prefValue === undefined || prefValue === defaultValue) {
Services.prefs.clearUserPref(prefName);
} else {
prefType = typeof prefValue;
defaultType = typeof defaultValue;
if (prefType !== defaultType) {
continue;
}
switch (defaultType) {
case 'boolean':
setBoolPref(prefName, prefValue);
break;
case 'number':
setIntPref(prefName, prefValue);
break;
case 'string':
// Protect against adding arbitrarily long strings in about:config.
if (prefValue.length <= MAX_STRING_PREF_LENGTH) {
setStringPref(prefName, prefValue);
}
break;
}
}
}
},
getPreferences: function() {
var currentPrefs = {};
var defaultValue, prefName;
for (var key in DEFAULT_PREFERENCES) {
defaultValue = DEFAULT_PREFERENCES[key];
prefName = (PREF_PREFIX + '.' + key);
switch (typeof defaultValue) {
case 'boolean':
currentPrefs[key] = getBoolPref(prefName, defaultValue);
break;
case 'number':
currentPrefs[key] = getIntPref(prefName, defaultValue);
break;
case 'string':
currentPrefs[key] = getStringPref(prefName, defaultValue);
break;
}
}
return JSON.stringify(currentPrefs);
}
};
@ -439,9 +507,12 @@ var RangedChromeActions = (function RangedChromeActionsClosure() {
* This is for range requests
*/
function RangedChromeActions(
domWindow, contentDispositionFilename, originalRequest) {
domWindow, contentDispositionFilename, originalRequest,
dataListener) {
ChromeActions.call(this, domWindow, contentDispositionFilename);
this.dataListener = dataListener;
this.originalRequest = originalRequest;
this.pdfUrl = originalRequest.URI.spec;
this.contentLength = originalRequest.contentLength;
@ -487,11 +558,15 @@ var RangedChromeActions = (function RangedChromeActionsClosure() {
proto.constructor = RangedChromeActions;
proto.initPassiveLoading = function RangedChromeActions_initPassiveLoading() {
this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
this.originalRequest = null;
this.domWindow.postMessage({
pdfjsLoadAction: 'supportsRangedLoading',
pdfUrl: this.pdfUrl,
length: this.contentLength
length: this.contentLength,
data: this.dataListener.getData()
}, '*');
this.dataListener = null;
return true;
};
@ -692,8 +767,8 @@ PdfStreamConverter.prototype = {
* 1. asyncConvertData stores the listener
* 2. onStartRequest creates a new channel, streams the viewer
* 3. If range requests are supported:
* 3.1. Suspends and cancels the request so we can issue range
* requests instead.
* 3.1. Leave the request open until the viewer is ready to switch to
* range requests.
*
* If range rquests are not supported:
* 3.1. Read the stream as it's loaded in onDataAvailable to send
@ -779,18 +854,12 @@ PdfStreamConverter.prototype = {
PdfJsTelemetry.onViewerIsUsed();
PdfJsTelemetry.onDocumentSize(aRequest.contentLength);
if (!rangeRequest) {
// Creating storage for PDF data
var contentLength = aRequest.contentLength;
this.dataListener = new PdfDataListener(contentLength);
this.binaryStream = Cc['@mozilla.org/binaryinputstream;1']
.createInstance(Ci.nsIBinaryInputStream);
} else {
// Suspend the request so we're not consuming any of the stream,
// but we can't cancel the request yet. Otherwise, the original
// listener will think we do not want to go the new PDF url
aRequest.suspend();
}
// Creating storage for PDF data
var contentLength = aRequest.contentLength;
this.dataListener = new PdfDataListener(contentLength);
this.binaryStream = Cc['@mozilla.org/binaryinputstream;1']
.createInstance(Ci.nsIBinaryInputStream);
// Create a new channel that is viewer loaded as a resource.
var ioService = Services.io;
@ -816,12 +885,8 @@ PdfStreamConverter.prototype = {
var domWindow = getDOMWindow(channel);
var actions;
if (rangeRequest) {
// We are going to be issuing range requests, so cancel the
// original request
aRequest.resume();
aRequest.cancel(Cr.NS_BINDING_ABORTED);
actions = new RangedChromeActions(domWindow,
contentDispositionFilename, aRequest);
actions = new RangedChromeActions(
domWindow, contentDispositionFilename, aRequest, dataListener);
} else {
actions = new StandardChromeActions(
domWindow, contentDispositionFilename, dataListener);

View File

@ -20,8 +20,8 @@ if (typeof PDFJS === 'undefined') {
(typeof window !== 'undefined' ? window : this).PDFJS = {};
}
PDFJS.version = '0.8.681';
PDFJS.build = '48c672b';
PDFJS.version = '0.8.759';
PDFJS.build = 'd3b5aa3';
(function pdfjsWrapper() {
// Use strict in our context only - users might not want it
@ -43,7 +43,7 @@ PDFJS.build = '48c672b';
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref */
/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL */
'use strict';
@ -78,6 +78,98 @@ if (!globalScope.PDFJS) {
globalScope.PDFJS.pdfBug = false;
// All the possible operations for an operator list.
var OPS = PDFJS.OPS = {
// Intentionally start from 1 so it is easy to spot bad operators that will be
// 0's.
dependency: 1,
setLineWidth: 2,
setLineCap: 3,
setLineJoin: 4,
setMiterLimit: 5,
setDash: 6,
setRenderingIntent: 7,
setFlatness: 8,
setGState: 9,
save: 10,
restore: 11,
transform: 12,
moveTo: 13,
lineTo: 14,
curveTo: 15,
curveTo2: 16,
curveTo3: 17,
closePath: 18,
rectangle: 19,
stroke: 20,
closeStroke: 21,
fill: 22,
eoFill: 23,
fillStroke: 24,
eoFillStroke: 25,
closeFillStroke: 26,
closeEOFillStroke: 27,
endPath: 28,
clip: 29,
eoClip: 30,
beginText: 31,
endText: 32,
setCharSpacing: 33,
setWordSpacing: 34,
setHScale: 35,
setLeading: 36,
setFont: 37,
setTextRenderingMode: 38,
setTextRise: 39,
moveText: 40,
setLeadingMoveText: 41,
setTextMatrix: 42,
nextLine: 43,
showText: 44,
showSpacedText: 45,
nextLineShowText: 46,
nextLineSetSpacingShowText: 47,
setCharWidth: 48,
setCharWidthAndBounds: 49,
setStrokeColorSpace: 50,
setFillColorSpace: 51,
setStrokeColor: 52,
setStrokeColorN: 53,
setFillColor: 54,
setFillColorN: 55,
setStrokeGray: 56,
setFillGray: 57,
setStrokeRGBColor: 58,
setFillRGBColor: 59,
setStrokeCMYKColor: 60,
setFillCMYKColor: 61,
shadingFill: 62,
beginInlineImage: 63,
beginImageData: 64,
endInlineImage: 65,
paintXObject: 66,
markPoint: 67,
markPointProps: 68,
beginMarkedContent: 69,
beginMarkedContentProps: 70,
endMarkedContent: 71,
beginCompat: 72,
endCompat: 73,
paintFormXObjectBegin: 74,
paintFormXObjectEnd: 75,
beginGroup: 76,
endGroup: 77,
beginAnnotations: 78,
endAnnotations: 79,
beginAnnotation: 80,
endAnnotation: 81,
paintJpegXObject: 82,
paintImageMaskXObject: 83,
paintImageMaskXObjectGroup: 84,
paintImageXObject: 85,
paintInlineImageXObject: 86,
paintInlineImageXObjectGroup: 87
};
// Use only for debugging purposes. This should not be used in any code that is
// in mozilla master.
@ -292,7 +384,7 @@ var MissingDataException = (function MissingDataExceptionClosure() {
function MissingDataException(begin, end) {
this.begin = begin;
this.end = end;
this.message = 'Missing data [begin, end)';
this.message = 'Missing data [' + begin + ', ' + end + ')';
}
MissingDataException.prototype = new Error();
@ -865,7 +957,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
/**
* Builds a promise that is resolved when all the passed in promises are
* resolved.
* @param {Promise[]} promises Array of promises to wait for.
* @param {array} array of data and/or promises to wait for.
* @return {Promise} New dependant promise.
*/
Promise.all = function Promise_all(promises) {
@ -885,7 +977,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
}
for (var i = 0, ii = promises.length; i < ii; ++i) {
var promise = promises[i];
promise.then((function(i) {
var resolve = (function(i) {
return function(value) {
if (deferred._status === STATUS_REJECTED) {
return;
@ -895,11 +987,24 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
if (unresolved === 0)
deferred.resolve(results);
};
})(i), reject);
})(i);
if (Promise.isPromise(promise)) {
promise.then(resolve, reject);
} else {
resolve(promise);
}
}
return deferred;
};
/**
* Checks if the value is likely a promise (has a 'then' function).
* @return {boolean} true if x is thenable
*/
Promise.isPromise = function Promise_isPromise(value) {
return value && typeof value.then === 'function';
};
Promise.prototype = {
_status: null,
_value: null,
@ -913,7 +1018,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
}
if (status == STATUS_RESOLVED &&
value && typeof(value.then) === 'function') {
Promise.isPromise(value)) {
value.then(this._updateStatus.bind(this, STATUS_RESOLVED),
this._updateStatus.bind(this, STATUS_REJECTED));
return;
@ -1016,7 +1121,7 @@ var StatTimer = (function StatTimerClosure() {
})();
PDFJS.createBlob = function createBlob(data, contentType) {
if (typeof Blob === 'function')
if (typeof Blob !== 'undefined')
return new Blob([data], { type: contentType });
// Blob builder is deprecated in FF14 and removed in FF18.
var bb = new MozBlobBuilder();
@ -1024,10 +1129,38 @@ PDFJS.createBlob = function createBlob(data, contentType) {
return bb.getBlob(contentType);
};
PDFJS.createObjectURL = (function createObjectURLClosure() {
if (typeof URL !== 'undefined' && URL.createObjectURL) {
return function createObjectURL(data, contentType) {
var blob = PDFJS.createBlob(data, contentType);
return URL.createObjectURL(blob);
};
}
// Blob/createObjectURL is not available, falling back to data schema.
var digits =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
return function createObjectURL(data, contentType) {
var buffer = 'data:' + contentType + ';base64,';
for (var i = 0, ii = data.length; i < ii; i += 3) {
var b1 = data[i] & 0xFF;
var b2 = data[i + 1] & 0xFF;
var b3 = data[i + 2] & 0xFF;
var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
var d4 = i + 2 < ii ? (b3 & 0x3F) : 64;
buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
}
return buffer;
};
})();
function MessageHandler(name, comObj) {
this.name = name;
this.comObj = comObj;
this.callbackIndex = 1;
this.postMessageTransfers = true;
var callbacks = this.callbacks = {};
var ah = this.actionHandler = {};
@ -1094,8 +1227,9 @@ MessageHandler.prototype = {
* @param {String} actionName Action to call.
* @param {JSON} data JSON data to send.
* @param {function} [callback] Optional callback that will handle a reply.
* @param {Array} [transfers] Optional list of transfers/ArrayBuffers
*/
send: function messageHandlerSend(actionName, data, callback) {
send: function messageHandlerSend(actionName, data, callback, transfers) {
var message = {
action: actionName,
data: data
@ -1105,16 +1239,20 @@ MessageHandler.prototype = {
this.callbacks[callbackId] = callback;
message.callbackId = callbackId;
}
this.comObj.postMessage(message);
if (transfers && this.postMessageTransfers) {
this.comObj.postMessage(message, transfers);
} else {
this.comObj.postMessage(message);
}
}
};
function loadJpegStream(id, imageData, objs) {
function loadJpegStream(id, imageUrl, objs) {
var img = new Image();
img.onload = (function loadJpegStream_onloadClosure() {
objs.resolve(id, img);
});
img.src = 'data:image/jpeg;base64,' + window.btoa(imageData);
img.src = imageUrl;
}
@ -3430,9 +3568,9 @@ var Annotation = (function AnnotationClosure() {
resourcesPromise.then(function(resources) {
var opList = new OperatorList();
opList.addOp('beginAnnotation', [data.rect, transform, matrix]);
opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
evaluator.getOperatorList(this.appearance, resources, opList);
opList.addOp('endAnnotation', []);
opList.addOp(OPS.endAnnotation, []);
promise.resolve(opList);
}.bind(this));
@ -3526,12 +3664,12 @@ var Annotation = (function AnnotationClosure() {
annotationPromises.push(annotations[i].getOperatorList(partialEvaluator));
}
Promise.all(annotationPromises).then(function(datas) {
opList.addOp('beginAnnotations', []);
opList.addOp(OPS.beginAnnotations, []);
for (var i = 0, n = datas.length; i < n; ++i) {
var annotOpList = datas[i];
opList.addOpList(annotOpList);
}
opList.addOp('endAnnotations', []);
opList.addOp(OPS.endAnnotations, []);
annotationsReadyPromise.resolve();
}, reject);
@ -3707,10 +3845,10 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
data.rgb = [0, 0, 0];
// TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY!
for (var i = 0, n = fnArray.length; i < n; ++i) {
var fnName = appearanceFnArray[i];
var fnId = appearanceFnArray[i];
var args = appearanceArgsArray[i];
if (fnName === 'setFont') {
if (fnId === OPS.setFont) {
data.fontRefName = args[0];
var size = args[1];
if (size < 0) {
@ -3720,9 +3858,9 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
data.fontDirection = 1;
data.fontSize = size;
}
} else if (fnName === 'setFillRGBColor') {
} else if (fnId === OPS.setFillRGBColor) {
data.rgb = args;
} else if (fnName === 'setFillGray') {
} else if (fnId === OPS.setFillGray) {
var rgbValue = args[0] * 255;
data.rgb = [rgbValue, rgbValue, rgbValue];
}
@ -3973,7 +4111,9 @@ PDFJS.disableWorker = PDFJS.disableWorker === undefined ?
false : PDFJS.disableWorker;
/**
* Path and filename of the worker file. Required when the worker is enabled.
* Path and filename of the worker file. Required when the worker is enabled in
* development mode. If unspecified in the production build, the worker will be
* loaded based on the location of the pdf.js file.
* @var {String}
*/
PDFJS.workerSrc = PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc;
@ -4002,6 +4142,12 @@ PDFJS.disableAutoFetch = PDFJS.disableAutoFetch === undefined ?
*/
PDFJS.pdfBug = PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug;
/**
* Enables transfer usage in postMessage for ArrayBuffers.
* @var {boolean}
*/
PDFJS.postMessageTransfers = PDFJS.postMessageTransfers === undefined ?
true : PDFJS.postMessageTransfers;
/**
* This is the main entry point for loading a PDF and interacting with it.
* NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
@ -4015,6 +4161,9 @@ PDFJS.pdfBug = PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug;
* - data - A typed array with PDF data.
* - httpHeaders - Basic authentication headers.
* - password - For decrypting password-protected PDFs.
* - initialData - A typed array with the first portion or all of the pdf data.
* Used by the extension since some data is already loaded
* before the switch to range requests.
*
* @param {object} pdfDataRangeTransport is optional. It is used if you want
* to manually serve range requests for data in the PDF. See viewer.js for
@ -4104,6 +4253,14 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
getPage: function PDFDocumentProxy_getPage(number) {
return this.transport.getPage(number);
},
/**
* @param {object} Must have 'num' and 'gen' properties.
* @return {Promise} A promise that is resolved with the page index that is
* associated with the reference.
*/
getPageIndex: function PDFDocumentProxy_getPageIndex(ref) {
return this.transport.getPageIndex(ref);
},
/**
* @return {Promise} A promise that is resolved with a lookup table for
* mapping named destinations to reference numbers.
@ -4179,6 +4336,9 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
dataLoaded: function PDFDocumentProxy_dataLoaded() {
return this.transport.dataLoaded();
},
cleanup: function PDFDocumentProxy_cleanup() {
this.transport.startCleanup();
},
destroy: function PDFDocumentProxy_destroy() {
this.transport.destroy();
}
@ -4398,10 +4558,10 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
*/
_renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk) {
// Add the new chunk to the current operator list.
Util.concatenateToArray(this.operatorList.fnArray,
operatorListChunk.fnArray);
Util.concatenateToArray(this.operatorList.argsArray,
operatorListChunk.argsArray);
for (var i = 0, ii = operatorListChunk.length; i < ii; i++) {
this.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
this.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
}
this.operatorList.lastChunk = operatorListChunk.lastChunk;
// Notify all the rendering tasks there are more operators to be consumed.
@ -4453,9 +4613,13 @@ var WorkerTransport = (function WorkerTransportClosure() {
var messageHandler = new MessageHandler('main', worker);
this.messageHandler = messageHandler;
messageHandler.on('test', function transportTest(supportTypedArray) {
messageHandler.on('test', function transportTest(data) {
var supportTypedArray = data && data.supportTypedArray;
if (supportTypedArray) {
this.worker = worker;
if (!data.supportTransfers) {
PDFJS.postMessageTransfers = false;
}
this.setupMessageHandler(messageHandler);
workerInitializedPromise.resolve();
} else {
@ -4467,10 +4631,16 @@ var WorkerTransport = (function WorkerTransportClosure() {
}
}.bind(this));
var testObj = new Uint8Array(1);
// Some versions of Opera throw a DATA_CLONE_ERR on
// serializing the typed array.
messageHandler.send('test', testObj);
var testObj = new Uint8Array([PDFJS.postMessageTransfers ? 255 : 0]);
// Some versions of Opera throw a DATA_CLONE_ERR on serializing the
// typed array. Also, checking if we can use transfers.
try {
messageHandler.send('test', testObj, null, [testObj.buffer]);
} catch (ex) {
info('Cannot use postMessage transfers');
testObj[0] = 0;
messageHandler.send('test', testObj);
}
return;
} catch (e) {
info('The worker has been disabled.');
@ -4704,7 +4874,7 @@ var WorkerTransport = (function WorkerTransportClosure() {
}, this);
messageHandler.on('JpegDecode', function(data, promise) {
var imageData = data[0];
var imageUrl = data[0];
var components = data[1];
if (components != 3 && components != 1)
error('Only 3 component or 1 component can be returned');
@ -4734,8 +4904,7 @@ var WorkerTransport = (function WorkerTransportClosure() {
}
promise.resolve({ data: buf, width: width, height: height});
}).bind(this);
var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
img.src = src;
img.src = imageUrl;
});
},
@ -4774,6 +4943,16 @@ var WorkerTransport = (function WorkerTransportClosure() {
return promise;
},
getPageIndex: function WorkerTransport_getPageIndexByRef(ref) {
var promise = new PDFJS.Promise();
this.messageHandler.send('GetPageIndex', { ref: ref },
function (pageIndex) {
promise.resolve(pageIndex);
}
);
return promise;
},
getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
this.messageHandler.send('GetAnnotationsRequest',
{ pageIndex: pageIndex });
@ -4787,6 +4966,21 @@ var WorkerTransport = (function WorkerTransportClosure() {
}
);
return promise;
},
startCleanup: function WorkerTransport_startCleanup() {
this.messageHandler.send('Cleanup', null,
function endCleanup() {
for (var i = 0, ii = this.pageCache.length; i < ii; i++) {
var page = this.pageCache[i];
if (page) {
page.destroy();
}
}
this.commonObjs.clear();
FontLoader.clear();
}.bind(this)
);
}
};
return WorkerTransport;
@ -5007,7 +5201,7 @@ var InternalRenderTask = (function InternalRenderTaskClosure() {
this.operatorListIdx,
this._continue.bind(this),
this.stepper);
if (this.operatorListIdx === this.operatorList.fnArray.length) {
if (this.operatorListIdx === this.operatorList.argsArray.length) {
this.running = false;
if (this.operatorList.lastChunk) {
this.gfx.endDrawing();
@ -5536,37 +5730,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var EO_CLIP = {};
CanvasGraphics.prototype = {
slowCommands: {
'stroke': true,
'closeStroke': true,
'fill': true,
'eoFill': true,
'fillStroke': true,
'eoFillStroke': true,
'closeFillStroke': true,
'closeEOFillStroke': true,
'showText': true,
'showSpacedText': true,
'setStrokeColorSpace': true,
'setFillColorSpace': true,
'setStrokeColor': true,
'setStrokeColorN': true,
'setFillColor': true,
'setFillColorN': true,
'setStrokeGray': true,
'setFillGray': true,
'setStrokeRGBColor': true,
'setFillRGBColor': true,
'setStrokeCMYKColor': true,
'setFillCMYKColor': true,
'paintJpegXObject': true,
'paintImageXObject': true,
'paintInlineImageXObject': true,
'paintInlineImageXObjectGroup': true,
'paintImageMaskXObject': true,
'paintImageMaskXObjectGroup': true,
'shadingFill': true
},
beginDrawing: function CanvasGraphics_beginDrawing(viewport, transparency) {
// For pdfs that use blend modes we have to clear the canvas else certain
@ -5618,8 +5781,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var commonObjs = this.commonObjs;
var objs = this.objs;
var fnName;
var slowCommands = this.slowCommands;
var fnId;
while (true) {
if (stepper && i === stepper.nextBreakPoint) {
@ -5627,10 +5789,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
return i;
}
fnName = fnArray[i];
fnId = fnArray[i];
if (fnName !== 'dependency') {
this[fnName].apply(this, argsArray[i]);
if (fnId !== OPS.dependency) {
this[fnId].apply(this, argsArray[i]);
} else {
var deps = argsArray[i];
for (var n = 0, nn = deps.length; n < nn; n++) {
@ -5657,10 +5819,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
return i;
}
// If the execution took longer then a certain amount of time, shedule
// If the execution took longer then a certain amount of time, schedule
// to continue exeution after a short delay.
// However, this is only possible if a 'continueCallback' is passed in.
if (continueCallback && slowCommands[fnName] && Date.now() > endTime) {
if (continueCallback && Date.now() > endTime) {
setTimeout(continueCallback, 0);
return i;
}
@ -6947,6 +7109,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}
};
for (var op in OPS) {
CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
}
return CanvasGraphics;
})();
@ -6967,6 +7133,12 @@ var FontLoader = {
var styleSheet = styleElement.sheet;
styleSheet.insertRule(rule, styleSheet.cssRules.length);
},
clear: function fontLoaderClear() {
var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG');
if (styleElement) {
styleElement.parentNode.removeChild(styleElement);
}
},
bind: function fontLoaderBind(fonts, callback) {
assert(!isWorker, 'bind() shall be called from main thread');

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* Copyright 2013 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals */
'use strict';
var EXPORTED_SYMBOLS = ['DEFAULT_PREFERENCES'];
var DEFAULT_PREFERENCES = {
showPreviousViewOnLoad: true,
defaultZoomValue: '',
ifAvailableShowOutlineOnLoad: false
};

View File

@ -240,6 +240,8 @@ var Stepper = (function StepperClosure() {
return out;
}
var opMap = null;
var glyphCommands = {
'showText': 0,
'showSpacedText': 0,
@ -271,6 +273,12 @@ var Stepper = (function StepperClosure() {
headerRow.appendChild(c('th', 'args'));
panel.appendChild(content);
this.table = table;
if (!opMap) {
opMap = Object.create(null);
for (var key in PDFJS.OPS) {
opMap[PDFJS.OPS[key]] = key;
}
}
},
updateOperatorList: function updateOperatorList(operatorList) {
var self = this;
@ -300,7 +308,7 @@ var Stepper = (function StepperClosure() {
breakCell.appendChild(cbox);
line.appendChild(breakCell);
line.appendChild(c('td', i.toString()));
var fn = operatorList.fnArray[i];
var fn = opMap[operatorList.fnArray[i]];
var decArgs = args;
if (fn in glyphCommands) {
var glyphIndex = glyphCommands[fn];

View File

@ -144,7 +144,6 @@ limitations under the License.
</div>
<label id="pageNumberLabel" class="toolbarLabel" for="pageNumber" data-l10n-id="page_label">Page: </label>
<input type="number" id="pageNumber" class="toolbarField pageNumber" value="1" size="4" min="1" tabindex="8">
</input>
<span id="numPages" class="toolbarLabel"></span>
</div>
<div id="toolbarViewerRight">

View File

@ -16,8 +16,8 @@
*/
/* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, PDFFindBar, CustomStyle,
PDFFindController, ProgressBar, TextLayerBuilder, DownloadManager,
getFileName, getOutputScale, scrollIntoView, getPDFFileNameFromURL,
PDFHistory, Settings, PageView, ThumbnailView, noContextMenuHandler,
getFileName, scrollIntoView, getPDFFileNameFromURL, PDFHistory,
Preferences, Settings, PageView, ThumbnailView, noContextMenuHandler,
SecondaryToolbar, PasswordPrompt, PresentationMode */
'use strict';
@ -38,6 +38,7 @@ var SCALE_SELECT_CONTAINER_PADDING = 8;
var SCALE_SELECT_PADDING = 22;
var THUMBNAIL_SCROLL_MARGIN = -19;
var USE_ONLY_CSS_ZOOM = false;
var CLEANUP_TIMEOUT = 30000;
var RenderingStates = {
INITIAL: 0,
RUNNING: 1,
@ -154,6 +155,9 @@ function scrollIntoView(element, spot) {
return;
}
while (parent.clientHeight == parent.scrollHeight) {
if (parent.dataset._scaleY) {
offsetY /= parent.dataset._scaleY;
}
offsetY += parent.offsetTop;
parent = parent.offsetParent;
if (!parent)
@ -284,6 +288,92 @@ var Cache = function cacheCache(size) {
var EXPORTED_SYMBOLS = ['DEFAULT_PREFERENCES'];
var DEFAULT_PREFERENCES = {
showPreviousViewOnLoad: true,
defaultZoomValue: '',
ifAvailableShowOutlineOnLoad: false
};
var Preferences = (function PreferencesClosure() {
function Preferences() {
this.prefs = {};
this.initializedPromise = this.readFromStorage().then(function(prefObj) {
if (prefObj) {
this.prefs = prefObj;
}
}.bind(this));
}
Preferences.prototype = {
writeToStorage: function Preferences_writeToStorage(prefObj) {
return;
},
readFromStorage: function Preferences_readFromStorage() {
var readFromStoragePromise = new PDFJS.Promise();
return readFromStoragePromise;
},
reset: function Preferences_reset() {
if (this.initializedPromise.isResolved) {
this.prefs = {};
this.writeToStorage(this.prefs);
}
},
set: function Preferences_set(name, value) {
if (!this.initializedPromise.isResolved) {
return;
} else if (DEFAULT_PREFERENCES[name] === undefined) {
console.error('Preferences_set: \'' + name + '\' is undefined.');
return;
} else if (value === undefined) {
console.error('Preferences_set: no value is specified.');
return;
}
var valueType = typeof value;
var defaultType = typeof DEFAULT_PREFERENCES[name];
if (valueType !== defaultType) {
if (valueType === 'number' && defaultType === 'string') {
value = value.toString();
} else {
console.error('Preferences_set: \'' + value + '\' is a \"' +
valueType + '\", expected a \"' + defaultType + '\".');
return;
}
}
this.prefs[name] = value;
this.writeToStorage(this.prefs);
},
get: function Preferences_get(name) {
var defaultPref = DEFAULT_PREFERENCES[name];
if (defaultPref === undefined) {
console.error('Preferences_get: \'' + name + '\' is undefined.');
return;
} else if (this.initializedPromise.isResolved) {
var pref = this.prefs[name];
if (pref !== undefined) {
return pref;
}
}
return defaultPref;
}
};
return Preferences;
})();
var FirefoxCom = (function FirefoxComClosure() {
@ -373,6 +463,17 @@ var DownloadManager = (function DownloadManagerClosure() {
return DownloadManager;
})();
Preferences.prototype.writeToStorage = function(prefObj) {
FirefoxCom.requestSync('setPreferences', prefObj);
};
Preferences.prototype.readFromStorage = function() {
var readFromStoragePromise = new PDFJS.Promise();
var readPrefs = JSON.parse(FirefoxCom.requestSync('getPreferences'));
readFromStoragePromise.resolve(readPrefs);
return readFromStoragePromise;
};
var cache = new Cache(CACHE_SIZE);
var currentPageNumber = 1;
@ -580,11 +681,12 @@ var PDFFindBar = {
},
open: function() {
if (this.opened) return;
if (!this.opened) {
this.opened = true;
this.toggleButton.classList.add('toggled');
this.bar.classList.remove('hidden');
}
this.opened = true;
this.toggleButton.classList.add('toggled');
this.bar.classList.remove('hidden');
this.findField.select();
this.findField.focus();
},
@ -797,8 +899,8 @@ var PDFFindController = {
},
nextMatch: function() {
var pages = this.pdfPageSource.pages;
var previous = this.state.findPrevious;
var currentPageIndex = this.pdfPageSource.page - 1;
var numPages = this.pdfPageSource.pages.length;
this.active = true;
@ -807,7 +909,7 @@ var PDFFindController = {
// Need to recalculate the matches, reset everything.
this.dirtyMatch = false;
this.selected.pageIdx = this.selected.matchIdx = -1;
this.offset.pageIdx = previous ? numPages - 1 : 0;
this.offset.pageIdx = currentPageIndex;
this.offset.matchIdx = null;
this.hadMatch = false;
this.resumeCallback = null;
@ -998,8 +1100,7 @@ var PDFHistory = {
// is opened in the web viewer.
this.reInitialized = true;
}
window.history.replaceState({ fingerprint: this.fingerprint }, '',
document.URL);
this._pushOrReplaceState({ fingerprint: this.fingerprint }, true);
}
var self = this;
@ -1064,6 +1165,15 @@ var PDFHistory = {
state.target && state.target.hash) ? true : false;
},
_pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
replace) {
if (replace) {
window.history.replaceState(stateObj, '');
} else {
window.history.pushState(stateObj, '');
}
},
get isHashChangeUnlocked() {
if (!this.initialized) {
return true;
@ -1224,11 +1334,8 @@ var PDFHistory = {
this._pushToHistory(previousParams, false, replacePrevious);
}
}
if (overwrite || this.uid === 0) {
window.history.replaceState(this._stateObj(params), '', document.URL);
} else {
window.history.pushState(this._stateObj(params), '', document.URL);
}
this._pushOrReplaceState(this._stateObj(params),
(overwrite || this.uid === 0));
this.currentUid = this.uid++;
this.current = params;
this.updatePreviousBookmark = true;
@ -1689,6 +1796,7 @@ var PDFView = {
lastScroll: 0,
previousPageNumber: 1,
isViewerEmbedded: (window.parent !== window),
idleTimeout: null,
// called once when the document is loaded
initialize: function pdfViewInitialize() {
@ -1789,7 +1897,7 @@ var PDFView = {
var number = parseFloat(value);
var scale;
if (number) {
if (number > 0) {
scale = number;
resetAutoSettings = true;
} else {
@ -1816,6 +1924,10 @@ var PDFView = {
case 'auto':
scale = Math.min(MAX_AUTO_SCALE, pageWidthScale);
break;
default:
console.error('pdfViewSetScale: \'' + value +
'\' is an unknown zoom value.');
return;
}
}
this.currentScaleValue = value;
@ -2019,7 +2131,8 @@ var PDFView = {
switch (args.pdfjsLoadAction) {
case 'supportsRangedLoading':
PDFView.open(args.pdfUrl, 0, undefined, pdfDataRangeTransport, {
length: args.length
length: args.length,
initialData: args.data
});
break;
case 'range':
@ -2182,9 +2295,9 @@ var PDFView = {
// Update the browsing history.
PDFHistory.push({ dest: dest, hash: destString, page: pageNumber });
} else {
self.pendingRefStrLoaded = new PDFJS.Promise();
self.pendingRefStr = destRef.num + ' ' + destRef.gen + ' R';
self.pendingRefStrLoaded.then(function() {
self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
var pageNum = pageIndex + 1;
self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum;
goToDestination(destRef);
});
}
@ -2289,9 +2402,14 @@ var PDFView = {
},
load: function pdfViewLoad(pdfDocument, scale) {
var self = this;
var onePageRendered = new PDFJS.Promise();
function bindOnAfterDraw(pageView, thumbnailView) {
// when page is painted, using the image as thumbnail base
pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
if (!onePageRendered.isResolved) {
onePageRendered.resolve();
}
thumbnailView.setImage(pageView.canvas);
};
}
@ -2329,6 +2447,7 @@ var PDFView = {
mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
document.getElementById('pageNumber').max = pagesCount;
var prefs = PDFView.prefs = new Preferences();
PDFView.documentFingerprint = id;
var store = PDFView.store = new Settings(id);
@ -2339,7 +2458,6 @@ var PDFView = {
var thumbnails = this.thumbnails = [];
var pagesPromise = this.pagesPromise = new PDFJS.Promise();
var self = this;
var firstPagePromise = pdfDocument.getPage(1);
@ -2347,7 +2465,6 @@ var PDFView = {
// viewport for all pages
firstPagePromise.then(function(pdfPage) {
var viewport = pdfPage.getViewport((scale || 1.0) * CSS_UNITS);
var pagePromises = [];
for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
var viewportClone = viewport.clone();
var pageView = new PageView(container, pageNum, scale,
@ -2360,56 +2477,59 @@ var PDFView = {
thumbnails.push(thumbnailView);
}
// Fetch all the pages since the viewport is needed before printing
// starts to create the correct size canvas. Wait until one page is
// rendered so we don't tie up too many resources early on.
onePageRendered.then(function () {
if (!PDFJS.disableAutoFetch) {
var getPagesLeft = pagesCount;
for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
var pageView = pages[pageNum - 1];
if (!pageView.pdfPage) {
pageView.setPdfPage(pdfPage);
}
var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
pagesRefMap[refStr] = pageNum;
getPagesLeft--;
if (!getPagesLeft) {
pagesPromise.resolve();
}
}.bind(null, pageNum));
}
} else {
// XXX: Printing is semi-broken with auto fetch disabled.
pagesPromise.resolve();
}
});
var event = document.createEvent('CustomEvent');
event.initCustomEvent('documentload', true, true, {});
window.dispatchEvent(event);
PDFView.loadingBar.setWidth(container);
for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
var pagePromise = pdfDocument.getPage(pageNum);
pagePromise.then(function(pdfPage) {
var pageNum = pdfPage.pageNumber;
var pageView = pages[pageNum - 1];
if (!pageView.pdfPage) {
// The pdfPage might already be set if we've already entered
// pageView.draw()
pageView.setPdfPage(pdfPage);
}
var thumbnailView = thumbnails[pageNum - 1];
if (!thumbnailView.pdfPage) {
thumbnailView.setPdfPage(pdfPage);
}
var pageRef = pdfPage.ref;
var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
pagesRefMap[refStr] = pdfPage.pageNumber;
if (self.pendingRefStr && self.pendingRefStr === refStr) {
self.pendingRefStrLoaded.resolve();
}
});
pagePromises.push(pagePromise);
}
PDFFindController.firstPagePromise.resolve();
PDFJS.Promise.all(pagePromises).then(function(pages) {
pagesPromise.resolve(pages);
});
});
var prefsPromise = prefs.initializedPromise;
var storePromise = store.initializedPromise;
PDFJS.Promise.all([firstPagePromise, storePromise]).then(function() {
PDFJS.Promise.all([firstPagePromise, prefsPromise, storePromise]).
then(function() {
var showPreviousViewOnLoad = prefs.get('showPreviousViewOnLoad');
var defaultZoomValue = prefs.get('defaultZoomValue');
var storedHash = null;
if (store.get('exists', false)) {
if (showPreviousViewOnLoad && store.get('exists', false)) {
var pageNum = store.get('page', '1');
var zoom = store.get('zoom', PDFView.currentScale);
var zoom = defaultZoomValue || store.get('zoom', PDFView.currentScale);
var left = store.get('scrollLeft', '0');
var top = store.get('scrollTop', '0');
storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
left + ',' + top;
} else if (defaultZoomValue) {
storedHash = 'page=1&zoom=' + defaultZoomValue;
}
// Initialize the browsing history.
PDFHistory.initialize(self.documentFingerprint);
@ -2459,6 +2579,13 @@ var PDFView = {
pdfDocument.getOutline().then(function(outline) {
self.outline = new DocumentOutlineView(outline);
document.getElementById('viewOutline').disabled = !outline;
if (outline && prefs.get('ifAvailableShowOutlineOnLoad')) {
if (!self.sidebarOpen) {
document.getElementById('sidebarToggle').click();
}
self.switchSidebarView('outline');
}
});
});
@ -2550,6 +2677,11 @@ var PDFView = {
},
renderHighestPriority: function pdfViewRenderHighestPriority() {
if (PDFView.idleTimeout) {
clearTimeout(PDFView.idleTimeout);
PDFView.idleTimeout = null;
}
// Pages have a higher priority than thumbnails, so check them first.
var visiblePages = this.getVisiblePages();
var pageView = this.getHighestPriority(visiblePages, this.pages,
@ -2564,9 +2696,25 @@ var PDFView = {
var thumbView = this.getHighestPriority(visibleThumbs,
this.thumbnails,
this.thumbnailViewScroll.down);
if (thumbView)
if (thumbView) {
this.renderView(thumbView, 'thumbnail');
return;
}
}
PDFView.idleTimeout = setTimeout(function () {
PDFView.cleanup();
}, CLEANUP_TIMEOUT);
},
cleanup: function pdfViewCleanup() {
for (var i = 0, ii = this.pages.length; i < ii; i++) {
if (this.pages[i] &&
this.pages[i].renderingState !== RenderingStates.FINISHED) {
this.pages[i].reset();
}
}
this.pdfDocument.cleanup();
},
getHighestPriority: function pdfViewGetHighestPriority(visible, views,
@ -2643,28 +2791,31 @@ var PDFView = {
PDFView.navigateTo(params.nameddest);
return;
}
var pageNumber, dest;
if ('page' in params) {
var pageNumber = (params.page | 0) || 1;
if ('zoom' in params) {
var zoomArgs = params.zoom.split(','); // scale,left,top
// building destination array
pageNumber = (params.page | 0) || 1;
}
if ('zoom' in params) {
var zoomArgs = params.zoom.split(','); // scale,left,top
// building destination array
// If the zoom value, it has to get divided by 100. If it is a string,
// it should stay as it is.
var zoomArg = zoomArgs[0];
var zoomArgNumber = parseFloat(zoomArg);
if (zoomArgNumber)
zoomArg = zoomArgNumber / 100;
var dest = [null, {name: 'XYZ'},
zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
zoomArg];
var currentPage = this.pages[pageNumber - 1];
currentPage.scrollIntoView(dest);
} else {
this.page = pageNumber; // simple page
// If the zoom value, it has to get divided by 100. If it is a string,
// it should stay as it is.
var zoomArg = zoomArgs[0];
var zoomArgNumber = parseFloat(zoomArg);
if (zoomArgNumber) {
zoomArg = zoomArgNumber / 100;
}
dest = [null, {name: 'XYZ'},
zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
zoomArg];
}
if (dest) {
var currentPage = this.pages[(pageNumber || this.page) - 1];
currentPage.scrollIntoView(dest);
} else if (pageNumber) {
this.page = pageNumber; // simple page
}
if ('pagemode' in params) {
var toggle = document.getElementById('sidebarToggle');
@ -2935,12 +3086,11 @@ var PageView = function pageView(container, id, scale,
this.rotation = 0;
this.scale = scale || 1.0;
this.viewport = defaultViewport;
this.pdfPageRotate = defaultViewport.rotate;
this.pdfPageRotate = defaultViewport.rotation;
this.renderingState = RenderingStates.INITIAL;
this.resume = null;
this.textContent = null;
this.textLayer = null;
this.zoomLayer = null;
@ -2962,7 +3112,8 @@ var PageView = function pageView(container, id, scale,
this.setPdfPage = function pageViewSetPdfPage(pdfPage) {
this.pdfPage = pdfPage;
this.pdfPageRotate = pdfPage.rotate;
this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS);
var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation);
this.stats = pdfPage.stats;
this.reset();
};
@ -3279,7 +3430,7 @@ var PageView = function pageView(container, id, scale,
width / CSS_UNITS;
heightScale = (PDFView.container.clientHeight - SCROLLBAR_PADDING) /
height / CSS_UNITS;
scale = Math.min(widthScale, heightScale);
scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
break;
default:
return;
@ -3313,10 +3464,9 @@ var PageView = function pageView(container, id, scale,
};
this.getTextContent = function pageviewGetTextContent() {
if (!this.textContent) {
this.textContent = this.pdfPage.getTextContent();
}
return this.textContent;
return PDFView.getPage(this.id).then(function(pdfPage) {
return pdfPage.getTextContent();
});
};
this.draw = function pageviewDraw(callback) {
@ -3364,8 +3514,8 @@ var PageView = function pageView(container, id, scale,
outputScale.scaled = true;
}
canvas.width = Math.floor(viewport.width * outputScale.sx);
canvas.height = Math.floor(viewport.height * outputScale.sy);
canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0;
canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0;
canvas.style.width = Math.floor(viewport.width) + 'px';
canvas.style.height = Math.floor(viewport.height) + 'px';
// Add the viewport so it's known what it was originally drawn with.
@ -3398,6 +3548,8 @@ var PageView = function pageView(container, id, scale,
(1 / outputScale.sy) + ')';
CustomStyle.setProp('transform' , textLayerDiv, cssScale);
CustomStyle.setProp('transformOrigin' , textLayerDiv, '0% 0%');
textLayerDiv.dataset._scaleX = outputScale.sx;
textLayerDiv.dataset._scaleY = outputScale.sy;
}
// Checking if document fonts are used only once
@ -3813,27 +3965,24 @@ var TextLayerBuilder = function textLayerBuilder(options) {
if ('isWhitespace' in textDiv.dataset) {
continue;
}
textLayerFrag.appendChild(textDiv);
ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily;
var width = ctx.measureText(textDiv.textContent).width;
if (width > 0) {
textLayerFrag.appendChild(textDiv);
var textScale = textDiv.dataset.canvasWidth / width;
var rotation = textDiv.dataset.angle;
var transform = 'scale(' + textScale + ', 1)';
transform = 'rotate(' + rotation + 'deg) ' + transform;
CustomStyle.setProp('transform' , textDiv, transform);
CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%');
textLayerDiv.appendChild(textDiv);
}
}
textLayerDiv.appendChild(textLayerFrag);
this.renderingDone = true;
this.updateMatches();
textLayerDiv.appendChild(textLayerFrag);
};
this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() {
@ -4587,7 +4736,7 @@ window.addEventListener('keydown', function keydown(evt) {
switch (evt.keyCode) {
case 70: // f
if (!PDFView.supportsIntegratedFind) {
PDFFindBar.toggle();
PDFFindBar.open();
handled = true;
}
break;
@ -4629,6 +4778,11 @@ window.addEventListener('keydown', function keydown(evt) {
SecondaryToolbar.presentationModeClick();
handled = true;
break;
case 71: // g
// focuses input#pageNumber field
document.getElementById('pageNumber').select();
handled = true;
break;
}
}

View File

@ -5,6 +5,7 @@ content/PdfJs.jsm
content/PdfJsTelemetry.jsm
content/build/pdf.js
content/build/pdf.worker.js
content/default_preferences.js
content/network.js
content/web/debugger.js
content/web/images/annotation-check.svg

View File

@ -1187,10 +1187,14 @@ paintflashingManual=Draw repainted areas in different colors
# tooltip of button in devtools toolbox which toggles paint flashing.
paintflashingTooltip=Highlight painted area
# LOCALIZATION NOTE (paintflashingOnDesc) A very short string used to describe the
# function of the "paintflashing on" command.
# LOCALIZATION NOTE (paintflashingToggleDesc) A very short string used to describe the
# function of the "paintflashing toggle" command.
paintflashingToggleDesc=Toggle paint flashing
# LOCALIZATION NOTE (splitconsoleTooltip) A string displayed as the
# tooltip of button in devtools toolbox which toggles the split webconsole.
splitconsoleTooltip=Toggle split console
# LOCALIZATION NOTE (appCacheDesc) A very short string used to describe the
# function of the "appcache" command
appCacheDesc=Application cache utilities

View File

@ -115,10 +115,11 @@ var Appbar = {
onMenuButton: function(aEvent) {
let typesArray = [];
if (!BrowserUI.isStartTabVisible)
if (!BrowserUI.isStartTabVisible) {
typesArray.push("find-in-page");
if (ContextCommands.getPageSource())
typesArray.push("view-page-source");
if (ContextCommands.getPageSource())
typesArray.push("view-page-source");
}
if (ContextCommands.getStoreLink())
typesArray.push("ms-meta-data");
if (ConsolePanelView.enabled)

View File

@ -265,10 +265,10 @@ Desktop browser's sync prefs.
<hbox id="toolbar-context-page" pack="end">
<circularprogressindicator id="download-progress" class="appbar-primary"
oncommand="MetroDownloadsView.onDownloadButton()"/>
<toolbarbutton id="star-button" class="appbar-primary"
<toolbarbutton id="star-button" class="appbar-primary hide-on-start"
type="checkbox"
oncommand="Appbar.onStarButton()"/>
<toolbarbutton id="pin-button" class="appbar-primary"
<toolbarbutton id="pin-button" class="appbar-primary hide-on-start"
type="checkbox"
oncommand="Appbar.onPinButton()"/>
<toolbarbutton id="menu-button" class="appbar-primary"

View File

@ -118,6 +118,14 @@ var ContextMenuHandler = {
} else {
Util.dumpLn("error: target element does not support nsIDOMNSEditableElement");
}
} else if (this._target.isContentEditable) {
try {
this._target.ownerDocument.execCommand("paste",
false,
Ci.nsIClipboard.kGlobalClipboard);
} catch (ex) {
dump("ContextMenuHandler: exception pasting into contentEditable: " + ex.message + "\n");
}
}
this.reset();
},
@ -134,6 +142,12 @@ var ContextMenuHandler = {
} else {
Util.dumpLn("error: target element does not support nsIDOMNSEditableElement");
}
} else if (this._target.isContentEditable) {
try {
this._target.ownerDocument.execCommand("cut", false);
} catch (ex) {
dump("ContextMenuHandler: exception cutting from contentEditable: " + ex.message + "\n");
}
}
this.reset();
},
@ -188,12 +202,13 @@ var ContextMenuHandler = {
contentDisposition: "",
string: "",
};
let uniqueStateTypes = new Set();
// Do checks for nodes that never have children.
if (popupNode.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
// See if the user clicked on an image.
if (popupNode instanceof Ci.nsIImageLoadingContent && popupNode.currentURI) {
state.types.push("image");
uniqueStateTypes.add("image");
state.label = state.mediaURL = popupNode.currentURI.spec;
imageUrl = state.mediaURL;
this._target = popupNode;
@ -218,6 +233,7 @@ var ContextMenuHandler = {
let elem = popupNode;
let isText = false;
let isEditableText = false;
while (elem) {
if (elem.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
@ -230,7 +246,7 @@ var ContextMenuHandler = {
continue;
}
state.types.push("link");
uniqueStateTypes.add("link");
state.label = state.linkURL = this._getLinkURL(elem);
linkUrl = state.linkURL;
state.linkTitle = popupNode.textContent || popupNode.title;
@ -238,49 +254,61 @@ var ContextMenuHandler = {
// mark as text so we can pickup on selection below
isText = true;
break;
} else if (Util.isTextInput(elem)) {
}
// is the target contentEditable (not just inheriting contentEditable)
else if (elem.contentEditable == "true") {
this._target = elem;
isEditableText = true;
isText = true;
uniqueStateTypes.add("input-text");
if (elem.textContent.length) {
uniqueStateTypes.add("selectable");
} else {
uniqueStateTypes.add("input-empty");
}
break;
}
// is the target a text input
else if (Util.isTextInput(elem)) {
this._target = elem;
isEditableText = true;
uniqueStateTypes.add("input-text");
let selectionStart = elem.selectionStart;
let selectionEnd = elem.selectionEnd;
state.types.push("input-text");
this._target = elem;
// Don't include "copy" for password fields.
if (!(elem instanceof Ci.nsIDOMHTMLInputElement) || elem.mozIsTextField(true)) {
// If there is a selection add cut and copy
if (selectionStart != selectionEnd) {
state.types.push("cut");
state.types.push("copy");
uniqueStateTypes.add("cut");
uniqueStateTypes.add("copy");
state.string = elem.value.slice(selectionStart, selectionEnd);
} else if (elem.value && elem.textLength) {
// There is text and it is not selected so add selectable items
state.types.push("selectable");
uniqueStateTypes.add("selectable");
state.string = elem.value;
}
}
if (!elem.textLength) {
state.types.push("input-empty");
}
let flavors = ["text/unicode"];
let cb = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
let hasData = cb.hasDataMatchingFlavors(flavors,
flavors.length,
Ci.nsIClipboard.kGlobalClipboard);
if (hasData && !elem.readOnly) {
state.types.push("paste");
uniqueStateTypes.add("input-empty");
}
break;
} else if (Util.isText(elem)) {
}
// is the target an element containing text content
else if (Util.isText(elem)) {
isText = true;
} else if (elem instanceof Ci.nsIDOMHTMLMediaElement ||
}
// is the target a media element
else if (elem instanceof Ci.nsIDOMHTMLMediaElement ||
elem instanceof Ci.nsIDOMHTMLVideoElement) {
state.label = state.mediaURL = (elem.currentSrc || elem.src);
state.types.push((elem.paused || elem.ended) ?
uniqueStateTypes.add((elem.paused || elem.ended) ?
"media-paused" : "media-playing");
if (elem instanceof Ci.nsIDOMHTMLVideoElement) {
state.types.push("video");
uniqueStateTypes.add("video");
}
}
}
@ -295,20 +323,37 @@ var ContextMenuHandler = {
let selection = targetWindow.getSelection();
if (selection && this._tapInSelection(selection, aX, aY)) {
state.string = targetWindow.getSelection().toString();
state.types.push("copy");
state.types.push("selected-text");
uniqueStateTypes.add("copy");
uniqueStateTypes.add("selected-text");
if (isEditableText) {
uniqueStateTypes.add("cut");
}
} else {
// Add general content text if this isn't anything specific
if (state.types.indexOf("image") == -1 &&
state.types.indexOf("media") == -1 &&
state.types.indexOf("video") == -1 &&
state.types.indexOf("link") == -1 &&
state.types.indexOf("input-text") == -1) {
state.types.push("content-text");
if (!(
uniqueStateTypes.has("image") ||
uniqueStateTypes.has("media") ||
uniqueStateTypes.has("video") ||
uniqueStateTypes.has("link") ||
uniqueStateTypes.has("input-text")
)) {
uniqueStateTypes.add("content-text");
}
}
}
// Is paste applicable here?
if (isEditableText) {
let flavors = ["text/unicode"];
let cb = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
let hasData = cb.hasDataMatchingFlavors(flavors,
flavors.length,
Ci.nsIClipboard.kGlobalClipboard);
// add paste if there's data
if (hasData && !elem.readOnly) {
uniqueStateTypes.add("paste");
}
}
// populate position and event source
state.xPos = offsetX + aX;
state.yPos = offsetY + aY;
@ -316,8 +361,9 @@ var ContextMenuHandler = {
for (let i = 0; i < this._types.length; i++)
if (this._types[i].handler(state, popupNode))
state.types.push(this._types[i].name);
uniqueStateTypes.add(this._types[i].name);
state.types = [type for (type of uniqueStateTypes)];
this._previousState = state;
sendAsyncMessage("Content:ContextMenu", state);

View File

@ -109,3 +109,24 @@
<!ENTITY optionsFlyout.key "o">
<!ENTITY syncFlyout.key "s">
<!ENTITY aboutFlyout.key "a">
<!-- FIRT RUN EXPERIENCE -->
<!-- LOCALIZATION NOTE (firstRunTabs.label,
firstRunTopSites.label,
firstRunBookmarks.label,
firstRunMenu.label,
firstRunHistory.label )
These strings appear on the Firefox Start page the first time
Firefox for Windows 8 Touch (Metro) is launched. Each one
has an arrow pointing toward the feature it references. The code
to display these strings is not enabled yet, but will be soon.
For now, you can see this mockup for an example of how they are
used: https://bug941284.bugzilla.mozilla.org/attachment.cgi?id=8344046
-->
<!ENTITY firstRunTabs.label "Looking for your tabs? Just pull down or right-click">
<!ENTITY firstRunTopSites.label "Go to the sites you visit most">
<!ENTITY firstRunBookmarks.label "Find pages you've saved for later">
<!ENTITY firstRunMenu.label "Access more features and options">
<!ENTITY firstRunHistory.label "See where you've been on the Web">
<!ENTITY firstRunWelcome.label "Welcome to &brandShortName;">
<!ENTITY firstRunDifferent.label "Different by design">

View File

@ -714,6 +714,7 @@ documenttab[selected] .documenttab-selection {
/* Contextual toolbar controls */
#toolbar-context-autocomplete,
.hide-on-start,
#toolbar-context-page {
transition-property: opacity, visibility;
transition-duration: @forward_transition_length@;
@ -721,7 +722,7 @@ documenttab[selected] .documenttab-selection {
}
#toolbar-contextual:not([autocomplete]) #toolbar-context-autocomplete,
#toolbar-contextual[startpage] #toolbar-context-page,
#toolbar-contextual[startpage] .hide-on-start,
#toolbar-contextual[autocomplete] #toolbar-context-page {
opacity: 0;
visibility: hidden;

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

View File

@ -156,6 +156,7 @@ browser.jar:
skin/classic/browser/devtools/command-responsivemode.png (devtools/command-responsivemode.png)
skin/classic/browser/devtools/command-scratchpad.png (devtools/command-scratchpad.png)
skin/classic/browser/devtools/command-tilt.png (devtools/command-tilt.png)
skin/classic/browser/devtools/command-console.png (devtools/command-console.png)
skin/classic/browser/devtools/alerticon-warning.png (devtools/alerticon-warning.png)
skin/classic/browser/devtools/ruleview.css (devtools/ruleview.css)
* skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

View File

@ -258,6 +258,7 @@ browser.jar:
skin/classic/browser/devtools/command-responsivemode.png (devtools/command-responsivemode.png)
skin/classic/browser/devtools/command-scratchpad.png (devtools/command-scratchpad.png)
skin/classic/browser/devtools/command-tilt.png (devtools/command-tilt.png)
skin/classic/browser/devtools/command-console.png (devtools/command-console.png)
skin/classic/browser/devtools/alerticon-warning.png (devtools/alerticon-warning.png)
skin/classic/browser/devtools/ruleview.css (devtools/ruleview.css)
skin/classic/browser/devtools/commandline.css (devtools/commandline.css)

View File

@ -441,6 +441,23 @@
-moz-image-region: rect(0px, 48px, 16px, 32px);
}
#command-button-splitconsole {
list-style-image: url("chrome://browser/skin/devtools/command-console.png");
-moz-image-region: rect(0px, 16px, 16px, 0px);
}
#command-button-splitconsole:hover {
-moz-image-region: rect(0px, 32px, 16px, 16px);
}
#command-button-splitconsole:hover:active {
-moz-image-region: rect(0px, 48px, 16px, 32px);
}
#command-button-splitconsole[checked=true] {
-moz-image-region: rect(0px, 64px, 16px, 48px);
}
/* Tabs */
.devtools-tabbar {

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

View File

@ -186,6 +186,7 @@ browser.jar:
skin/classic/browser/devtools/command-responsivemode.png (devtools/command-responsivemode.png)
skin/classic/browser/devtools/command-scratchpad.png (devtools/command-scratchpad.png)
skin/classic/browser/devtools/command-tilt.png (devtools/command-tilt.png)
skin/classic/browser/devtools/command-console.png (devtools/command-console.png)
skin/classic/browser/devtools/markup-view.css (../shared/devtools/markup-view.css)
skin/classic/browser/devtools/editor-error.png (devtools/editor-error.png)
skin/classic/browser/devtools/editor-breakpoint.png (devtools/editor-breakpoint.png)
@ -487,6 +488,7 @@ browser.jar:
skin/classic/aero/browser/devtools/command-responsivemode.png (devtools/command-responsivemode.png)
skin/classic/aero/browser/devtools/command-scratchpad.png (devtools/command-scratchpad.png)
skin/classic/aero/browser/devtools/command-tilt.png (devtools/command-tilt.png)
skin/classic/aero/browser/devtools/command-console.png (devtools/command-console.png)
skin/classic/aero/browser/devtools/alerticon-warning.png (devtools/alerticon-warning.png)
skin/classic/aero/browser/devtools/ruleview.css (devtools/ruleview.css)
skin/classic/aero/browser/devtools/commandline.css (devtools/commandline.css)

View File

@ -4,7 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
package org.mozilla.gecko.tests;
public class TestConstants {
public static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@";

View File

@ -12,7 +12,6 @@ import org.mozilla.gecko.FennecMochitestAssert;
import org.mozilla.gecko.FennecNativeActions;
import org.mozilla.gecko.FennecNativeDriver;
import org.mozilla.gecko.FennecTalosAssert;
import org.mozilla.gecko.TestConstants;
import org.mozilla.gecko.tests.components.*;
import org.mozilla.gecko.tests.helpers.*;

View File

@ -45,7 +45,7 @@ public class AboutHomeComponent extends BaseComponent {
(expectedPageIndex >= 0) ? expectedPageIndex : Page.values().length - 1;
}
assertEquals("The current HomePager page is " + Page.values()[expectedPageIndex],
assertEquals("The current HomePager page is " + expectedPage,
expectedPageIndex, getHomePagerView().getCurrentItem());
return this;
}

View File

@ -6631,7 +6631,27 @@ var SearchEngines = {
let filter = {
matches: function (aElement) {
return (aElement.form && NativeWindow.contextmenus.textContext.matches(aElement));
// Copied from body of isTargetAKeywordField function in nsContextMenu.js
if(!(aElement instanceof HTMLInputElement))
return false;
let form = aElement.form;
if (!form || aElement.type == "password")
return false;
let method = form.method.toUpperCase();
// These are the following types of forms we can create keywords for:
//
// method encoding type can create keyword
// GET * YES
// * YES
// POST * YES
// POST application/x-www-form-urlencoded YES
// POST text/plain NO ( a little tricky to do)
// POST multipart/form-data NO
// POST everything else YES
return (method == "GET" || method == "") ||
(form.enctype != "text/plain") && (form.enctype != "multipart/form-data");
}
};
SelectionHandler.actions.SEARCH_ADD = {

View File

@ -10,7 +10,6 @@ betnet.fr: could not connect to host
bigshinylock.minazo.net: could not connect to host
bitbucket.org: max-age too low: 2592000
blog.lookout.com: did not receive HSTS header
blueseed.co: could not connect to host
braintreegateway.com: did not receive HSTS header
braintreepayments.com: did not receive HSTS header
browserid.org: did not receive HSTS header
@ -19,10 +18,11 @@ cert.se: max-age too low: 2628001
checkout.google.com: did not receive HSTS header
chrome-devtools-frontend.appspot.com: did not receive HSTS header
chrome.google.com: did not receive HSTS header
cloud.google.com: did not receive HSTS header
code.google.com: did not receive HSTS header
codereview.chromium.org: did not receive HSTS header
crowdcurity.com: did not receive HSTS header
crypto.is: could not connect to host
crypto.is: did not receive HSTS header
csawctf.poly.edu: did not receive HSTS header
dl.google.com: did not receive HSTS header
docs.google.com: did not receive HSTS header
@ -35,6 +35,7 @@ epoxate.com: max-age too low: 259200
fatzebra.com.au: did not receive HSTS header
fj.simple.com: did not receive HSTS header
get.zenpayroll.com: did not receive HSTS header
glass.google.com: did not receive HSTS header
gmail.com: did not receive HSTS header
gocardless.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-hsts-000000000000000/getHSTSPreloadList.js :: processStsHeader :: line 124" data: no]
googlemail.com: did not receive HSTS header
@ -52,7 +53,8 @@ jitsi.org: did not receive HSTS header
jottit.com: did not receive HSTS header
kiwiirc.com: max-age too low: 5256000
ledgerscope.net: did not receive HSTS header
liberty.lavabit.com: did not receive HSTS header
liberty.lavabit.com: could not connect to host
lifeguard.aecom.com: max-age too low: 3600
lists.mayfirst.org: did not receive HSTS header
logentries.com: did not receive HSTS header
lumi.do: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-hsts-000000000000000/getHSTSPreloadList.js :: processStsHeader :: line 124" data: no]
@ -67,11 +69,11 @@ ottospora.nl: could not connect to host
packagist.org: max-age too low: 2592000
paypal.com: max-age too low: 14400
platform.lookout.com: could not connect to host
play.google.com: did not receive HSTS header
plus.google.com: did not receive HSTS header
plus.sandbox.google.com: did not receive HSTS header
profiles.google.com: did not receive HSTS header
rapidresearch.me: did not receive HSTS header
riseup.net: could not connect to host
romab.com: max-age too low: 2628000
sah3.net: could not connect to host
saturngames.co.uk: did not receive HSTS header

View File

@ -8,7 +8,7 @@
/*****************************************************************************/
#include <stdint.h>
const PRTime gPreloadListExpirationTime = INT64_C(1396696033320000);
const PRTime gPreloadListExpirationTime = INT64_C(1397301006735000);
class nsSTSPreload
{