merge fx-team to mozilla-central

This commit is contained in:
Carsten "Tomcat" Book 2013-12-07 12:47:39 +01:00
Родитель 488583768f 3dda85af3c
Коммит c78cc390fa
35 изменённых файлов: 2243 добавлений и 681 удалений

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

@ -35,13 +35,13 @@ geolocation API in Firefox.
## Using Geolocation in an Add-on ## ## Using Geolocation in an Add-on ##
Suppose we want to use the 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 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). [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): 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 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: object, and retrieves the user's current position:
var {Cc, Ci} = require("chrome"); var {Cc, Ci} = require("chrome");
@ -88,7 +88,7 @@ info: longitude: 93.0785269
</pre> </pre>
So far, so good. But the geolocation guide on MDN tells us that we must 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. before using the API.
So we'll extend the add-on to include an adapted version of the code in 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 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). [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 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).

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

@ -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 function is called. It is passed the object instance (i.e. `this`) as
the first parameter, followed by any parameters passed into the method. the first parameter, followed by any parameters passed into the method.
let { method } = require("sdk/lang/functional"); const { method } = require("sdk/lang/functional");
let myNumber = {
const times = (target, x) => target.number *= x;
const add = (target, x) => target.number += x;
const myNumber = {
times: method(times), times: method(times),
add: method(add), add: method(add),
number: 0 number: 0
}; };
function times (target, x) { return target.number *= x; }
function add (target, x) { return target.number += x; }
console.log(myNumber.number); // 0 console.log(myNumber.number); // 0
myNumber.add(10); // 10 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 may be reused and does not need to be repeated each time. This also enables you
to use these functions as event listeners. to use these functions as event listeners.
let { defer } = require("sdk/lang/functional"); const { defer } = require("sdk/lang/functional");
let fn = defer(function myEvent (event, value) { const fn = defer((event, value) => {
console.log(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`. Invokes `callee`, passing `params` as an argument and `self` as `this`.
Returns the value that is returned by `callee`. 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 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} @param callee {function}
Function to invoke. Function to invoke.
@param params {Array} @param params {Array}
@ -98,9 +95,9 @@ Returns the value that is returned by `callee`.
@function @function
Takes a function and bind values to one or more arguments, returning a new function of smaller arity. Takes a function and bind values to one or more arguments, returning a new function of smaller arity.
let { partial } = require("sdk/lang/functional"); const { partial } = require("sdk/lang/functional");
let add = function add (x, y) { return x + y; } const add = (x, y) => x + y;
let addOne = partial(add, 1); const addOne = partial(add, 1);
addOne(5); // 6 addOne(5); // 6
addOne(10); // 11 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 return value of the function that follows. In math terms, composing the functions
`f()`, `g()`, and `h()` produces `f(g(h()))`. `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; } const f2 = compose(square, increment);
function exclaim (statement) { return statement + "!"; } f2(5); // => 36
@param fn... {function} @param fn... {function}
Takes a variable number of functions as arguments and composes them from right to left. 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 allowing you to adjust arguments, run code before and after, and
conditionally execute the original function. conditionally execute the original function.
let { wrap } = require("sdk/lang/functional"); const { wrap } = require("sdk/lang/functional");
let wrappedHello = wrap(hello, function (fn, name) { const hello = name => "hello: " + name;
return "before, " + fn(name) + "after"; const wrappedHello = wrap(hello, (fn, name) =>
}); "before, " + fn(name) + "after");
wrappedHello("moe"); // "before, hello: moe, after" wrappedHello("moe"); // "before, hello: moe, after"
function hello (name) { return "hello: " + name; }
@param fn {function} @param fn {function}
The function to be passed into the `wrapper` function. The function to be passed into the `wrapper` function.
@ -170,8 +167,8 @@ conditionally execute the original function.
@function @function
Returns the same value that is used as the argument. In math: f(x) = x. Returns the same value that is used as the argument. In math: f(x) = x.
let { identity } = require("sdk/lang/functional"); const { identity } = require("sdk/lang/functional");
let x = 5; const x = 5;
identity(x); // 5 identity(x); // 5
@param value {mixed} @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 default `hashFunction` just uses the first argument to the memoized function as
the key. 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(50); // Returns [2, 5, 5], had to compute
memoizedFn(100); // Returns [2, 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 memoizedFn(50); // Returns [2, 5, 5] again, but pulled from cache
function primeFactorization (x) { const primeFactorization = x => {
// Some tricky stuff // Some tricky stuff
} }
@ -209,16 +206,14 @@ the key.
// function will just parse the last name, as our naive // function will just parse the last name, as our naive
// implementation assumes that they will share the same lineage // implementation assumes that they will share the same lineage
let getLineage = memoize(function (name) { const getLineage = memoize(name => {
// computes lineage // computes lineage
return data; return data;
}, hasher); }, hasher);
// Hashing function takes a string of first and last name // Hashing function takes a string of first and last name
// and returns the last name. // and returns the last name.
function hasher (input) { const hasher = input => input.split(" ")[1];
return input.split(" ")[1];
}
getLineage("homer simpson"); // Computes and returns information for "simpson" getLineage("homer simpson"); // Computes and returns information for "simpson"
getLineage("lisa simpson"); // Returns cached 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 milliseconds. If you pass additional, optional, arguments, they will be forwarded
on to the function when it is invoked. 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); delay(printAdd, 2000, 5, 10);
// Prints "5+10=15" in two seconds (2000ms) // Prints "5+10=15" in two seconds (2000ms)
function printAdd (a, b) { console.log(a + "+" + b + "=" + (a+b)); }
@param fn {function} @param fn {function}
A function to be delayed. 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 the value from the original call. Useful for initialization functions, instead
of having to set a boolean flag and checking it later. of having to set a boolean flag and checking it later.
let { once } = require("sdk/lang/functional"); const { once } = require("sdk/lang/functional");
let setup = once(function (env) { const setup = once(env => {
// initializing important things // initializing important things
console.log("successfully initialized " + env); console.log("successfully initialized " + env);
return 1; // Assume success and return 1 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 setup('dev'); // returns 1
// prints "successfully initialized dev" // prints "successfully initialized dev"
// Future attempts to call this function just return the cached // Future attempts to call this function just return the cached
// value that was returned previously // value that was returned previously
setup('production'); // Returns 1 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. The wrapped `fn` that can only be executed once.
</api> </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 @function
Creates a version of the input function that will return `this`. 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; } 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 person
.happyBirthday() .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`. The wrapped function that executes `fn` and returns `this`.
</api> </api>
<api name="cache"> <api name="field">
@function @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> </api>

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

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

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

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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 // Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
// those goes to him. // those goes to him.
@ -12,19 +12,33 @@ module.metadata = {
"stability": "unstable" "stability": "unstable"
}; };
const { setImmediate, setTimeout } = require("../timers");
const { deprecateFunction } = require("../util/deprecate"); 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 * Takes variadic numeber of functions and returns composed one.
* invoked it calls wrapped `lambda` and passes `this` as a first argument * Returned function pushes `this` pseudo-variable to the head
* and given argument as rest. * 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) { const method = (...lambdas) => {
return function method() { return function method(...args) {
return lambda.apply(null, [this].concat(Array.slice(arguments))); args.unshift(this);
} return lambdas.reduce((_, lambda) => lambda.apply(this, args),
} void(0));
};
};
exports.method = method; exports.method = method;
/** /**
@ -34,24 +48,13 @@ exports.method = method;
* function is reused, instead of creating a new one each time. This also allows * function is reused, instead of creating a new one each time. This also allows
* to use this functions as event listeners. * to use this functions as event listeners.
*/ */
function defer(f) { const defer = f => derive(function(...args) {
return function deferred() setImmediate(invoke, f, arguments, this); setImmediate(invoke, f, args, this);
} }, f);
exports.defer = defer; exports.defer = defer;
// Exporting `remit` alias as `defer` may conflict with promises. // Exporting `remit` alias as `defer` may conflict with promises.
exports.remit = defer; 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` * Invokes `callee` by passing `params` as an arguments and `self` as `this`
* pseudo-variable. Returns value that is returned by a callee. * pseudo-variable. Returns value that is returned by a callee.
@ -62,7 +65,7 @@ exports.chain = chain;
* @param {Object} self * @param {Object} self
* Object to be passed as a `this` pseudo variable. * 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; exports.invoke = invoke;
/** /**
@ -74,14 +77,16 @@ exports.invoke = invoke;
* *
* @returns The new function with binded values * @returns The new function with binded values
*/ */
function partial(fn) { const partial = (f, ...curried) => {
if (typeof fn !== "function") if (typeof(f) !== "function")
throw new TypeError(String(fn) + " is not a function"); throw new TypeError(String(f) + " is not a function");
let args = Array.slice(arguments, 1); let fn = derive(function(...args) {
return f.apply(this, curried.concat(args));
return function() fn.apply(this, args.concat(Array.slice(arguments))); }, f);
} fn.arity = arity(f) - curried.length;
return fn;
};
exports.partial = partial; exports.partial = partial;
/** /**
@ -98,12 +103,11 @@ exports.partial = partial;
* console.log(sum(2, 2)) // 4 * console.log(sum(2, 2)) // 4
* console.log(sum(2)(4)) // 6 * console.log(sum(2)(4)) // 6
*/ */
var curry = new function() { const curry = new function() {
function currier(fn, arity, params) { const currier = (fn, arity, params) => {
// Function either continues to curry arguments or executes function // Function either continues to curry arguments or executes function
// if desired arguments have being collected. // if desired arguments have being collected.
return function curried() { const curried = function(...input) {
var input = Array.slice(arguments);
// Prepend all curried arguments to the given arguments. // Prepend all curried arguments to the given arguments.
if (params) input.unshift.apply(input, params); if (params) input.unshift.apply(input, params);
// If expected number of arguments has being collected invoke fn, // 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) : return (input.length >= arity) ? fn.apply(this, input) :
currier(fn, arity, input); currier(fn, arity, input);
}; };
} curried.arity = arity - (params ? params.length : 0);
return function curry(fn) { return curried;
return currier(fn, fn.length); };
}
return fn => currier(fn, arity(fn));
}; };
exports.curry = curry; exports.curry = curry;
@ -131,12 +136,12 @@ exports.curry = curry;
* *
* welcome('moe'); // => 'hi: moe!' * welcome('moe'); // => 'hi: moe!'
*/ */
function compose() { function compose(...lambdas) {
let lambdas = Array.slice(arguments); return function composed(...args) {
return function composed() { let index = lambdas.length;
let args = Array.slice(arguments), index = lambdas.length;
while (0 <= --index) while (0 <= --index)
args = [ lambdas[index].apply(this, args) ]; args = [lambdas[index].apply(this, args)];
return args[0]; return args[0];
}; };
} }
@ -155,16 +160,15 @@ exports.compose = compose;
* *
* hello(); // => 'before, hello: moe, after' * hello(); // => 'before, hello: moe, after'
*/ */
function wrap(f, wrapper) { const wrap = (f, wrapper) => derive(function wrapped(...args) {
return function wrapped() return wrapper.apply(this, [f].concat(args));
wrapper.apply(this, [ f ].concat(Array.slice(arguments))) }, f);
};
exports.wrap = wrap; exports.wrap = wrap;
/** /**
* Returns the same value that is used as the argument. In math: f(x) = x * 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; exports.identity = identity;
/** /**
@ -174,14 +178,25 @@ exports.identity = identity;
* the arguments to the original function. The default hashFunction just uses * the arguments to the original function. The default hashFunction just uses
* the first argument to the memoized function as the key. * the first argument to the memoized function as the key.
*/ */
function memoize(f, hasher) { const memoize = (f, hasher) => {
let memo = Object.create(null); let memo = Object.create(null);
let cache = new WeakMap();
hasher = hasher || identity; hasher = hasher || identity;
return function memoizer() { return derive(function memoizer(...args) {
let key = hasher.apply(this, arguments); const key = hasher.apply(this, args);
return key in memo ? memo[key] : (memo[key] = f.apply(this, arguments)); 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; exports.memoize = memoize;
/** /**
@ -189,9 +204,8 @@ exports.memoize = memoize;
* the optional arguments, they will be forwarded on to the function when it is * the optional arguments, they will be forwarded on to the function when it is
* invoked. * invoked.
*/ */
function delay(f, ms) { const delay = function delay(f, ms, ...args) {
let args = Array.slice(arguments, 2); setTimeout(() => f.apply(this, args), ms);
setTimeout(function(context) { return f.apply(context, args); }, ms, this);
}; };
exports.delay = delay; exports.delay = delay;
@ -201,10 +215,116 @@ exports.delay = delay;
* the original call. Useful for initialization functions, instead of having to * the original call. Useful for initialization functions, instead of having to
* set a boolean flag and then check it later. * set a boolean flag and then check it later.
*/ */
function once(f) { const once = f => {
let ran = false, cache; 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; exports.once = once;
// export cache as once will may be conflicting with event once a lot. // export cache as once will may be conflicting with event once a lot.
exports.cache = once; 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;

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

@ -1,21 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public /* 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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { setTimeout } = require('sdk/timers'); const { setTimeout } = require('sdk/timers');
const utils = require('sdk/lang/functional'); 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'); const { LoaderWithHookedConsole } = require('sdk/test/loader');
exports['test forwardApply'] = function(assert) { 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, assert.equal(invoke(sum, [2, 3], { a: 1 }), 6,
'passed arguments and pseoude-variable are used'); 'passed arguments and pseoude-variable are used');
assert.equal(invoke(sum.bind({ a: 2 }), [2, 3], { a: 1 }), 7, assert.equal(invoke(sum.bind({ a: 2 }), [2, 3], { a: 1 }), 7,
'bounded `this` pseoudo variable is used'); 'bounded `this` pseoudo variable is used');
} };
exports['test deferred function'] = function(assert, done) { exports['test deferred function'] = function(assert, done) {
let nextTurn = false; let nextTurn = false;
@ -26,13 +27,13 @@ exports['test deferred function'] = function(assert, done) {
done(); done();
} }
let fixture = { a: 1, method: defer(sum) } let fixture = { a: 1, method: defer(sum) };
fixture.method(2, 3); fixture.method(2, 3);
nextTurn = true; nextTurn = true;
}; };
exports['test partial function'] = function(assert) { 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 }; let foo = { a : 5 };
@ -68,11 +69,11 @@ exports['test compose'] = function(assert) {
let target = { let target = {
name: 'Joe', name: 'Joe',
greet: compose(function exclaim(sentence) { greet: compose(function exclaim(sentence) {
return sentence + '!' return sentence + '!';
}, function(title) { }, function(title) {
return 'hi : ' + title + ' ' + this.name; return 'hi : ' + title + ' ' + this.name;
}) })
} };
assert.equal(target.greet('Mr'), 'hi : Mr Joe!', assert.equal(target.greet('Mr'), 'hi : Mr Joe!',
'this can be passed in'); 'this can be passed in');
@ -106,7 +107,7 @@ exports['test wrap'] = function(assert) {
assert.equal(target.hi(), 'Hello Matteo', 'works with this'); assert.equal(target.hi(), 'Hello Matteo', 'works with this');
function noop() { }; function noop() { }
let wrapped = wrap(noop, function(f) { let wrapped = wrap(noop, function(f) {
return Array.slice(arguments); return Array.slice(arguments);
}); });
@ -117,7 +118,7 @@ exports['test wrap'] = function(assert) {
}; };
exports['test memoize'] = 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); let fibnitro = memoize(fib);
assert.equal(fib(10), 55, assert.equal(fib(10), 55,
@ -125,7 +126,7 @@ exports['test memoize'] = function(assert) {
assert.equal(fibnitro(10), 55, assert.equal(fibnitro(10), 55,
'a memoized version of fibonacci produces identical results'); '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 = {}; 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.notEqual(oo(1), oo(2), 'values do not override');
assert.equal(o(3, v2), oo(2, 3), 'returns same value as un-memoized'); assert.equal(o(3, v2), oo(2, 3), 'returns same value as un-memoized');
let get = memoize(function(attribute) this[attribute]) let get = memoize(function(attribute) { return this[attribute]; });
let target = { name: 'Bob', get: get } let target = { name: 'Bob', get: get };
assert.equal(target.get('name'), 'Bob', 'has correct `this`'); assert.equal(target.get('name'), 'Bob', 'has correct `this`');
assert.equal(target.get.call({ name: 'Jack' }, 'name'), 'Bob', 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'); 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) { exports['test delay with this'] = function(assert, done) {
let context = {} let context = {};
delay.call(context, function(name) { delay.call(context, function(name) {
assert.equal(this, context, 'this was passed in'); assert.equal(this, context, 'this was passed in');
assert.equal(name, 'Tom', 'argument was passed in'); assert.equal(name, 'Tom', 'argument was passed in');
done(); done();
}, 10, 'Tom'); }, 10, 'Tom');
} };
exports['test once'] = function(assert) { exports['test once'] = function(assert) {
let n = 0; let n = 0;
@ -172,7 +173,12 @@ exports['test once'] = function(assert) {
assert.equal(n, 1, 'only incremented once'); 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();
target.update(); target.update();
@ -182,7 +188,7 @@ exports['test once'] = function(assert) {
exports['test once with argument'] = function(assert) { exports['test once with argument'] = function(assert) {
let n = 0; let n = 0;
let increment = once(function(a) n++); let increment = once(a => n++);
increment(); increment();
increment('foo'); increment('foo');
@ -195,11 +201,121 @@ exports['test once with argument'] = function(assert) {
assert.equal(n, 1, 'only incremented once'); 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; }; let Player = function () { this.volume = 5; };
Player.prototype = { Player.prototype = {
setBand: chain(function (band) this.band = band), setBand: chainable(function (band) { return (this.band = band); }),
incVolume: chain(function () this.volume++) incVolume: chainable(function () { return this.volume++; })
}; };
let player = new Player(); let player = new Player();
player player
@ -210,4 +326,97 @@ exports['test chain'] = function (assert) {
assert.equal(player.volume, 11, 'accepts no arguments in chain'); 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); require('test').run(exports);

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

@ -1093,7 +1093,7 @@ pref("devtools.toolbox.footer.height", 250);
pref("devtools.toolbox.sidebar.width", 500); pref("devtools.toolbox.sidebar.width", 500);
pref("devtools.toolbox.host", "bottom"); pref("devtools.toolbox.host", "bottom");
pref("devtools.toolbox.selectedTool", "webconsole"); 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.sideEnabled", true);
pref("devtools.toolbox.zoomValue", "1"); pref("devtools.toolbox.zoomValue", "1");

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

@ -2272,3 +2272,68 @@ gcli.addCommand({
} }
}); });
}(this)); }(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));

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

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

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

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

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

@ -1,4 +1,4 @@
This is the pdf.js project output, https://github.com/mozilla/pdf.js 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

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

@ -16,7 +16,7 @@
*/ */
/* jshint esnext:true */ /* jshint esnext:true */
/* globals Components, Services, XPCOMUtils, NetUtil, PrivateBrowsingUtils, /* globals Components, Services, XPCOMUtils, NetUtil, PrivateBrowsingUtils,
dump, NetworkManager, PdfJsTelemetry */ dump, NetworkManager, PdfJsTelemetry, DEFAULT_PREFERENCES */
'use strict'; 'use strict';
@ -33,12 +33,16 @@ const PDF_CONTENT_TYPE = 'application/pdf';
const PREF_PREFIX = 'pdfjs'; const PREF_PREFIX = 'pdfjs';
const PDF_VIEWER_WEB_PAGE = 'resource://pdf.js/web/viewer.html'; const PDF_VIEWER_WEB_PAGE = 'resource://pdf.js/web/viewer.html';
const MAX_DATABASE_LENGTH = 4096; const MAX_DATABASE_LENGTH = 4096;
const MAX_STRING_PREF_LENGTH = 4096;
Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm'); Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/NetUtil.jsm'); Cu.import('resource://gre/modules/NetUtil.jsm');
Cu.import('resource://pdf.js/network.js'); Cu.import('resource://pdf.js/network.js');
// Load the default preferences.
Cu.import('resource://pdf.js/default_preferences.js');
XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils', XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
'resource://gre/modules/PrivateBrowsingUtils.jsm'); 'resource://gre/modules/PrivateBrowsingUtils.jsm');
@ -58,6 +62,10 @@ function getChromeWindow(domWindow) {
return containingBrowser.ownerDocument.defaultView; return containingBrowser.ownerDocument.defaultView;
} }
function setBoolPref(pref, value) {
Services.prefs.setBoolPref(pref, value);
}
function getBoolPref(pref, def) { function getBoolPref(pref, def) {
try { try {
return Services.prefs.getBoolPref(pref); 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) { function getIntPref(pref, def) {
try { try {
return Services.prefs.getIntPref(pref); return Services.prefs.getIntPref(pref);
@ -431,6 +443,62 @@ ChromeActions.prototype = {
} }
getChromeWindow(this.domWindow).gFindBar getChromeWindow(this.domWindow).gFindBar
.updateControlState(result, findPrevious); .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 * This is for range requests
*/ */
function RangedChromeActions( function RangedChromeActions(
domWindow, contentDispositionFilename, originalRequest) { domWindow, contentDispositionFilename, originalRequest,
dataListener) {
ChromeActions.call(this, domWindow, contentDispositionFilename); ChromeActions.call(this, domWindow, contentDispositionFilename);
this.dataListener = dataListener;
this.originalRequest = originalRequest;
this.pdfUrl = originalRequest.URI.spec; this.pdfUrl = originalRequest.URI.spec;
this.contentLength = originalRequest.contentLength; this.contentLength = originalRequest.contentLength;
@ -487,11 +558,15 @@ var RangedChromeActions = (function RangedChromeActionsClosure() {
proto.constructor = RangedChromeActions; proto.constructor = RangedChromeActions;
proto.initPassiveLoading = function RangedChromeActions_initPassiveLoading() { proto.initPassiveLoading = function RangedChromeActions_initPassiveLoading() {
this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
this.originalRequest = null;
this.domWindow.postMessage({ this.domWindow.postMessage({
pdfjsLoadAction: 'supportsRangedLoading', pdfjsLoadAction: 'supportsRangedLoading',
pdfUrl: this.pdfUrl, pdfUrl: this.pdfUrl,
length: this.contentLength length: this.contentLength,
data: this.dataListener.getData()
}, '*'); }, '*');
this.dataListener = null;
return true; return true;
}; };
@ -692,8 +767,8 @@ PdfStreamConverter.prototype = {
* 1. asyncConvertData stores the listener * 1. asyncConvertData stores the listener
* 2. onStartRequest creates a new channel, streams the viewer * 2. onStartRequest creates a new channel, streams the viewer
* 3. If range requests are supported: * 3. If range requests are supported:
* 3.1. Suspends and cancels the request so we can issue range * 3.1. Leave the request open until the viewer is ready to switch to
* requests instead. * range requests.
* *
* If range rquests are not supported: * If range rquests are not supported:
* 3.1. Read the stream as it's loaded in onDataAvailable to send * 3.1. Read the stream as it's loaded in onDataAvailable to send
@ -779,18 +854,12 @@ PdfStreamConverter.prototype = {
PdfJsTelemetry.onViewerIsUsed(); PdfJsTelemetry.onViewerIsUsed();
PdfJsTelemetry.onDocumentSize(aRequest.contentLength); PdfJsTelemetry.onDocumentSize(aRequest.contentLength);
if (!rangeRequest) {
// Creating storage for PDF data // Creating storage for PDF data
var contentLength = aRequest.contentLength; var contentLength = aRequest.contentLength;
this.dataListener = new PdfDataListener(contentLength); this.dataListener = new PdfDataListener(contentLength);
this.binaryStream = Cc['@mozilla.org/binaryinputstream;1'] this.binaryStream = Cc['@mozilla.org/binaryinputstream;1']
.createInstance(Ci.nsIBinaryInputStream); .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();
}
// Create a new channel that is viewer loaded as a resource. // Create a new channel that is viewer loaded as a resource.
var ioService = Services.io; var ioService = Services.io;
@ -816,12 +885,8 @@ PdfStreamConverter.prototype = {
var domWindow = getDOMWindow(channel); var domWindow = getDOMWindow(channel);
var actions; var actions;
if (rangeRequest) { if (rangeRequest) {
// We are going to be issuing range requests, so cancel the actions = new RangedChromeActions(
// original request domWindow, contentDispositionFilename, aRequest, dataListener);
aRequest.resume();
aRequest.cancel(Cr.NS_BINDING_ABORTED);
actions = new RangedChromeActions(domWindow,
contentDispositionFilename, aRequest);
} else { } else {
actions = new StandardChromeActions( actions = new StandardChromeActions(
domWindow, contentDispositionFilename, dataListener); domWindow, contentDispositionFilename, dataListener);

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

@ -20,8 +20,8 @@ if (typeof PDFJS === 'undefined') {
(typeof window !== 'undefined' ? window : this).PDFJS = {}; (typeof window !== 'undefined' ? window : this).PDFJS = {};
} }
PDFJS.version = '0.8.681'; PDFJS.version = '0.8.759';
PDFJS.build = '48c672b'; PDFJS.build = 'd3b5aa3';
(function pdfjsWrapper() { (function pdfjsWrapper() {
// Use strict in our context only - users might not want it // 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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref */ /* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL */
'use strict'; 'use strict';
@ -78,6 +78,98 @@ if (!globalScope.PDFJS) {
globalScope.PDFJS.pdfBug = false; 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 // Use only for debugging purposes. This should not be used in any code that is
// in mozilla master. // in mozilla master.
@ -292,7 +384,7 @@ var MissingDataException = (function MissingDataExceptionClosure() {
function MissingDataException(begin, end) { function MissingDataException(begin, end) {
this.begin = begin; this.begin = begin;
this.end = end; this.end = end;
this.message = 'Missing data [begin, end)'; this.message = 'Missing data [' + begin + ', ' + end + ')';
} }
MissingDataException.prototype = new Error(); 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 * Builds a promise that is resolved when all the passed in promises are
* resolved. * 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. * @return {Promise} New dependant promise.
*/ */
Promise.all = function Promise_all(promises) { 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) { for (var i = 0, ii = promises.length; i < ii; ++i) {
var promise = promises[i]; var promise = promises[i];
promise.then((function(i) { var resolve = (function(i) {
return function(value) { return function(value) {
if (deferred._status === STATUS_REJECTED) { if (deferred._status === STATUS_REJECTED) {
return; return;
@ -895,11 +987,24 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
if (unresolved === 0) if (unresolved === 0)
deferred.resolve(results); deferred.resolve(results);
}; };
})(i), reject); })(i);
if (Promise.isPromise(promise)) {
promise.then(resolve, reject);
} else {
resolve(promise);
}
} }
return deferred; 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 = { Promise.prototype = {
_status: null, _status: null,
_value: null, _value: null,
@ -913,7 +1018,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() {
} }
if (status == STATUS_RESOLVED && if (status == STATUS_RESOLVED &&
value && typeof(value.then) === 'function') { Promise.isPromise(value)) {
value.then(this._updateStatus.bind(this, STATUS_RESOLVED), value.then(this._updateStatus.bind(this, STATUS_RESOLVED),
this._updateStatus.bind(this, STATUS_REJECTED)); this._updateStatus.bind(this, STATUS_REJECTED));
return; return;
@ -1016,7 +1121,7 @@ var StatTimer = (function StatTimerClosure() {
})(); })();
PDFJS.createBlob = function createBlob(data, contentType) { PDFJS.createBlob = function createBlob(data, contentType) {
if (typeof Blob === 'function') if (typeof Blob !== 'undefined')
return new Blob([data], { type: contentType }); return new Blob([data], { type: contentType });
// Blob builder is deprecated in FF14 and removed in FF18. // Blob builder is deprecated in FF14 and removed in FF18.
var bb = new MozBlobBuilder(); var bb = new MozBlobBuilder();
@ -1024,10 +1129,38 @@ PDFJS.createBlob = function createBlob(data, contentType) {
return bb.getBlob(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) { function MessageHandler(name, comObj) {
this.name = name; this.name = name;
this.comObj = comObj; this.comObj = comObj;
this.callbackIndex = 1; this.callbackIndex = 1;
this.postMessageTransfers = true;
var callbacks = this.callbacks = {}; var callbacks = this.callbacks = {};
var ah = this.actionHandler = {}; var ah = this.actionHandler = {};
@ -1094,8 +1227,9 @@ MessageHandler.prototype = {
* @param {String} actionName Action to call. * @param {String} actionName Action to call.
* @param {JSON} data JSON data to send. * @param {JSON} data JSON data to send.
* @param {function} [callback] Optional callback that will handle a reply. * @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 = { var message = {
action: actionName, action: actionName,
data: data data: data
@ -1105,16 +1239,20 @@ MessageHandler.prototype = {
this.callbacks[callbackId] = callback; this.callbacks[callbackId] = callback;
message.callbackId = callbackId; 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(); var img = new Image();
img.onload = (function loadJpegStream_onloadClosure() { img.onload = (function loadJpegStream_onloadClosure() {
objs.resolve(id, img); 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) { resourcesPromise.then(function(resources) {
var opList = new OperatorList(); 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); evaluator.getOperatorList(this.appearance, resources, opList);
opList.addOp('endAnnotation', []); opList.addOp(OPS.endAnnotation, []);
promise.resolve(opList); promise.resolve(opList);
}.bind(this)); }.bind(this));
@ -3526,12 +3664,12 @@ var Annotation = (function AnnotationClosure() {
annotationPromises.push(annotations[i].getOperatorList(partialEvaluator)); annotationPromises.push(annotations[i].getOperatorList(partialEvaluator));
} }
Promise.all(annotationPromises).then(function(datas) { Promise.all(annotationPromises).then(function(datas) {
opList.addOp('beginAnnotations', []); opList.addOp(OPS.beginAnnotations, []);
for (var i = 0, n = datas.length; i < n; ++i) { for (var i = 0, n = datas.length; i < n; ++i) {
var annotOpList = datas[i]; var annotOpList = datas[i];
opList.addOpList(annotOpList); opList.addOpList(annotOpList);
} }
opList.addOp('endAnnotations', []); opList.addOp(OPS.endAnnotations, []);
annotationsReadyPromise.resolve(); annotationsReadyPromise.resolve();
}, reject); }, reject);
@ -3707,10 +3845,10 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
data.rgb = [0, 0, 0]; data.rgb = [0, 0, 0];
// TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY! // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY!
for (var i = 0, n = fnArray.length; i < n; ++i) { for (var i = 0, n = fnArray.length; i < n; ++i) {
var fnName = appearanceFnArray[i]; var fnId = appearanceFnArray[i];
var args = appearanceArgsArray[i]; var args = appearanceArgsArray[i];
if (fnName === 'setFont') { if (fnId === OPS.setFont) {
data.fontRefName = args[0]; data.fontRefName = args[0];
var size = args[1]; var size = args[1];
if (size < 0) { if (size < 0) {
@ -3720,9 +3858,9 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
data.fontDirection = 1; data.fontDirection = 1;
data.fontSize = size; data.fontSize = size;
} }
} else if (fnName === 'setFillRGBColor') { } else if (fnId === OPS.setFillRGBColor) {
data.rgb = args; data.rgb = args;
} else if (fnName === 'setFillGray') { } else if (fnId === OPS.setFillGray) {
var rgbValue = args[0] * 255; var rgbValue = args[0] * 255;
data.rgb = [rgbValue, rgbValue, rgbValue]; data.rgb = [rgbValue, rgbValue, rgbValue];
} }
@ -3973,7 +4111,9 @@ PDFJS.disableWorker = PDFJS.disableWorker === undefined ?
false : PDFJS.disableWorker; 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} * @var {String}
*/ */
PDFJS.workerSrc = PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc; 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; 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. * 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) * 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. * - data - A typed array with PDF data.
* - httpHeaders - Basic authentication headers. * - httpHeaders - Basic authentication headers.
* - password - For decrypting password-protected PDFs. * - 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 * @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 * 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) { getPage: function PDFDocumentProxy_getPage(number) {
return this.transport.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 * @return {Promise} A promise that is resolved with a lookup table for
* mapping named destinations to reference numbers. * mapping named destinations to reference numbers.
@ -4179,6 +4336,9 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
dataLoaded: function PDFDocumentProxy_dataLoaded() { dataLoaded: function PDFDocumentProxy_dataLoaded() {
return this.transport.dataLoaded(); return this.transport.dataLoaded();
}, },
cleanup: function PDFDocumentProxy_cleanup() {
this.transport.startCleanup();
},
destroy: function PDFDocumentProxy_destroy() { destroy: function PDFDocumentProxy_destroy() {
this.transport.destroy(); this.transport.destroy();
} }
@ -4398,10 +4558,10 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
*/ */
_renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk) { _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk) {
// Add the new chunk to the current operator list. // Add the new chunk to the current operator list.
Util.concatenateToArray(this.operatorList.fnArray, for (var i = 0, ii = operatorListChunk.length; i < ii; i++) {
operatorListChunk.fnArray); this.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
Util.concatenateToArray(this.operatorList.argsArray, this.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
operatorListChunk.argsArray); }
this.operatorList.lastChunk = operatorListChunk.lastChunk; this.operatorList.lastChunk = operatorListChunk.lastChunk;
// Notify all the rendering tasks there are more operators to be consumed. // 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); var messageHandler = new MessageHandler('main', worker);
this.messageHandler = messageHandler; this.messageHandler = messageHandler;
messageHandler.on('test', function transportTest(supportTypedArray) { messageHandler.on('test', function transportTest(data) {
var supportTypedArray = data && data.supportTypedArray;
if (supportTypedArray) { if (supportTypedArray) {
this.worker = worker; this.worker = worker;
if (!data.supportTransfers) {
PDFJS.postMessageTransfers = false;
}
this.setupMessageHandler(messageHandler); this.setupMessageHandler(messageHandler);
workerInitializedPromise.resolve(); workerInitializedPromise.resolve();
} else { } else {
@ -4467,10 +4631,16 @@ var WorkerTransport = (function WorkerTransportClosure() {
} }
}.bind(this)); }.bind(this));
var testObj = new Uint8Array(1); var testObj = new Uint8Array([PDFJS.postMessageTransfers ? 255 : 0]);
// Some versions of Opera throw a DATA_CLONE_ERR on // Some versions of Opera throw a DATA_CLONE_ERR on serializing the
// serializing the typed array. // typed array. Also, checking if we can use transfers.
messageHandler.send('test', testObj); try {
messageHandler.send('test', testObj, null, [testObj.buffer]);
} catch (ex) {
info('Cannot use postMessage transfers');
testObj[0] = 0;
messageHandler.send('test', testObj);
}
return; return;
} catch (e) { } catch (e) {
info('The worker has been disabled.'); info('The worker has been disabled.');
@ -4704,7 +4874,7 @@ var WorkerTransport = (function WorkerTransportClosure() {
}, this); }, this);
messageHandler.on('JpegDecode', function(data, promise) { messageHandler.on('JpegDecode', function(data, promise) {
var imageData = data[0]; var imageUrl = data[0];
var components = data[1]; var components = data[1];
if (components != 3 && components != 1) if (components != 3 && components != 1)
error('Only 3 component or 1 component can be returned'); 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}); promise.resolve({ data: buf, width: width, height: height});
}).bind(this); }).bind(this);
var src = 'data:image/jpeg;base64,' + window.btoa(imageData); img.src = imageUrl;
img.src = src;
}); });
}, },
@ -4774,6 +4943,16 @@ var WorkerTransport = (function WorkerTransportClosure() {
return promise; 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) { getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
this.messageHandler.send('GetAnnotationsRequest', this.messageHandler.send('GetAnnotationsRequest',
{ pageIndex: pageIndex }); { pageIndex: pageIndex });
@ -4787,6 +4966,21 @@ var WorkerTransport = (function WorkerTransportClosure() {
} }
); );
return promise; 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; return WorkerTransport;
@ -5007,7 +5201,7 @@ var InternalRenderTask = (function InternalRenderTaskClosure() {
this.operatorListIdx, this.operatorListIdx,
this._continue.bind(this), this._continue.bind(this),
this.stepper); this.stepper);
if (this.operatorListIdx === this.operatorList.fnArray.length) { if (this.operatorListIdx === this.operatorList.argsArray.length) {
this.running = false; this.running = false;
if (this.operatorList.lastChunk) { if (this.operatorList.lastChunk) {
this.gfx.endDrawing(); this.gfx.endDrawing();
@ -5536,37 +5730,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var EO_CLIP = {}; var EO_CLIP = {};
CanvasGraphics.prototype = { 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) { beginDrawing: function CanvasGraphics_beginDrawing(viewport, transparency) {
// For pdfs that use blend modes we have to clear the canvas else certain // 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 commonObjs = this.commonObjs;
var objs = this.objs; var objs = this.objs;
var fnName; var fnId;
var slowCommands = this.slowCommands;
while (true) { while (true) {
if (stepper && i === stepper.nextBreakPoint) { if (stepper && i === stepper.nextBreakPoint) {
@ -5627,10 +5789,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
return i; return i;
} }
fnName = fnArray[i]; fnId = fnArray[i];
if (fnName !== 'dependency') { if (fnId !== OPS.dependency) {
this[fnName].apply(this, argsArray[i]); this[fnId].apply(this, argsArray[i]);
} else { } else {
var deps = argsArray[i]; var deps = argsArray[i];
for (var n = 0, nn = deps.length; n < nn; n++) { for (var n = 0, nn = deps.length; n < nn; n++) {
@ -5657,10 +5819,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
return i; 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. // to continue exeution after a short delay.
// However, this is only possible if a 'continueCallback' is passed in. // 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); setTimeout(continueCallback, 0);
return i; return i;
} }
@ -6947,6 +7109,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
} }
}; };
for (var op in OPS) {
CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
}
return CanvasGraphics; return CanvasGraphics;
})(); })();
@ -6967,6 +7133,12 @@ var FontLoader = {
var styleSheet = styleElement.sheet; var styleSheet = styleElement.sheet;
styleSheet.insertRule(rule, styleSheet.cssRules.length); 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) { bind: function fontLoaderBind(fonts, callback) {
assert(!isWorker, 'bind() shall be called from main thread'); assert(!isWorker, 'bind() shall be called from main thread');

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -118,6 +118,14 @@ var ContextMenuHandler = {
} else { } else {
Util.dumpLn("error: target element does not support nsIDOMNSEditableElement"); 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(); this.reset();
}, },
@ -134,6 +142,12 @@ var ContextMenuHandler = {
} else { } else {
Util.dumpLn("error: target element does not support nsIDOMNSEditableElement"); 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(); this.reset();
}, },
@ -188,12 +202,13 @@ var ContextMenuHandler = {
contentDisposition: "", contentDisposition: "",
string: "", string: "",
}; };
let uniqueStateTypes = new Set();
// Do checks for nodes that never have children. // Do checks for nodes that never have children.
if (popupNode.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) { if (popupNode.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
// See if the user clicked on an image. // See if the user clicked on an image.
if (popupNode instanceof Ci.nsIImageLoadingContent && popupNode.currentURI) { if (popupNode instanceof Ci.nsIImageLoadingContent && popupNode.currentURI) {
state.types.push("image"); uniqueStateTypes.add("image");
state.label = state.mediaURL = popupNode.currentURI.spec; state.label = state.mediaURL = popupNode.currentURI.spec;
imageUrl = state.mediaURL; imageUrl = state.mediaURL;
this._target = popupNode; this._target = popupNode;
@ -218,6 +233,7 @@ var ContextMenuHandler = {
let elem = popupNode; let elem = popupNode;
let isText = false; let isText = false;
let isEditableText = false;
while (elem) { while (elem) {
if (elem.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) { if (elem.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
@ -230,7 +246,7 @@ var ContextMenuHandler = {
continue; continue;
} }
state.types.push("link"); uniqueStateTypes.add("link");
state.label = state.linkURL = this._getLinkURL(elem); state.label = state.linkURL = this._getLinkURL(elem);
linkUrl = state.linkURL; linkUrl = state.linkURL;
state.linkTitle = popupNode.textContent || popupNode.title; state.linkTitle = popupNode.textContent || popupNode.title;
@ -238,49 +254,61 @@ var ContextMenuHandler = {
// mark as text so we can pickup on selection below // mark as text so we can pickup on selection below
isText = true; isText = true;
break; 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 selectionStart = elem.selectionStart;
let selectionEnd = elem.selectionEnd; let selectionEnd = elem.selectionEnd;
state.types.push("input-text");
this._target = elem;
// Don't include "copy" for password fields. // Don't include "copy" for password fields.
if (!(elem instanceof Ci.nsIDOMHTMLInputElement) || elem.mozIsTextField(true)) { if (!(elem instanceof Ci.nsIDOMHTMLInputElement) || elem.mozIsTextField(true)) {
// If there is a selection add cut and copy // If there is a selection add cut and copy
if (selectionStart != selectionEnd) { if (selectionStart != selectionEnd) {
state.types.push("cut"); uniqueStateTypes.add("cut");
state.types.push("copy"); uniqueStateTypes.add("copy");
state.string = elem.value.slice(selectionStart, selectionEnd); state.string = elem.value.slice(selectionStart, selectionEnd);
} else if (elem.value && elem.textLength) { } else if (elem.value && elem.textLength) {
// There is text and it is not selected so add selectable items // There is text and it is not selected so add selectable items
state.types.push("selectable"); uniqueStateTypes.add("selectable");
state.string = elem.value; state.string = elem.value;
} }
} }
if (!elem.textLength) { if (!elem.textLength) {
state.types.push("input-empty"); uniqueStateTypes.add("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");
} }
break; break;
} else if (Util.isText(elem)) { }
// is the target an element containing text content
else if (Util.isText(elem)) {
isText = true; isText = true;
} else if (elem instanceof Ci.nsIDOMHTMLMediaElement || }
// is the target a media element
else if (elem instanceof Ci.nsIDOMHTMLMediaElement ||
elem instanceof Ci.nsIDOMHTMLVideoElement) { elem instanceof Ci.nsIDOMHTMLVideoElement) {
state.label = state.mediaURL = (elem.currentSrc || elem.src); 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"); "media-paused" : "media-playing");
if (elem instanceof Ci.nsIDOMHTMLVideoElement) { if (elem instanceof Ci.nsIDOMHTMLVideoElement) {
state.types.push("video"); uniqueStateTypes.add("video");
} }
} }
} }
@ -295,20 +323,37 @@ var ContextMenuHandler = {
let selection = targetWindow.getSelection(); let selection = targetWindow.getSelection();
if (selection && this._tapInSelection(selection, aX, aY)) { if (selection && this._tapInSelection(selection, aX, aY)) {
state.string = targetWindow.getSelection().toString(); state.string = targetWindow.getSelection().toString();
state.types.push("copy"); uniqueStateTypes.add("copy");
state.types.push("selected-text"); uniqueStateTypes.add("selected-text");
if (isEditableText) {
uniqueStateTypes.add("cut");
}
} else { } else {
// Add general content text if this isn't anything specific // Add general content text if this isn't anything specific
if (state.types.indexOf("image") == -1 && if (!(
state.types.indexOf("media") == -1 && uniqueStateTypes.has("image") ||
state.types.indexOf("video") == -1 && uniqueStateTypes.has("media") ||
state.types.indexOf("link") == -1 && uniqueStateTypes.has("video") ||
state.types.indexOf("input-text") == -1) { uniqueStateTypes.has("link") ||
state.types.push("content-text"); 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 // populate position and event source
state.xPos = offsetX + aX; state.xPos = offsetX + aX;
state.yPos = offsetY + aY; state.yPos = offsetY + aY;
@ -316,8 +361,9 @@ var ContextMenuHandler = {
for (let i = 0; i < this._types.length; i++) for (let i = 0; i < this._types.length; i++)
if (this._types[i].handler(state, popupNode)) 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; this._previousState = state;
sendAsyncMessage("Content:ContextMenu", state); sendAsyncMessage("Content:ContextMenu", state);

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

@ -109,3 +109,24 @@
<!ENTITY optionsFlyout.key "o"> <!ENTITY optionsFlyout.key "o">
<!ENTITY syncFlyout.key "s"> <!ENTITY syncFlyout.key "s">
<!ENTITY aboutFlyout.key "a"> <!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">

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

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

Двоичные данные
browser/themes/linux/devtools/command-console.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 695 B

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

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

Двоичные данные
browser/themes/osx/devtools/command-console.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 695 B

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

@ -258,6 +258,7 @@ browser.jar:
skin/classic/browser/devtools/command-responsivemode.png (devtools/command-responsivemode.png) 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-scratchpad.png (devtools/command-scratchpad.png)
skin/classic/browser/devtools/command-tilt.png (devtools/command-tilt.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/alerticon-warning.png (devtools/alerticon-warning.png)
skin/classic/browser/devtools/ruleview.css (devtools/ruleview.css) skin/classic/browser/devtools/ruleview.css (devtools/ruleview.css)
skin/classic/browser/devtools/commandline.css (devtools/commandline.css) skin/classic/browser/devtools/commandline.css (devtools/commandline.css)

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

@ -441,6 +441,23 @@
-moz-image-region: rect(0px, 48px, 16px, 32px); -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 */ /* Tabs */
.devtools-tabbar { .devtools-tabbar {

Двоичные данные
browser/themes/windows/devtools/command-console.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 695 B

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

@ -186,6 +186,7 @@ browser.jar:
skin/classic/browser/devtools/command-responsivemode.png (devtools/command-responsivemode.png) 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-scratchpad.png (devtools/command-scratchpad.png)
skin/classic/browser/devtools/command-tilt.png (devtools/command-tilt.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/markup-view.css (../shared/devtools/markup-view.css)
skin/classic/browser/devtools/editor-error.png (devtools/editor-error.png) skin/classic/browser/devtools/editor-error.png (devtools/editor-error.png)
skin/classic/browser/devtools/editor-breakpoint.png (devtools/editor-breakpoint.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-responsivemode.png (devtools/command-responsivemode.png)
skin/classic/aero/browser/devtools/command-scratchpad.png (devtools/command-scratchpad.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-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/alerticon-warning.png (devtools/alerticon-warning.png)
skin/classic/aero/browser/devtools/ruleview.css (devtools/ruleview.css) skin/classic/aero/browser/devtools/ruleview.css (devtools/ruleview.css)
skin/classic/aero/browser/devtools/commandline.css (devtools/commandline.css) skin/classic/aero/browser/devtools/commandline.css (devtools/commandline.css)

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

@ -4,7 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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 class TestConstants {
public static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@"; public static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@";

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

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

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

@ -45,7 +45,7 @@ public class AboutHomeComponent extends BaseComponent {
(expectedPageIndex >= 0) ? expectedPageIndex : Page.values().length - 1; (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()); expectedPageIndex, getHomePagerView().getCurrentItem());
return this; return this;
} }

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

@ -6631,7 +6631,27 @@ var SearchEngines = {
let filter = { let filter = {
matches: function (aElement) { 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 = { SelectionHandler.actions.SEARCH_ADD = {