зеркало из https://github.com/mozilla/gecko-dev.git
Bug 861277 - Uplift addon-sdk's integration branch
This commit is contained in:
Родитель
d2d374b265
Коммит
cb0e50182e
|
@ -14,8 +14,6 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* [Heather Arthur](https://github.com/harthur)
|
||||
* Dietrich Ayala
|
||||
|
||||
<!--end-->
|
||||
|
||||
### B ###
|
||||
|
||||
* [Romain B](https://github.com/Niamor)
|
||||
|
@ -27,18 +25,15 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* Daniel Buchner
|
||||
* James Burke
|
||||
|
||||
<!--end-->
|
||||
|
||||
### C ###
|
||||
|
||||
* [Shane Caraveo](https://github.com/mixedpuppy)
|
||||
* [Matěj Cepl](https://github.com/mcepl)
|
||||
* Marc Chevrier
|
||||
* [Timothy Guan-tin Chien](https://github.com/timdream)
|
||||
* Hernán Rodriguez Colmeiro
|
||||
* [David Creswick](https://github.com/dcrewi)
|
||||
|
||||
<!--end-->
|
||||
|
||||
### D ###
|
||||
|
||||
* dexter
|
||||
|
@ -46,15 +41,11 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* Connor Dunn
|
||||
* dynamis
|
||||
|
||||
<!--end-->
|
||||
|
||||
### F ###
|
||||
|
||||
* [Matteo Ferretti (ZER0)](https://github.com/ZER0)
|
||||
* [Matteo Ferretti](https://github.com/ZER0)
|
||||
* fuzzykiller
|
||||
|
||||
<!--end-->
|
||||
|
||||
### G ###
|
||||
|
||||
* [Marcio Galli](https://github.com/taboca)
|
||||
|
@ -65,8 +56,6 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* Jeff Griffiths
|
||||
* [David Guo](https://github.com/dglol)
|
||||
|
||||
<!--end-->
|
||||
|
||||
### H ###
|
||||
|
||||
* Mark Hammond
|
||||
|
@ -74,20 +63,14 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* Lloyd Hilaiel
|
||||
* Bobby Holley
|
||||
|
||||
<!--end-->
|
||||
|
||||
### I ###
|
||||
|
||||
* Shun Ikejima
|
||||
|
||||
<!--end-->
|
||||
|
||||
### J ###
|
||||
|
||||
* Eric H. Jung
|
||||
|
||||
<!--end-->
|
||||
|
||||
### K ###
|
||||
|
||||
* Hrishikesh Kale
|
||||
|
@ -95,15 +78,11 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* Lajos Koszti
|
||||
* [Vladimir Kukushkin](https://github.com/kukushechkin)
|
||||
|
||||
<!--end-->
|
||||
|
||||
### L ###
|
||||
|
||||
* Edward Lee
|
||||
* Gregg Lind
|
||||
|
||||
<!--end-->
|
||||
|
||||
### M ###
|
||||
|
||||
* [Nils Maier](https://github.com/nmaier)
|
||||
|
@ -113,8 +92,6 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* Zandr Milewski
|
||||
* Noelle Murata
|
||||
|
||||
<!--end-->
|
||||
|
||||
### N ###
|
||||
|
||||
* Siavash Askari Nasr
|
||||
|
@ -122,16 +99,12 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* Dương H. Nguyễn
|
||||
* Nick Nguyen
|
||||
|
||||
<!--end-->
|
||||
|
||||
### O ###
|
||||
|
||||
* [ongaeshi](https://github.com/ongaeshi)
|
||||
* Paul O’Shannessy
|
||||
* Les Orchard
|
||||
|
||||
<!--end-->
|
||||
|
||||
### P ###
|
||||
|
||||
* Robert Pankowecki
|
||||
|
@ -139,16 +112,13 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* [Alexandre Poirot](https://github.com/ochameau)
|
||||
* Nickolay Ponomarev
|
||||
|
||||
<!--end-->
|
||||
|
||||
### R ###
|
||||
|
||||
* Aza Raskin
|
||||
|
||||
<!--end-->
|
||||
|
||||
### S ###
|
||||
|
||||
* [Jordan Santell](https://github.com/jsantell)
|
||||
* Till Schneidereit
|
||||
* Justin Scott
|
||||
* Ayan Shah
|
||||
|
@ -160,8 +130,6 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* [J. Ryan Stinnett](https://github.com/jryans)
|
||||
* [Mihai Sucan](https://github.com/mihaisucan)
|
||||
|
||||
<!--end-->
|
||||
|
||||
### T ###
|
||||
|
||||
* taku0
|
||||
|
@ -171,8 +139,6 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* Dave Townsend
|
||||
* [Matthias Tylkowski](https://github.com/tylkomat)
|
||||
|
||||
<!--end-->
|
||||
|
||||
### V ###
|
||||
|
||||
* Peter Van der Beken
|
||||
|
@ -181,8 +147,6 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* [Erik Vold](https://github.com/erikvold)
|
||||
* Vladimir Vukicevic
|
||||
|
||||
<!--end-->
|
||||
|
||||
### W ###
|
||||
|
||||
* Brian Warner
|
||||
|
@ -191,8 +155,6 @@ We'd like to thank our many Jetpack project contributors! They include:
|
|||
* Blake Winton
|
||||
* Michal Wojciechowski
|
||||
|
||||
<!--end-->
|
||||
|
||||
### Z ###
|
||||
|
||||
* Piotr Zalewa
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
The `mod` module provides functions to modify a page content.
|
||||
|
||||
<api name="attachTo">
|
||||
@function
|
||||
Function applies given `modification` to a given `window`.
|
||||
|
||||
For example, the following code applies a style to a content window, adding
|
||||
a border to all divs in page:
|
||||
|
||||
var attachTo = require("sdk/content/mod").attachTo;
|
||||
var Style = require("sdk/stylesheet/style").Style;
|
||||
|
||||
var style = Style({
|
||||
source: "div { border: 4px solid gray }"
|
||||
});
|
||||
|
||||
// assuming window points to the content page we want to modify
|
||||
attachTo(style, window);
|
||||
|
||||
@param modification {object}
|
||||
The modification we want to apply to the target.
|
||||
|
||||
@param window {nsIDOMWindow}
|
||||
The window to be modified.
|
||||
</api>
|
||||
|
||||
<api name="detachFrom">
|
||||
@function
|
||||
Function removes attached `modification` from a given `window`.
|
||||
If `window` is not specified, `modification` is removed from all the windows
|
||||
it's being attached to.
|
||||
|
||||
For example, the following code applies and removes a style to a content
|
||||
window, adding a border to all divs in page:
|
||||
|
||||
var { attachTo, detachFrom } = require("sdk/content/mod");
|
||||
var Style = require("sdk/stylesheet/style").Style;
|
||||
|
||||
var style = Style({
|
||||
source: "div { border: 4px solid gray }"
|
||||
});
|
||||
|
||||
// assuming window points to the content page we want to modify
|
||||
attachTo(style, window);
|
||||
// ...
|
||||
detachFrom(style, window);
|
||||
|
||||
@param modification {object}
|
||||
The modification we want to remove from the target
|
||||
|
||||
@param window {nsIDOMWindow}
|
||||
The window to be modified.
|
||||
If `window` is not provided `modification` is removed from all targets it's
|
||||
being attached to.
|
||||
</api>
|
||||
|
||||
<api name="getTargetWindow">
|
||||
@function
|
||||
Function takes `target`, value representing content (page) and returns
|
||||
`nsIDOMWindow` for that content.
|
||||
If `target` does not represents valid content `null` is returned.
|
||||
For example target can be a content window itself in which case it's will be
|
||||
returned back.
|
||||
|
||||
@param target {object}
|
||||
The object for which we want to obtain the window represented or contained.
|
||||
If a `nsIDOMWindow` is given, it works as an identify function, returns
|
||||
`target` itself.
|
||||
@returns {nsIDOMWindow|null}
|
||||
The window represented or contained by the `target`, if any. Returns `null`
|
||||
otherwise.
|
||||
</api>
|
||||
|
||||
<api name="attach">
|
||||
@function
|
||||
Function applies given `modification` to a given `target` representing a
|
||||
content to be modified.
|
||||
|
||||
@param modification {object}
|
||||
The modification we want to apply to the target
|
||||
|
||||
@param target {object}
|
||||
Target is a value that representing content to be modified. It is valid only
|
||||
when `getTargetWindow(target)` returns nsIDOMWindow of content it represents.
|
||||
</api>
|
||||
|
||||
<api name="detach">
|
||||
@function
|
||||
Function removes attached `modification`. If `target` is specified
|
||||
`modification` is removed from that `target` only, otherwise `modification` is
|
||||
removed from all the targets it's being attached to.
|
||||
|
||||
@param modification {object}
|
||||
The modification we want to remove from the target
|
||||
|
||||
@param target {object}
|
||||
Target is a value that representing content to be modified. It is valid only
|
||||
when `getTargetWindow(target)` returns `nsIDOMWindow` of content it represents.
|
||||
If `target` is not provided `modification` is removed from all targets it's
|
||||
being attached to.
|
||||
</api>
|
|
@ -425,7 +425,7 @@ Creates a panel.
|
|||
Set to `false` to prevent taking the focus away when the panel is shown.
|
||||
Only turn this off if necessary, to prevent accessibility issue.
|
||||
Optional, default to `true`.
|
||||
@prop [contentURL] {string}
|
||||
@prop [contentURL] {string,URL}
|
||||
The URL of the content to load in the panel.
|
||||
@prop [allow] {object}
|
||||
An optional object describing permissions for the content. It should
|
||||
|
|
|
@ -48,7 +48,7 @@ interface to listen for and log all topic notifications:
|
|||
observerService.addObserver(this, this.topic, false);
|
||||
},
|
||||
unregister: function() {
|
||||
addObserver.removeObserver(this, this.topic, false);
|
||||
addObserver.removeObserver(this, this.topic);
|
||||
},
|
||||
observe: function observe(subject, topic, data) {
|
||||
console.log('star observer:', subject, topic, data);
|
||||
|
|
|
@ -62,8 +62,8 @@ This constructor creates a request object that can be used to make network
|
|||
requests. The constructor takes a single parameter `options` which is used to
|
||||
set several properties on the resulting `Request`.
|
||||
@param options {object}
|
||||
@prop url {string}
|
||||
This is the url to which the request will be made.
|
||||
@prop [url] {string,url}
|
||||
This is the url to which the request will be made. Can either be a [String](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String) or an instance of the SDK's [URL](https://addons.mozilla.org/en-US/developers/docs/sdk/latest/modules/sdk/url.html#URL).
|
||||
|
||||
@prop [onComplete] {function}
|
||||
This function will be called when the request has received a response (or in
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
Module provides `Style` function that can be used to construct content style
|
||||
modification via stylesheet files or CSS rules.
|
||||
|
||||
<api name="Style">
|
||||
@class
|
||||
<api name="Style">
|
||||
@constructor
|
||||
The Style constructor creates an object that represents style modifications
|
||||
via stylesheet file(s) or/and CSS rules. Stylesheet file URL(s) are verified
|
||||
to be local to an add-on, while CSS rules are virified to be a string or
|
||||
array of strings.
|
||||
|
||||
The style created can be applied to a content by calling `attach`,
|
||||
and removed using `detach`. Those functions are part of [content/mod](modules/sdk/content/mod.html) module.
|
||||
@param options {object}
|
||||
Options for the style. All these options are optional. Although if you
|
||||
don't supply any stylesheet or CSS rules, your style won't be very useful.
|
||||
|
||||
@prop uri {string,array}
|
||||
A string, or an array of strings, that represents local URI to stylesheet.
|
||||
@prop source {string,array}
|
||||
A string, or an array of strings, that contains CSS rules. Those rules
|
||||
are applied after the rules in the stylesheet specified with `uri` options,
|
||||
if provided.
|
||||
@prop [type="author"] {string}
|
||||
The type of the sheet. It accepts the following values: `"agent"`, `"user"`
|
||||
and `"author"`.
|
||||
If not provided, the default value is `"author"`.
|
||||
</api>
|
||||
|
||||
<api name="source">
|
||||
@property {string}
|
||||
An array of strings that contains the CSS rule(s) specified in the constructor's
|
||||
option; `null` if no `source` option was given to the constructor.
|
||||
This property is read-only.
|
||||
</api>
|
||||
<api name="uri">
|
||||
@property {string}
|
||||
An array of strings that contains the stylesheet local URI(s) specified in the
|
||||
constructor's option; `null` if no `uri` option was given to the
|
||||
constructor.
|
||||
This property is read-only.
|
||||
</api>
|
||||
<api name="type">
|
||||
@property {string}
|
||||
The type of the sheet. If no type is provided in constructor's option,
|
||||
it returns the default value, `"author"`. This property is read-only.
|
||||
</api>
|
||||
</api>
|
|
@ -0,0 +1,41 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
Module provides helper functions for working with stylesheets.
|
||||
|
||||
<api name="loadSheet">
|
||||
@function
|
||||
Synchronously loads a style sheet from `uri` and adds it to the list of
|
||||
additional style sheets of the document.
|
||||
The sheets added takes effect immediately, and only on the document of the
|
||||
`window` given.
|
||||
@param window {nsIDOMWindow}
|
||||
@param uri {string, nsIURI}
|
||||
@param [type="author"] {string}
|
||||
The type of the sheet. It accepts the following values: `"agent"`, `"user"`
|
||||
and `"author"`.
|
||||
If not provided, the default value is `"author"`.
|
||||
</api>
|
||||
|
||||
<api name="removeSheet">
|
||||
@function
|
||||
Remove the document style sheet at `sheetURI` from the list of additional
|
||||
style sheets of the document. The removal takes effect immediately.
|
||||
@param window {nsIDOMWindow}
|
||||
@param uri {string, nsIURI}
|
||||
@param [type="author"] {string}
|
||||
The type of the sheet. It accepts the following values: `"agent"`, `"user"`
|
||||
and `"author"`.
|
||||
If not provided, the default value is `"author"`.
|
||||
</api>
|
||||
|
||||
<api name="isTypeValid">
|
||||
@function
|
||||
Verifies that the `type` given is a valid stylesheet's type.
|
||||
The values considered valid are: `"agent"`, `"user"` and `"author"`.
|
||||
@param type {string}
|
||||
The type of the sheet.
|
||||
@returns {boolean}
|
||||
`true` if the `type` given is valid, otherwise `false`.
|
||||
</api>
|
|
@ -83,6 +83,17 @@ The `url` module provides functionality for the parsing and retrieving of URLs.
|
|||
The converted URL as a string.
|
||||
</api>
|
||||
|
||||
<api name="isValidURI">
|
||||
@function
|
||||
Checks the validity of a URI. `isValidURI("http://mozilla.org")` would return `true`, whereas `isValidURI("mozilla.org")` would return `false`.
|
||||
|
||||
@param uri {string}
|
||||
The URI, as a string, to be tested.
|
||||
|
||||
@returns {boolean}
|
||||
A boolean indicating whether or not the URI is valid.
|
||||
</api>
|
||||
|
||||
<api name="DataURL">
|
||||
@class
|
||||
<api name="DataURL">
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
The `util/array` module provides simple helper functions for working with
|
||||
arrays.
|
||||
|
||||
<api name="has">
|
||||
@function
|
||||
Returns `true` if the given [`Array`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array) contains the element or `false` otherwise.
|
||||
A simplified version of `array.indexOf(element) >= 0`.
|
||||
|
||||
let { has } = require('sdk/util/array');
|
||||
let a = ['rock', 'roll', 100];
|
||||
|
||||
has(a, 'rock'); // true
|
||||
has(a, 'rush'); // false
|
||||
has(a, 100); // true
|
||||
|
||||
@param array {array}
|
||||
The array to search.
|
||||
|
||||
@param element {*}
|
||||
The element to search for in the array.
|
||||
|
||||
@returns {boolean}
|
||||
A boolean indicating whether or not the element was found in the array.
|
||||
</api>
|
||||
|
||||
<api name="hasAny">
|
||||
@function
|
||||
Returns `true` if the given [`Array`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array) contains any of the elements in the
|
||||
`elements` array, or `false` otherwise.
|
||||
|
||||
let { hasAny } = require('sdk/util/array');
|
||||
let a = ['rock', 'roll', 100];
|
||||
|
||||
hasAny(a, ['rock', 'bright', 'light']); // true
|
||||
hasAny(a, ['rush', 'coil', 'jet']); // false
|
||||
|
||||
@param array {array}
|
||||
The array to search for elements.
|
||||
|
||||
@param elements {array}
|
||||
An array of elements to search for in `array`.
|
||||
|
||||
@returns {boolean}
|
||||
A boolean indicating whether or not any of the elements were found
|
||||
in the array.
|
||||
</api>
|
||||
|
||||
<api name="add">
|
||||
@function
|
||||
If the given [array](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array) does not already contain the given element, this function
|
||||
adds the element to the array and returns `true`. Otherwise, this function
|
||||
does not add the element and returns `false`.
|
||||
|
||||
let { add } = require('sdk/util/array');
|
||||
let a = ['alice', 'bob', 'carol'];
|
||||
|
||||
add(a, 'dave'); // true
|
||||
add(a, 'dave'); // false
|
||||
add(a, 'alice'); // false
|
||||
|
||||
console.log(a); // ['alice', 'bob', 'carol', 'dave']
|
||||
|
||||
@param array {array}
|
||||
The array to add the element to.
|
||||
|
||||
@param element {*}
|
||||
The element to add
|
||||
|
||||
@returns {boolean}
|
||||
A boolean indicating whether or not the element was added to the array.
|
||||
</api>
|
||||
|
||||
<api name="remove">
|
||||
@function
|
||||
If the given [array](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array) contains the given element, this function
|
||||
removes the element from the array and returns `true`. Otherwise, this function
|
||||
does not alter the array and returns `false`.
|
||||
|
||||
let { remove } = require('sdk/util/array');
|
||||
let a = ['alice', 'bob', 'carol'];
|
||||
|
||||
remove(a, 'dave'); // false
|
||||
remove(a, 'bob'); // true
|
||||
remove(a, 'bob'); // false
|
||||
|
||||
console.log(a); // ['alice', 'carol']
|
||||
|
||||
@param array {array}
|
||||
The array to remove the element from.
|
||||
|
||||
@param element {*}
|
||||
The element to remove from the array if it contains it.
|
||||
|
||||
@returns {boolean}
|
||||
A boolean indicating whether or not the element was removed from the array.
|
||||
</api>
|
||||
|
||||
<api name="unique">
|
||||
@function
|
||||
Produces a duplicate-free version of the given [array](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array).
|
||||
|
||||
let { unique } = require('sdk/util/array');
|
||||
let a = [1, 1, 1, 1, 3, 4, 7, 7, 10, 10, 10, 10];
|
||||
let b = unique(a);
|
||||
|
||||
console.log(a); // [1, 1, 1, 1, 3, 4, 7, 7, 10, 10, 10, 10];
|
||||
console.log(b); // [1, 3, 4, 7, 10];
|
||||
|
||||
@param array {array}
|
||||
Source array.
|
||||
|
||||
@returns {array}
|
||||
The new, duplicate-free array.
|
||||
</api>
|
||||
|
||||
<api name="flatten">
|
||||
@function
|
||||
Flattens a nested [array](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array) of any depth.
|
||||
|
||||
let { flatten } = require('sdk/util/array');
|
||||
let a = ['cut', ['ice', ['fire']], 'elec'];
|
||||
let b = flatten(a);
|
||||
|
||||
console.log(a); // ['cut', ['ice', ['fire']], 'elec']
|
||||
console.log(b); // ['cut', 'ice', 'fire', 'elec'];
|
||||
|
||||
@param array {array}
|
||||
The array to flatten.
|
||||
|
||||
@returns {array}
|
||||
The new, flattened array.
|
||||
</api>
|
||||
|
||||
<api name="fromIterator">
|
||||
@function
|
||||
Iterates over an [iterator](https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Iterators_and_Generators#Iterators) and returns the results as an [array](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array).
|
||||
|
||||
let { fromIterator } = require('sdk/util/array');
|
||||
let i = new Set();
|
||||
i.add('otoro');
|
||||
i.add('unagi');
|
||||
i.add('keon');
|
||||
|
||||
fromIterator(i) // ['otoro', 'unagi', 'keon']
|
||||
|
||||
@param iterator {iterator}
|
||||
The [`Iterator`](https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Iterators_and_Generators#Iterators) object over which to iterate and place results into an array.
|
||||
|
||||
@returns {array}
|
||||
The iterator's results in an array.
|
||||
</api>
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
The `util/object` module provides simple helper functions for working with
|
||||
objects.
|
||||
|
||||
<api name="merge">
|
||||
@function
|
||||
Merges all of the properties of all arguments into the first argument. If
|
||||
two or more argument objects have properties with the same name, the
|
||||
property is overwritten with precedence from right to left, implying that
|
||||
properties of the object on the left are overwritten by a same named property
|
||||
of an object on the right. Properties are merged with [descriptors](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty#Description) onto the source object.
|
||||
|
||||
Any argument given with "falsy" values (null, undefined) in case of objects
|
||||
are skipped.
|
||||
|
||||
let { merge } = require("sdk/util/object");
|
||||
var a = { jetpacks: "are yes", foo: 10 }
|
||||
var b = merge(a, { foo: 5, bar: 6 }, { foo: 50, location: "SF" });
|
||||
|
||||
b === a // true
|
||||
b.jetpacks // "are yes"
|
||||
b.foo // 50
|
||||
b.bar // 6
|
||||
b.location // "SF"
|
||||
|
||||
// Merge also translates property descriptors
|
||||
var c = { "type": "addon" };
|
||||
var d = {};
|
||||
Object.defineProperty(d, "name", {
|
||||
value: "jetpacks",
|
||||
configurable: false
|
||||
});
|
||||
merge(c, d);
|
||||
var props = Object.getOwnPropertyDescriptor(c, "name");
|
||||
console.log(props.configurable); // true
|
||||
|
||||
@param source {object}
|
||||
The object that other properties are merged into.
|
||||
|
||||
@param arguments {object}
|
||||
`n` amount of additional objects that are merged into `source` object.
|
||||
|
||||
@returns {object}
|
||||
The `source` object.
|
||||
</api>
|
||||
|
||||
<api name="extend">
|
||||
@function
|
||||
Returns an object that inherits from the first argument and contains
|
||||
all of the properties from all following arguments, with precedence from
|
||||
right to left.
|
||||
|
||||
`extend(source1, source2, source3)` is the equivalent of
|
||||
`merge(Object.create(source1), source2, source3)`.
|
||||
|
||||
let { extend } = require("sdk/util/object");
|
||||
var a = { alpha: "a" };
|
||||
var b = { beta: "b" };
|
||||
var g = { gamma: "g", alpha: null };
|
||||
var x = extend(a, b, g);
|
||||
|
||||
console.log(a); // { alpha: "a" }
|
||||
console.log(b); // { beta: "b" }
|
||||
console.log(g); // { gamma: "g", alpha: null }
|
||||
console.log(x); // { alpha: null, beta: "b", gamma: "g" }
|
||||
|
||||
@param arguments {object}
|
||||
`n` arguments that get merged into a new object.
|
||||
|
||||
@returns {object}
|
||||
The new, merged object.
|
||||
</api>
|
До Ширина: | Высота: | Размер: 386 B После Ширина: | Высота: | Размер: 386 B |
|
@ -54,4 +54,7 @@ exports.ready = promise;
|
|||
exports.window = window;
|
||||
|
||||
// Still close window on unload to claim memory back early.
|
||||
unload(function() { window.close() });
|
||||
unload(function() {
|
||||
window.close()
|
||||
frame.parentNode.removeChild(frame);
|
||||
});
|
||||
|
|
|
@ -12,27 +12,24 @@ module.metadata = {
|
|||
|
||||
const { EventEmitter } = require('../deprecated/events');
|
||||
const { validateOptions } = require('../deprecated/api-utils');
|
||||
const { URL } = require('../url');
|
||||
const { isValidURI, URL } = require('../url');
|
||||
const file = require('../io/file');
|
||||
const { contract } = require('../util/contract');
|
||||
|
||||
const LOCAL_URI_SCHEMES = ['resource', 'data'];
|
||||
|
||||
// Returns `null` if `value` is `null` or `undefined`, otherwise `value`.
|
||||
function ensureNull(value) {
|
||||
return value == null ? null : value;
|
||||
}
|
||||
function ensureNull(value) value == null ? null : value
|
||||
|
||||
// map of property validations
|
||||
const valid = {
|
||||
contentURL: {
|
||||
ok: function (value) {
|
||||
try {
|
||||
URL(value);
|
||||
}
|
||||
catch(e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
map: function(url) !url ? ensureNull(url) : url.toString(),
|
||||
is: ['undefined', 'null', 'string'],
|
||||
ok: function (url) {
|
||||
if (url === null)
|
||||
return true;
|
||||
return isValidURI(url);
|
||||
},
|
||||
msg: 'The `contentURL` option must be a valid URL.'
|
||||
},
|
||||
|
@ -202,3 +199,5 @@ const Loader = EventEmitter.compose({
|
|||
_contentScript: null
|
||||
});
|
||||
exports.Loader = Loader;
|
||||
|
||||
exports.contract = contract(valid);
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
const { Ci } = require("chrome");
|
||||
const method = require("method/core");
|
||||
const { add, remove, iterator } = require("../lang/weak-set");
|
||||
|
||||
let getTargetWindow = method("getTargetWindow");
|
||||
|
||||
getTargetWindow.define(function (target) {
|
||||
if (target instanceof Ci.nsIDOMWindow)
|
||||
return target;
|
||||
if (target instanceof Ci.nsIDOMDocument)
|
||||
return target.defaultView || null;
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
exports.getTargetWindow = getTargetWindow;
|
||||
|
||||
let attachTo = method("attachTo");
|
||||
exports.attachTo = attachTo;
|
||||
|
||||
let detachFrom = method("detatchFrom");
|
||||
exports.detachFrom = detachFrom;
|
||||
|
||||
function attach(modification, target) {
|
||||
let window = getTargetWindow(target);
|
||||
|
||||
attachTo(modification, window);
|
||||
|
||||
// modification are stored per content; `window` reference can still be the
|
||||
// same even if the content is changed, therefore `document` is used instead.
|
||||
add(modification, window.document);
|
||||
}
|
||||
exports.attach = attach;
|
||||
|
||||
function detach(modification, target) {
|
||||
if (target) {
|
||||
let window = getTargetWindow(target);
|
||||
detachFrom(modification, window);
|
||||
remove(modification, window.document);
|
||||
}
|
||||
else {
|
||||
let documents = iterator(modification);
|
||||
for (let document of documents) {
|
||||
detachFrom(modification, document.defaultView);
|
||||
remove(modification, document);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.detach = detach;
|
|
@ -463,48 +463,55 @@ const Worker = EventEmitter.compose({
|
|||
constructor: function Worker(options) {
|
||||
options = options || {};
|
||||
|
||||
if ('window' in options)
|
||||
this._window = options.window;
|
||||
if ('contentScriptFile' in options)
|
||||
this.contentScriptFile = options.contentScriptFile;
|
||||
if ('contentScriptOptions' in options)
|
||||
this.contentScriptOptions = options.contentScriptOptions;
|
||||
if ('contentScript' in options)
|
||||
this.contentScript = options.contentScript;
|
||||
if ('onError' in options)
|
||||
this.on('error', options.onError);
|
||||
if ('onMessage' in options)
|
||||
this.on('message', options.onMessage);
|
||||
if ('onDetach' in options)
|
||||
this.on('detach', options.onDetach);
|
||||
|
||||
this._setListeners(options);
|
||||
|
||||
// Internal feature that is only used by SDK unit tests.
|
||||
// See `PRIVATE_KEY` definition for more information.
|
||||
if ('exposeUnlockKey' in options && options.exposeUnlockKey === PRIVATE_KEY)
|
||||
this._expose_key = true;
|
||||
|
||||
unload.ensure(this._public, "destroy");
|
||||
|
||||
// Ensure that worker._port is initialized for contentWorker to be able
|
||||
// to send events during worker initialization.
|
||||
this.port;
|
||||
|
||||
this._documentUnload = this._documentUnload.bind(this);
|
||||
this._pageShow = this._pageShow.bind(this);
|
||||
this._pageHide = this._pageHide.bind(this);
|
||||
|
||||
if ("window" in options) this._attach(options.window);
|
||||
},
|
||||
|
||||
_setListeners: function(options) {
|
||||
if ('onError' in options)
|
||||
this.on('error', options.onError);
|
||||
if ('onMessage' in options)
|
||||
this.on('message', options.onMessage);
|
||||
if ('onDetach' in options)
|
||||
this.on('detach', options.onDetach);
|
||||
},
|
||||
|
||||
_attach: function(window) {
|
||||
this._window = window;
|
||||
// Track document unload to destroy this worker.
|
||||
// We can't watch for unload event on page's window object as it
|
||||
// prevents bfcache from working:
|
||||
// https://developer.mozilla.org/En/Working_with_BFCache
|
||||
this._windowID = getInnerId(this._window);
|
||||
observers.add("inner-window-destroyed",
|
||||
this._documentUnload = this._documentUnload.bind(this));
|
||||
observers.add("inner-window-destroyed", this._documentUnload);
|
||||
|
||||
// Listen to pagehide event in order to freeze the content script
|
||||
// while the document is frozen in bfcache:
|
||||
this._window.addEventListener("pageshow",
|
||||
this._pageShow = this._pageShow.bind(this),
|
||||
true);
|
||||
this._window.addEventListener("pagehide",
|
||||
this._pageHide = this._pageHide.bind(this),
|
||||
true);
|
||||
|
||||
unload.ensure(this._public, "destroy");
|
||||
|
||||
// Ensure that worker._port is initialized for contentWorker to be able
|
||||
// to send use event during WorkerSandbox(this)
|
||||
this.port;
|
||||
this._window.addEventListener("pageshow", this._pageShow, true);
|
||||
this._window.addEventListener("pagehide", this._pageHide, true);
|
||||
|
||||
// will set this._contentWorker pointing to the private API:
|
||||
this._contentWorker = WorkerSandbox(this);
|
||||
|
|
|
@ -13,33 +13,49 @@ let { Class } = require("./heritage");
|
|||
let { on, off } = require('../system/events');
|
||||
let unloadSubject = require('@loader/unload');
|
||||
|
||||
function DisposeHandler(disposable) {
|
||||
return function onDisposal({subject}) {
|
||||
if (subject.wrappedJSObject === unloadSubject) {
|
||||
off("sdk:loader:destroy", onDisposal);
|
||||
disposable.destroy();
|
||||
let disposables = WeakMap();
|
||||
|
||||
function initialize(instance) {
|
||||
// Create an event handler that will dispose instance on unload.
|
||||
function handler(event) {
|
||||
if (event.subject.wrappedJSObject === unloadSubject) {
|
||||
dispose(instance);
|
||||
instance.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Form weak reference between disposable instance and an unload event
|
||||
// handler associated with it. This will make sure that event handler can't
|
||||
// be garbage collected as long as instance is referenced. Also note that
|
||||
// system events intentionally hold weak reference to an event handler, this
|
||||
// will let GC claim both instance and an unload handler before actual add-on
|
||||
// unload if instance contains no other references.
|
||||
disposables.set(instance, handler);
|
||||
on("sdk:loader:destroy", handler);
|
||||
}
|
||||
exports.initialize = initialize;
|
||||
|
||||
function dispose(instance) {
|
||||
// Disposes given instance by removing it from weak map so that handler can
|
||||
// be GC-ed even if references to instance are kept. Also unregister unload
|
||||
// handler.
|
||||
|
||||
let handler = disposables.get(instance);
|
||||
if (handler) off("sdk:loader:destroy", handler);
|
||||
disposables.delete(instance);
|
||||
}
|
||||
exports.dispose = dispose;
|
||||
|
||||
// Base type that takes care of disposing it's instances on add-on unload.
|
||||
// Also makes sure to remove unload listener if it's already being disposed.
|
||||
let Disposable = Class({
|
||||
initialize: function dispose() {
|
||||
this.setupDisposal();
|
||||
initialize: function setupDisposable() {
|
||||
// First setup instance before initializing it's disposal. If instance
|
||||
// fails to initialize then there is no instance to be disposed at the
|
||||
// unload.
|
||||
this.setup.apply(this, arguments);
|
||||
initialize(this);
|
||||
},
|
||||
setupDisposal: function setupDisposal() {
|
||||
// Create `onDisposal` handler that will be invoked on unload of
|
||||
// the add-on, unless this is destroyed earlier.
|
||||
Object.defineProperty(this, "onDisposal", { value: DisposeHandler(this) });
|
||||
on("sdk:loader:destroy", this.onDisposal);
|
||||
},
|
||||
teardownDisposable: function tearDisposal() {
|
||||
// Removes `onDisposal` handler so that it won't be invoked on unload.
|
||||
off("sdk:loader:destroy", this.onDisposal);
|
||||
},
|
||||
|
||||
setup: function setup() {
|
||||
// Implement your initialize logic here.
|
||||
},
|
||||
|
@ -50,9 +66,8 @@ let Disposable = Class({
|
|||
destroy: function destroy() {
|
||||
// Destroying disposable removes unload handler so that attempt to dispose
|
||||
// won't be made at unload & delegates to dispose.
|
||||
this.teardownDisposable();
|
||||
dispose(this);
|
||||
this.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
exports.Disposable = Disposable;
|
||||
|
|
|
@ -17,6 +17,8 @@ const { ns } = require('../core/namespace');
|
|||
|
||||
const event = ns();
|
||||
|
||||
const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/;
|
||||
|
||||
// Utility function to access given event `target` object's event listeners for
|
||||
// the specific event `type`. If listeners for this type does not exists they
|
||||
// will be created.
|
||||
|
@ -158,3 +160,26 @@ function count(target, type) {
|
|||
return observers(target, type).length;
|
||||
}
|
||||
exports.count = count;
|
||||
|
||||
/**
|
||||
* Registers listeners on the given event `target` from the given `listeners`
|
||||
* dictionary. Iterates over the listeners and if property name matches name
|
||||
* pattern `onEventType` and property is a function, then registers it as
|
||||
* an `eventType` listener on `target`.
|
||||
*
|
||||
* @param {Object} target
|
||||
* The type of event.
|
||||
* @param {Object} listeners
|
||||
* Dictionary of listeners.
|
||||
*/
|
||||
function setListeners(target, listeners) {
|
||||
Object.keys(listeners || {}).forEach(function onEach(key) {
|
||||
let match = EVENT_TYPE_PATTERN.exec(key);
|
||||
let type = match && match[1].toLowerCase();
|
||||
let listener = listeners[key];
|
||||
|
||||
if (type && typeof(listener) === 'function')
|
||||
on(target, type, listener);
|
||||
});
|
||||
}
|
||||
exports.setListeners = setListeners;
|
||||
|
|
|
@ -10,35 +10,28 @@ module.metadata = {
|
|||
"stability": "stable"
|
||||
};
|
||||
|
||||
const { on, once, off } = require('./core');
|
||||
const { on, once, off, setListeners } = require('./core');
|
||||
const { method } = require('../lang/functional');
|
||||
const { Class } = require('../core/heritage');
|
||||
|
||||
const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/;
|
||||
|
||||
/**
|
||||
* `EventTarget` is an exemplar for creating an objects that can be used to
|
||||
* add / remove event listeners on them. Events on these objects may be emitted
|
||||
* via `emit` function exported by 'event/core' module.
|
||||
*/
|
||||
const EventTarget = Class({
|
||||
/**
|
||||
* Method initializes `this` event source. It goes through properties of a
|
||||
* given `options` and registers listeners for the ones that look like an
|
||||
* event listeners.
|
||||
*/
|
||||
/**
|
||||
* Method initializes `this` event source. It goes through properties of a
|
||||
* given `options` and registers listeners for the ones that look like an
|
||||
* event listeners.
|
||||
*/
|
||||
initialize: function initialize(options) {
|
||||
options = options || {};
|
||||
// Go through each property and registers event listeners for those
|
||||
// that have a name matching following pattern (`onEventType`).
|
||||
Object.keys(options).forEach(function onEach(key) {
|
||||
let match = EVENT_TYPE_PATTERN.exec(key);
|
||||
let type = match && match[1].toLowerCase();
|
||||
let listener = options[key];
|
||||
|
||||
if (type && typeof(listener) === 'function')
|
||||
this.on(type, listener);
|
||||
}, this);
|
||||
setListeners(this, options);
|
||||
},
|
||||
/**
|
||||
* Registers an event `listener` that is called every time events of
|
||||
|
|
|
@ -99,3 +99,6 @@ exports.merge = merge;
|
|||
|
||||
function expand(f, inputs) merge(map(f, inputs))
|
||||
exports.expand = expand;
|
||||
|
||||
function pipe(from, to) on(from, "*", emit.bind(emit, to))
|
||||
exports.pipe = pipe;
|
||||
|
|
|
@ -40,12 +40,16 @@ exports.getDocShell = getDocShell;
|
|||
* @params {Boolean} options.allowPlugins
|
||||
* Whether to allow plugin execution. Defaults to `false`.
|
||||
*/
|
||||
function create(document, options) {
|
||||
function create(target, options) {
|
||||
target = target instanceof Ci.nsIDOMDocument ? target.documentElement :
|
||||
target instanceof Ci.nsIDOMWindow ? target.document.documentElement :
|
||||
target;
|
||||
options = options || {};
|
||||
let remote = options.remote || false;
|
||||
let namespaceURI = options.namespaceURI || XUL;
|
||||
let isXUL = namespaceURI === XUL;
|
||||
let nodeName = isXUL && options.browser ? 'browser' : 'iframe';
|
||||
let document = target.ownerDocument;
|
||||
|
||||
let frame = document.createElementNS(namespaceURI, nodeName);
|
||||
// Type="content" is mandatory to enable stuff here:
|
||||
|
@ -53,7 +57,7 @@ function create(document, options) {
|
|||
frame.setAttribute('type', options.type || 'content');
|
||||
frame.setAttribute('src', options.uri || 'about:blank');
|
||||
|
||||
document.documentElement.appendChild(frame);
|
||||
target.appendChild(frame);
|
||||
|
||||
// Load in separate process if `options.remote` is `true`.
|
||||
// http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1347
|
||||
|
@ -79,8 +83,18 @@ function create(document, options) {
|
|||
docShell.allowAuth = options.allowAuth || false;
|
||||
docShell.allowJavascript = options.allowJavascript || false;
|
||||
docShell.allowPlugins = options.allowPlugins || false;
|
||||
|
||||
// Control whether the document can move/resize the window. Requires
|
||||
// recently added platform capability, so we test to avoid exceptions
|
||||
// in cases where capability is not present yet.
|
||||
if ("allowWindowControl" in docShell && "allowWindowControl" in options)
|
||||
docShell.allowWindowControl = !!options.allowWindowControl;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
exports.create = create;
|
||||
|
||||
function swapFrameLoaders(from, to)
|
||||
from.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(to)
|
||||
exports.swapFrameLoaders = swapFrameLoaders;
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
"use strict";
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
|
||||
function makeGetterFor(Type) {
|
||||
let cache = new WeakMap();
|
||||
|
||||
return function getFor(target) {
|
||||
if (!cache.has(target))
|
||||
cache.set(target, new Type());
|
||||
|
||||
return cache.get(target);
|
||||
}
|
||||
}
|
||||
|
||||
let getLookupFor = makeGetterFor(WeakMap);
|
||||
let getRefsFor = makeGetterFor(Set);
|
||||
|
||||
function add(target, value) {
|
||||
if (has(target, value))
|
||||
return;
|
||||
|
||||
getLookupFor(target).set(value, true);
|
||||
getRefsFor(target).add(Cu.getWeakReference(value));
|
||||
}
|
||||
exports.add = add;
|
||||
|
||||
function remove(target, value) {
|
||||
getLookupFor(target).delete(value);
|
||||
}
|
||||
exports.remove = remove;
|
||||
|
||||
function has(target, value) {
|
||||
return getLookupFor(target).has(value);
|
||||
}
|
||||
exports.has = has;
|
||||
|
||||
function clear(target) {
|
||||
getLookupFor(target).clear();
|
||||
getRefsFor(target).clear();
|
||||
}
|
||||
exports.clear = clear;
|
||||
|
||||
function iterator(target) {
|
||||
let refs = getRefsFor(target);
|
||||
|
||||
for (let ref of refs) {
|
||||
let value = ref.get();
|
||||
|
||||
if (has(target, value))
|
||||
yield value;
|
||||
else
|
||||
refs.delete(ref);
|
||||
}
|
||||
}
|
||||
exports.iterator = iterator;
|
|
@ -26,14 +26,8 @@ const { getTabs, getTabContentWindow, getTabForContentWindow,
|
|||
getURI: getTabURI } = require('./tabs/utils');
|
||||
const { has, hasAny } = require('./util/array');
|
||||
const { ignoreWindow } = require('sdk/private-browsing/utils');
|
||||
|
||||
const styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"].
|
||||
getService(Ci.nsIStyleSheetService);
|
||||
|
||||
const USER_SHEET = styleSheetService.USER_SHEET;
|
||||
|
||||
const io = Cc['@mozilla.org/network/io-service;1'].
|
||||
getService(Ci.nsIIOService);
|
||||
const { Style } = require("./stylesheet/style");
|
||||
const { attach, detach } = require("./content/mod");
|
||||
|
||||
// Valid values for `attachTo` option
|
||||
const VALID_ATTACHTO_OPTIONS = ['existing', 'top', 'frame'];
|
||||
|
@ -135,29 +129,16 @@ const PageMod = Loader.compose(EventEmitter, {
|
|||
else
|
||||
rules.add(include);
|
||||
|
||||
let styleRules = "";
|
||||
|
||||
if (contentStyleFile)
|
||||
styleRules = [].concat(contentStyleFile).map(readURISync).join("");
|
||||
|
||||
if (contentStyle)
|
||||
styleRules += [].concat(contentStyle).join("");
|
||||
|
||||
if (styleRules) {
|
||||
this._onRuleUpdate = this._onRuleUpdate.bind(this);
|
||||
|
||||
this._styleRules = styleRules;
|
||||
|
||||
this._registerStyleSheet();
|
||||
rules.on('add', this._onRuleUpdate);
|
||||
rules.on('remove', this._onRuleUpdate);
|
||||
if (contentStyle || contentStyleFile) {
|
||||
this._style = Style({
|
||||
uri: contentStyleFile,
|
||||
source: contentStyle
|
||||
});
|
||||
}
|
||||
|
||||
this.on('error', this._onUncaughtError = this._onUncaughtError.bind(this));
|
||||
pageModManager.add(this._public);
|
||||
|
||||
this._loadingWindows = [];
|
||||
|
||||
// `_applyOnExistingDocuments` has to be called after `pageModManager.add()`
|
||||
// otherwise its calls to `_onContent` method won't do anything.
|
||||
if ('attachTo' in options && has(options.attachTo, 'existing'))
|
||||
|
@ -166,20 +147,14 @@ const PageMod = Loader.compose(EventEmitter, {
|
|||
|
||||
destroy: function destroy() {
|
||||
|
||||
this._unregisterStyleSheet();
|
||||
|
||||
this.include.removeListener('add', this._onRuleUpdate);
|
||||
this.include.removeListener('remove', this._onRuleUpdate);
|
||||
if (this._style)
|
||||
detach(this._style);
|
||||
|
||||
for each (let rule in this.include)
|
||||
this.include.remove(rule);
|
||||
pageModManager.remove(this._public);
|
||||
this._loadingWindows = [];
|
||||
|
||||
},
|
||||
|
||||
_loadingWindows: [],
|
||||
|
||||
_applyOnExistingDocuments: function _applyOnExistingDocuments() {
|
||||
let mod = this;
|
||||
// Returns true if the tab match one rule
|
||||
|
@ -216,6 +191,9 @@ const PageMod = Loader.compose(EventEmitter, {
|
|||
if (!isTopDocument && !has(this.attachTo, "frame"))
|
||||
return;
|
||||
|
||||
if (this._style)
|
||||
attach(this._style, window);
|
||||
|
||||
// Immediatly evaluate content script if the document state is already
|
||||
// matching contentScriptWhen expectations
|
||||
let state = window.document.readyState;
|
||||
|
@ -261,59 +239,6 @@ const PageMod = Loader.compose(EventEmitter, {
|
|||
_onUncaughtError: function _onUncaughtError(e) {
|
||||
if (this._listeners('error').length == 1)
|
||||
console.exception(e);
|
||||
},
|
||||
_onRuleUpdate: function _onRuleUpdate(){
|
||||
this._registerStyleSheet();
|
||||
},
|
||||
|
||||
_registerStyleSheet : function _registerStyleSheet() {
|
||||
let rules = this.include;
|
||||
let styleRules = this._styleRules;
|
||||
|
||||
let documentRules = [];
|
||||
|
||||
this._unregisterStyleSheet();
|
||||
|
||||
for each (let rule in rules) {
|
||||
let pattern = RULES[rule];
|
||||
|
||||
if (!pattern)
|
||||
continue;
|
||||
|
||||
if (pattern.regexp)
|
||||
documentRules.push("regexp(\"" + pattern.regexp.source + "\")");
|
||||
else if (pattern.exactURL)
|
||||
documentRules.push("url(" + pattern.exactURL + ")");
|
||||
else if (pattern.domain)
|
||||
documentRules.push("domain(" + pattern.domain + ")");
|
||||
else if (pattern.urlPrefix)
|
||||
documentRules.push("url-prefix(" + pattern.urlPrefix + ")");
|
||||
else if (pattern.anyWebPage)
|
||||
documentRules.push("regexp(\"^(https?|ftp)://.*?\")");
|
||||
}
|
||||
|
||||
let uri = "data:text/css;charset=utf-8,";
|
||||
if (documentRules.length > 0)
|
||||
uri += encodeURIComponent("@-moz-document " +
|
||||
documentRules.join(",") + " {" + styleRules + "}");
|
||||
else
|
||||
uri += encodeURIComponent(styleRules);
|
||||
|
||||
this._registeredStyleURI = io.newURI(uri, null, null);
|
||||
|
||||
styleSheetService.loadAndRegisterSheet(
|
||||
this._registeredStyleURI,
|
||||
USER_SHEET
|
||||
);
|
||||
},
|
||||
|
||||
_unregisterStyleSheet : function () {
|
||||
let uri = this._registeredStyleURI;
|
||||
|
||||
if (uri && styleSheetService.sheetRegistered(uri, USER_SHEET))
|
||||
styleSheetService.unregisterSheet(uri, USER_SHEET);
|
||||
|
||||
this._registeredStyleURI = null;
|
||||
}
|
||||
});
|
||||
exports.PageMod = function(options) PageMod(options)
|
||||
|
|
|
@ -96,8 +96,15 @@ MatchPattern.prototype = {
|
|||
return true;
|
||||
if (this.exactURL && this.exactURL == urlStr)
|
||||
return true;
|
||||
|
||||
// Tests the urlStr against domain and check if
|
||||
// wildcard submitted (*.domain.com), it only allows
|
||||
// subdomains (sub.domain.com) or from the root (http://domain.com)
|
||||
// and reject non-matching domains (otherdomain.com)
|
||||
// bug 856913
|
||||
if (this.domain && url.host &&
|
||||
url.host.slice(-this.domain.length) == this.domain)
|
||||
(url.host === this.domain ||
|
||||
url.host.slice(-this.domain.length - 1) === "." + this.domain))
|
||||
return true;
|
||||
if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
|
||||
return true;
|
||||
|
|
|
@ -14,405 +14,238 @@ module.metadata = {
|
|||
|
||||
const { Ci } = require("chrome");
|
||||
const { validateOptions: valid } = require('./deprecated/api-utils');
|
||||
const { Symbiont } = require('./content/content');
|
||||
const { EventEmitter } = require('./deprecated/events');
|
||||
const { setTimeout } = require('./timers');
|
||||
const { on, off, emit } = require('./system/events');
|
||||
const runtime = require('./system/runtime');
|
||||
const { getDocShell } = require("./frame/utils");
|
||||
const { getWindow } = require('./panel/window');
|
||||
const { isPrivateBrowsingSupported } = require('./self');
|
||||
const { isWindowPBSupported } = require('./private-browsing/utils');
|
||||
const { getNodeView } = require('./view/core');
|
||||
const { Class } = require("./core/heritage");
|
||||
const { merge } = require("./util/object");
|
||||
const { WorkerHost, Worker, detach, attach } = require("./worker/utils");
|
||||
const { Disposable } = require("./core/disposable");
|
||||
const { contract: loaderContract } = require("./content/loader");
|
||||
const { contract } = require("./util/contract");
|
||||
const { on, off, emit, setListeners } = require("./event/core");
|
||||
const { EventTarget } = require("./event/target");
|
||||
const domPanel = require("./panel/utils");
|
||||
const { events } = require("./panel/events");
|
||||
const systemEvents = require("./system/events");
|
||||
const { filter, pipe } = require("./event/utils");
|
||||
const { getNodeView } = require("./view/core");
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
||||
ON_SHOW = 'popupshown',
|
||||
ON_HIDE = 'popuphidden',
|
||||
validNumber = { is: ['number', 'undefined', 'null'] },
|
||||
validBoolean = { is: ['boolean', 'undefined', 'null'] },
|
||||
ADDON_ID = require('./self').id;
|
||||
|
||||
if (isPrivateBrowsingSupported && isWindowPBSupported) {
|
||||
if (isPrivateBrowsingSupported && isWindowPBSupported)
|
||||
throw Error('The panel module cannot be used with per-window private browsing at the moment, see Bug 816257');
|
||||
|
||||
let isArray = Array.isArray;
|
||||
let assetsURI = require("./self").data.url();
|
||||
|
||||
function isAddonContent({ contentURL }) {
|
||||
return typeof(contentURL) === "string" && contentURL.indexOf(assetsURI) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits show and hide events.
|
||||
*/
|
||||
const Panel = Symbiont.resolve({
|
||||
constructor: '_init',
|
||||
_onInit: '_onSymbiontInit',
|
||||
destroy: '_symbiontDestructor',
|
||||
_documentUnload: '_workerDocumentUnload'
|
||||
}).compose({
|
||||
_frame: Symbiont.required,
|
||||
_init: Symbiont.required,
|
||||
_onSymbiontInit: Symbiont.required,
|
||||
_symbiontDestructor: Symbiont.required,
|
||||
_emit: Symbiont.required,
|
||||
on: Symbiont.required,
|
||||
removeListener: Symbiont.required,
|
||||
function hasContentScript({ contentScript, contentScriptFile }) {
|
||||
return (isArray(contentScript) ? contentScript.length > 0 :
|
||||
!!contentScript) ||
|
||||
(isArray(contentScriptFile) ? contentScriptFile.length > 0 :
|
||||
!!contentScriptFile);
|
||||
}
|
||||
|
||||
_inited: false,
|
||||
function requiresAddonGlobal(model) {
|
||||
return isAddonContent(model) && !hasContentScript(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to `true` frame loaders between xul panel frame and
|
||||
* hidden frame are swapped. If set to `false` frame loaders are
|
||||
* set back to normal. Setting the value that was already set will
|
||||
* have no effect.
|
||||
*/
|
||||
set _frameLoadersSwapped(value) {
|
||||
if (this.__frameLoadersSwapped == value) return;
|
||||
this._frame.QueryInterface(Ci.nsIFrameLoaderOwner)
|
||||
.swapFrameLoaders(this._viewFrame);
|
||||
this.__frameLoadersSwapped = value;
|
||||
},
|
||||
__frameLoadersSwapped: false,
|
||||
function getAttachEventType(model) {
|
||||
let when = model.contentScriptWhen;
|
||||
return requiresAddonGlobal(model) ? "sdk-panel-content-changed" :
|
||||
when === "start" ? "sdk-panel-content-changed" :
|
||||
when === "end" ? "sdk-panel-document-loaded" :
|
||||
when === "ready" ? "sdk-panel-content-loaded" :
|
||||
null;
|
||||
}
|
||||
|
||||
constructor: function Panel(options) {
|
||||
this._onShow = this._onShow.bind(this);
|
||||
this._onHide = this._onHide.bind(this);
|
||||
this._onAnyPanelShow = this._onAnyPanelShow.bind(this);
|
||||
on('sdk-panel-show', this._onAnyPanelShow);
|
||||
|
||||
this.on('inited', this._onSymbiontInit.bind(this));
|
||||
this.on('propertyChange', this._onChange.bind(this));
|
||||
let number = { is: ['number', 'undefined', 'null'] };
|
||||
let boolean = { is: ['boolean', 'undefined', 'null'] };
|
||||
|
||||
options = options || {};
|
||||
if ('onShow' in options)
|
||||
this.on('show', options.onShow);
|
||||
if ('onHide' in options)
|
||||
this.on('hide', options.onHide);
|
||||
if ('width' in options)
|
||||
this.width = options.width;
|
||||
if ('height' in options)
|
||||
this.height = options.height;
|
||||
if ('contentURL' in options)
|
||||
this.contentURL = options.contentURL;
|
||||
if ('focus' in options) {
|
||||
var value = options.focus;
|
||||
var validatedValue = valid({ $: value }, { $: validBoolean }).$;
|
||||
this._focus =
|
||||
(typeof validatedValue == 'boolean') ? validatedValue : this._focus;
|
||||
let panelContract = contract(merge({
|
||||
width: number,
|
||||
height: number,
|
||||
focus: boolean,
|
||||
}, loaderContract.rules));
|
||||
|
||||
|
||||
function isDisposed(panel) !views.has(panel);
|
||||
|
||||
let panels = new WeakMap();
|
||||
let models = new WeakMap();
|
||||
let views = new WeakMap();
|
||||
let workers = new WeakMap();
|
||||
|
||||
function viewFor(panel) views.get(panel)
|
||||
exports.viewFor = viewFor;
|
||||
|
||||
function modelFor(panel) models.get(panel)
|
||||
function panelFor(view) panels.get(view)
|
||||
function workerFor(panel) workers.get(panel)
|
||||
|
||||
// Utility function takes `panel` instance and makes sure it will be
|
||||
// automatically hidden as soon as other panel is shown.
|
||||
let setupAutoHide = new function() {
|
||||
let refs = new WeakMap();
|
||||
|
||||
return function setupAutoHide(panel) {
|
||||
// Create system event listener that reacts to any panel showing and
|
||||
// hides given `panel` if it's not the one being shown.
|
||||
function listener({subject}) {
|
||||
// It could be that listener is not GC-ed in the same cycle as
|
||||
// panel in such case we remove listener manually.
|
||||
let view = viewFor(panel);
|
||||
if (!view) systemEvents.off("sdk-panel-show", listener);
|
||||
else if (subject !== view) panel.hide();
|
||||
}
|
||||
|
||||
this._init(options);
|
||||
// system event listener is intentionally weak this way we'll allow GC
|
||||
// to claim panel if it's no longer referenced by an add-on code. This also
|
||||
// helps minimizing cleanup required on unload.
|
||||
systemEvents.on("sdk-panel-show", listener);
|
||||
// To make sure listener is not claimed by GC earlier than necessary we
|
||||
// associate it with `panel` it's associated with. This way it won't be
|
||||
// GC-ed earlier than `panel` itself.
|
||||
refs.set(panel, listener);
|
||||
}
|
||||
}
|
||||
|
||||
const Panel = Class({
|
||||
implements: [
|
||||
// Generate accessors for the validated properties that update model on
|
||||
// set and return values from model on get.
|
||||
panelContract.properties(modelFor),
|
||||
EventTarget,
|
||||
Disposable
|
||||
],
|
||||
extends: WorkerHost(workerFor),
|
||||
setup: function setup(options) {
|
||||
let model = merge({
|
||||
width: 320,
|
||||
height: 240,
|
||||
focus: true,
|
||||
}, panelContract(options));
|
||||
models.set(this, model);
|
||||
|
||||
// Setup listeners.
|
||||
setListeners(this, options);
|
||||
|
||||
// Setup view
|
||||
let view = domPanel.make();
|
||||
panels.set(view, this);
|
||||
views.set(this, view);
|
||||
|
||||
// Load panel content.
|
||||
domPanel.setURL(view, model.contentURL);
|
||||
|
||||
setupAutoHide(this);
|
||||
|
||||
let worker = new Worker(options);
|
||||
workers.set(this, worker);
|
||||
|
||||
// pipe events from worker to a panel.
|
||||
pipe(worker, this);
|
||||
},
|
||||
_destructor: function _destructor() {
|
||||
dispose: function dispose() {
|
||||
this.hide();
|
||||
this._removeAllListeners('show');
|
||||
this._removeAllListeners('hide');
|
||||
this._removeAllListeners('propertyChange');
|
||||
this._removeAllListeners('inited');
|
||||
off('sdk-panel-show', this._onAnyPanelShow);
|
||||
// defer cleanup to be performed after panel gets hidden
|
||||
this._xulPanel = null;
|
||||
this._symbiontDestructor(this);
|
||||
this._removeAllListeners();
|
||||
},
|
||||
destroy: function destroy() {
|
||||
this._destructor();
|
||||
off(this);
|
||||
|
||||
detach(workerFor(this));
|
||||
|
||||
domPanel.dispose(viewFor(this));
|
||||
|
||||
// Release circular reference between view and panel instance. This
|
||||
// way view will be GC-ed. And panel as well once all the other refs
|
||||
// will be removed from it.
|
||||
views.delete(this);
|
||||
},
|
||||
/* Public API: Panel.width */
|
||||
get width() this._width,
|
||||
set width(value)
|
||||
this._width = valid({ $: value }, { $: validNumber }).$ || this._width,
|
||||
_width: 320,
|
||||
get width() modelFor(this).width,
|
||||
set width(value) this.resize(value, this.height),
|
||||
/* Public API: Panel.height */
|
||||
get height() this._height,
|
||||
set height(value)
|
||||
this._height = valid({ $: value }, { $: validNumber }).$ || this._height,
|
||||
_height: 240,
|
||||
get height() modelFor(this).height,
|
||||
set height(value) this.resize(this.width, value),
|
||||
|
||||
/* Public API: Panel.focus */
|
||||
get focus() this._focus,
|
||||
_focus: true,
|
||||
get focus() modelFor(this).focus,
|
||||
|
||||
get contentURL() modelFor(this).contentURL,
|
||||
set contentURL(value) {
|
||||
let model = modelFor(this);
|
||||
model.contentURL = panelContract({ contentURL: value }).contentURL;
|
||||
domPanel.setURL(viewFor(this), model.contentURL);
|
||||
},
|
||||
|
||||
/* Public API: Panel.isShowing */
|
||||
get isShowing() !!this._xulPanel && this._xulPanel.state == "open",
|
||||
get isShowing() !isDisposed(this) && domPanel.isOpen(viewFor(this)),
|
||||
|
||||
/* Public API: Panel.show */
|
||||
show: function show(anchor) {
|
||||
anchor = anchor ? getNodeView(anchor) : null;
|
||||
let anchorWindow = getWindow(anchor);
|
||||
let model = modelFor(this);
|
||||
let view = viewFor(this);
|
||||
let anchorView = getNodeView(anchor);
|
||||
|
||||
// If there is no open window, or the anchor is in a private window
|
||||
// then we will not be able to display the panel
|
||||
if (!anchorWindow) {
|
||||
return;
|
||||
}
|
||||
if (!isDisposed(this))
|
||||
domPanel.show(view, model.width, model.height, model.focus, anchorView);
|
||||
|
||||
let document = anchorWindow.document;
|
||||
let xulPanel = this._xulPanel;
|
||||
let panel = this;
|
||||
if (!xulPanel) {
|
||||
xulPanel = this._xulPanel = document.createElementNS(XUL_NS, 'panel');
|
||||
xulPanel.setAttribute("type", "arrow");
|
||||
|
||||
// One anonymous node has a big padding that doesn't work well with
|
||||
// Jetpack, as we would like to display an iframe that completely fills
|
||||
// the panel.
|
||||
// -> Use a XBL wrapper with inner stylesheet to remove this padding.
|
||||
let css = ".panel-inner-arrowcontent, .panel-arrowcontent {padding: 0;}";
|
||||
let originalXBL = "chrome://global/content/bindings/popup.xml#arrowpanel";
|
||||
let binding =
|
||||
'<bindings xmlns="http://www.mozilla.org/xbl">' +
|
||||
'<binding id="id" extends="' + originalXBL + '">' +
|
||||
'<resources>' +
|
||||
'<stylesheet src="data:text/css;charset=utf-8,' +
|
||||
document.defaultView.encodeURIComponent(css) + '"/>' +
|
||||
'</resources>' +
|
||||
'</binding>' +
|
||||
'</bindings>';
|
||||
xulPanel.style.MozBinding = 'url("data:text/xml;charset=utf-8,' +
|
||||
document.defaultView.encodeURIComponent(binding) + '")';
|
||||
|
||||
let frame = document.createElementNS(XUL_NS, 'iframe');
|
||||
frame.setAttribute('type', 'content');
|
||||
frame.setAttribute('flex', '1');
|
||||
frame.setAttribute('transparent', 'transparent');
|
||||
|
||||
if (runtime.OS === "Darwin") {
|
||||
frame.style.borderRadius = "6px";
|
||||
frame.style.padding = "1px";
|
||||
}
|
||||
|
||||
// Load an empty document in order to have an immediatly loaded iframe,
|
||||
// so swapFrameLoaders is going to work without having to wait for load.
|
||||
frame.setAttribute("src","data:;charset=utf-8,");
|
||||
|
||||
xulPanel.appendChild(frame);
|
||||
document.getElementById("mainPopupSet").appendChild(xulPanel);
|
||||
}
|
||||
let { width, height, focus } = this, x, y, position;
|
||||
|
||||
if (!anchor) {
|
||||
// Open the popup in the middle of the window.
|
||||
x = document.documentElement.clientWidth / 2 - width / 2;
|
||||
y = document.documentElement.clientHeight / 2 - height / 2;
|
||||
position = null;
|
||||
}
|
||||
else {
|
||||
// Open the popup by the anchor.
|
||||
let rect = anchor.getBoundingClientRect();
|
||||
|
||||
let window = anchor.ownerDocument.defaultView;
|
||||
|
||||
let zoom = window.mozScreenPixelsPerCSSPixel;
|
||||
let screenX = rect.left + window.mozInnerScreenX * zoom;
|
||||
let screenY = rect.top + window.mozInnerScreenY * zoom;
|
||||
|
||||
// Set up the vertical position of the popup relative to the anchor
|
||||
// (always display the arrow on anchor center)
|
||||
let horizontal, vertical;
|
||||
if (screenY > window.screen.availHeight / 2 + height)
|
||||
vertical = "top";
|
||||
else
|
||||
vertical = "bottom";
|
||||
|
||||
if (screenY > window.screen.availWidth / 2 + width)
|
||||
horizontal = "left";
|
||||
else
|
||||
horizontal = "right";
|
||||
|
||||
let verticalInverse = vertical == "top" ? "bottom" : "top";
|
||||
position = vertical + "center " + verticalInverse + horizontal;
|
||||
|
||||
// Allow panel to flip itself if the panel can't be displayed at the
|
||||
// specified position (useful if we compute a bad position or if the
|
||||
// user moves the window and panel remains visible)
|
||||
xulPanel.setAttribute("flip","both");
|
||||
}
|
||||
|
||||
// Resize the iframe instead of using panel.sizeTo
|
||||
// because sizeTo doesn't work with arrow panels
|
||||
xulPanel.firstChild.style.width = width + "px";
|
||||
xulPanel.firstChild.style.height = height + "px";
|
||||
|
||||
// Only display xulPanel if Panel.hide() was not called
|
||||
// after Panel.show(), but before xulPanel.openPopup
|
||||
// was loaded
|
||||
emit('sdk-panel-show', { data: ADDON_ID, subject: xulPanel });
|
||||
|
||||
// Prevent the panel from getting focus when showing up
|
||||
// if focus is set to false
|
||||
xulPanel.setAttribute("noautofocus",!focus);
|
||||
|
||||
// Wait for the XBL binding to be constructed
|
||||
function waitForBinding() {
|
||||
if (!xulPanel.openPopup) {
|
||||
setTimeout(waitForBinding, 50);
|
||||
return;
|
||||
}
|
||||
|
||||
if (xulPanel.state !== 'hiding') {
|
||||
xulPanel.openPopup(anchor, position, x, y);
|
||||
}
|
||||
}
|
||||
waitForBinding();
|
||||
|
||||
return this._public;
|
||||
return this;
|
||||
},
|
||||
|
||||
/* Public API: Panel.hide */
|
||||
hide: function hide() {
|
||||
// The popuphiding handler takes care of swapping back the frame loaders
|
||||
// and removing the XUL panel from the application window, we just have to
|
||||
// trigger it by hiding the popup.
|
||||
// XXX Sometimes I get "TypeError: xulPanel.hidePopup is not a function"
|
||||
// when quitting the host application while a panel is visible. To suppress
|
||||
// them, this now checks for "hidePopup" in xulPanel before calling it.
|
||||
// It's not clear if there's an actual issue or the error is just normal.
|
||||
let xulPanel = this._xulPanel;
|
||||
if (xulPanel && "hidePopup" in xulPanel)
|
||||
xulPanel.hidePopup();
|
||||
return this._public;
|
||||
// Quit immediately if panel is disposed or there is no state change.
|
||||
domPanel.close(viewFor(this));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/* Public API: Panel.resize */
|
||||
resize: function resize(width, height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
// Resize the iframe instead of using panel.sizeTo
|
||||
// because sizeTo doesn't work with arrow panels
|
||||
let xulPanel = this._xulPanel;
|
||||
if (xulPanel) {
|
||||
xulPanel.firstChild.style.width = width + "px";
|
||||
xulPanel.firstChild.style.height = height + "px";
|
||||
}
|
||||
},
|
||||
let model = modelFor(this);
|
||||
let view = viewFor(this);
|
||||
let change = panelContract({
|
||||
width: width || model.width,
|
||||
height: height || model.height
|
||||
});
|
||||
|
||||
// While the panel is visible, this is the XUL <panel> we use to display it.
|
||||
// Otherwise, it's null.
|
||||
get _xulPanel() this.__xulPanel,
|
||||
set _xulPanel(value) {
|
||||
let xulPanel = this.__xulPanel;
|
||||
if (value === xulPanel) return;
|
||||
if (xulPanel) {
|
||||
xulPanel.removeEventListener(ON_HIDE, this._onHide, false);
|
||||
xulPanel.removeEventListener(ON_SHOW, this._onShow, false);
|
||||
xulPanel.parentNode.removeChild(xulPanel);
|
||||
}
|
||||
if (value) {
|
||||
value.addEventListener(ON_HIDE, this._onHide, false);
|
||||
value.addEventListener(ON_SHOW, this._onShow, false);
|
||||
}
|
||||
this.__xulPanel = value;
|
||||
},
|
||||
__xulPanel: null,
|
||||
get _viewFrame() this.__xulPanel.children[0],
|
||||
/**
|
||||
* When the XUL panel becomes hidden, we swap frame loaders back to move
|
||||
* the content of the panel to the hidden frame & remove panel element.
|
||||
*/
|
||||
_onHide: function _onHide() {
|
||||
try {
|
||||
this._frameLoadersSwapped = false;
|
||||
this._xulPanel = null;
|
||||
this._emit('hide');
|
||||
} catch(e) {
|
||||
this._emit('error', e);
|
||||
}
|
||||
},
|
||||
model.width = change.width
|
||||
model.height = change.height
|
||||
|
||||
/**
|
||||
* Retrieve computed text color style in order to apply to the iframe
|
||||
* document. As MacOS background is dark gray, we need to use skin's
|
||||
* text color.
|
||||
*/
|
||||
_applyStyleToDocument: function _applyStyleToDocument() {
|
||||
if (this._defaultStyleApplied)
|
||||
return;
|
||||
try {
|
||||
let win = this._xulPanel.ownerDocument.defaultView;
|
||||
let node = win.document.getAnonymousElementByAttribute(
|
||||
this._xulPanel, "class", "panel-arrowcontent");
|
||||
if (!node) {
|
||||
// Before bug 764755, anonymous content was different:
|
||||
// TODO: Remove this when targeting FF16+
|
||||
node = win.document.getAnonymousElementByAttribute(
|
||||
this._xulPanel, "class", "panel-inner-arrowcontent");
|
||||
}
|
||||
let textColor = win.getComputedStyle(node).getPropertyValue("color");
|
||||
let doc = this._xulPanel.firstChild.contentDocument;
|
||||
let style = doc.createElement("style");
|
||||
style.textContent = "body { color: " + textColor + "; }";
|
||||
let container = doc.head ? doc.head : doc.documentElement;
|
||||
domPanel.resize(view, model.width, model.height);
|
||||
|
||||
if (container.firstChild)
|
||||
container.insertBefore(style, container.firstChild);
|
||||
else
|
||||
container.appendChild(style);
|
||||
this._defaultStyleApplied = true;
|
||||
}
|
||||
catch(e) {
|
||||
console.error("Unable to apply panel style");
|
||||
console.exception(e);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When the XUL panel becomes shown, we swap frame loaders between panel
|
||||
* frame and hidden frame to preserve state of the content dom.
|
||||
*/
|
||||
_onShow: function _onShow() {
|
||||
try {
|
||||
if (!this._inited) { // defer if not initialized yet
|
||||
this.on('inited', this._onShow.bind(this));
|
||||
} else {
|
||||
this._frameLoadersSwapped = true;
|
||||
this._applyStyleToDocument();
|
||||
this._emit('show');
|
||||
}
|
||||
} catch(e) {
|
||||
this._emit('error', e);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When any panel is displayed, system-wide, close `this`
|
||||
* panel unless it's the most recently displayed panel
|
||||
*/
|
||||
_onAnyPanelShow: function _onAnyPanelShow(e) {
|
||||
if (e.subject !== this._xulPanel)
|
||||
this.hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* Notification that panel was fully initialized.
|
||||
*/
|
||||
_onInit: function _onInit() {
|
||||
this._inited = true;
|
||||
|
||||
// Avoid panel document from resizing the browser window
|
||||
// New platform capability added through bug 635673
|
||||
let docShell = getDocShell(this._frame);
|
||||
if (docShell && "allowWindowControl" in docShell)
|
||||
docShell.allowWindowControl = false;
|
||||
|
||||
// perform all deferred tasks like initSymbiont, show, hide ...
|
||||
// TODO: We're publicly exposing a private event here; this
|
||||
// 'inited' event should really be made private, somehow.
|
||||
this._emit('inited');
|
||||
},
|
||||
|
||||
// Catch document unload event in order to rebind load event listener with
|
||||
// Symbiont._initFrame if Worker._documentUnload destroyed the worker
|
||||
_documentUnload: function(subject, topic, data) {
|
||||
if (this._workerDocumentUnload(subject, topic, data)) {
|
||||
this._initFrame(this._frame);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_onChange: function _onChange(e) {
|
||||
this._frameLoadersSwapped = false;
|
||||
if ('contentURL' in e && this._frame) {
|
||||
// Cleanup the worker before injecting the content script in the new
|
||||
// document
|
||||
this._workerCleanup();
|
||||
this._initFrame(this._frame);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
exports.Panel = function(options) Panel(options)
|
||||
exports.Panel.prototype = Panel.prototype;
|
||||
exports.Panel = Panel;
|
||||
|
||||
// Filter panel events to only panels that are create by this module.
|
||||
let panelEvents = filter(function({target}) panelFor(target), events);
|
||||
|
||||
// Panel events emitted after panel has being shown.
|
||||
let shows = filter(function({type}) type === "sdk-panel-shown", panelEvents);
|
||||
|
||||
// Panel events emitted after panel became hidden.
|
||||
let hides = filter(function({type}) type === "sdk-panel-hidden", panelEvents);
|
||||
|
||||
// Panel events emitted after content inside panel is ready. For different
|
||||
// panels ready may mean different state based on `contentScriptWhen` attribute.
|
||||
// Weather given event represents readyness is detected by `getAttachEventType`
|
||||
// helper function.
|
||||
let ready = filter(function({type, target})
|
||||
getAttachEventType(modelFor(panelFor(target))) === type, panelEvents);
|
||||
|
||||
// Panel events emitted after content document in the panel has changed.
|
||||
let change = filter(function({type}) type === "sdk-panel-content-changed",
|
||||
panelEvents);
|
||||
|
||||
// Forward panel show / hide events to panel's own event listeners.
|
||||
on(shows, "data", function({target}) emit(panelFor(target), "show"));
|
||||
on(hides, "data", function({target}) emit(panelFor(target), "hide"));
|
||||
|
||||
on(ready, "data", function({target}) {
|
||||
let worker = workerFor(panelFor(target));
|
||||
attach(worker, domPanel.getContentDocument(target).defaultView);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
// This module basically translates system/events to a SDK standard events
|
||||
// so that `map`, `filter` and other utilities could be used with them.
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
const events = require("../system/events");
|
||||
const { emit } = require("../event/core");
|
||||
|
||||
let channel = {};
|
||||
|
||||
function forward({ subject, type, data })
|
||||
emit(channel, "data", { target: subject, type: type, data: data });
|
||||
|
||||
["sdk-panel-show", "sdk-panel-hide", "sdk-panel-shown",
|
||||
"sdk-panel-hidden", "sdk-panel-content-changed", "sdk-panel-content-loaded",
|
||||
"sdk-panel-document-loaded"
|
||||
].forEach(function(type) events.on(type, forward));
|
||||
|
||||
exports.events = channel;
|
|
@ -0,0 +1,322 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const { setTimeout } = require("../timers");
|
||||
const { platform } = require("../system");
|
||||
const { getMostRecentBrowserWindow, getOwnerBrowserWindow,
|
||||
getHiddenWindow } = require("../window/utils");
|
||||
const { create: createFrame, swapFrameLoaders } = require("../frame/utils");
|
||||
const { window: addonWindow } = require("../addon/window");
|
||||
const events = require("../system/events");
|
||||
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
function open(panel, width, height, anchor) {
|
||||
// Wait for the XBL binding to be constructed
|
||||
if (!panel.openPopup) setTimeout(open, 50, panel, width, height, anchor);
|
||||
else display(panel, width, height, anchor);
|
||||
}
|
||||
exports.open = open;
|
||||
|
||||
function isOpen(panel) {
|
||||
return panel.state === "open"
|
||||
}
|
||||
exports.isOpen = isOpen;
|
||||
|
||||
|
||||
function close(panel) {
|
||||
// Sometimes "TypeError: panel.hidePopup is not a function" is thrown
|
||||
// when quitting the host application while a panel is visible. To suppress
|
||||
// these errors, check for "hidePopup" in panel before calling it.
|
||||
// It's not clear if there's an issue or it's expected behavior.
|
||||
|
||||
return panel.hidePopup && panel.hidePopup();
|
||||
}
|
||||
exports.close = close
|
||||
|
||||
|
||||
function resize(panel, width, height) {
|
||||
// Resize the iframe instead of using panel.sizeTo
|
||||
// because sizeTo doesn't work with arrow panels
|
||||
panel.firstChild.style.width = width + "px";
|
||||
panel.firstChild.style.height = height + "px";
|
||||
}
|
||||
exports.resize = resize
|
||||
|
||||
function display(panel, width, height, anchor) {
|
||||
let document = panel.ownerDocument;
|
||||
let x = null;
|
||||
let y = null;
|
||||
let position = null;
|
||||
|
||||
// Panel XBL has some SDK incompatible styling decisions. We shim panel
|
||||
// instances until proper fix for Bug 859504 is shipped.
|
||||
shimDefaultStyle(panel);
|
||||
|
||||
if (!anchor) {
|
||||
// Open the popup in the middle of the window.
|
||||
x = document.documentElement.clientWidth / 2 - width / 2;
|
||||
y = document.documentElement.clientHeight / 2 - height / 2;
|
||||
position = null;
|
||||
}
|
||||
else {
|
||||
// Open the popup by the anchor.
|
||||
let rect = anchor.getBoundingClientRect();
|
||||
|
||||
let window = anchor.ownerDocument.defaultView;
|
||||
|
||||
let zoom = window.mozScreenPixelsPerCSSPixel;
|
||||
let screenX = rect.left + window.mozInnerScreenX * zoom;
|
||||
let screenY = rect.top + window.mozInnerScreenY * zoom;
|
||||
|
||||
// Set up the vertical position of the popup relative to the anchor
|
||||
// (always display the arrow on anchor center)
|
||||
let horizontal, vertical;
|
||||
if (screenY > window.screen.availHeight / 2 + height)
|
||||
vertical = "top";
|
||||
else
|
||||
vertical = "bottom";
|
||||
|
||||
if (screenY > window.screen.availWidth / 2 + width)
|
||||
horizontal = "left";
|
||||
else
|
||||
horizontal = "right";
|
||||
|
||||
let verticalInverse = vertical == "top" ? "bottom" : "top";
|
||||
position = vertical + "center " + verticalInverse + horizontal;
|
||||
|
||||
// Allow panel to flip itself if the panel can't be displayed at the
|
||||
// specified position (useful if we compute a bad position or if the
|
||||
// user moves the window and panel remains visible)
|
||||
panel.setAttribute("flip", "both");
|
||||
}
|
||||
|
||||
// Resize the iframe instead of using panel.sizeTo
|
||||
// because sizeTo doesn't work with arrow panels
|
||||
panel.firstChild.style.width = width + "px";
|
||||
panel.firstChild.style.height = height + "px";
|
||||
|
||||
panel.openPopup(anchor, position, x, y);
|
||||
}
|
||||
exports.display = display;
|
||||
|
||||
// This utility function is just a workaround until Bug 859504 has shipped.
|
||||
function shimDefaultStyle(panel) {
|
||||
let document = panel.ownerDocument;
|
||||
// Please note that `panel` needs to be part of document in order to reach
|
||||
// it's anonymous nodes. One of the anonymous node has a big padding which
|
||||
// doesn't work well since panel frame needs to fill all of the panel.
|
||||
// XBL binding is a not the best option as it's applied asynchronously, and
|
||||
// makes injected frames behave in strange way. Also this feels a lot
|
||||
// cheaper to do.
|
||||
["panel-inner-arrowcontent", "panel-arrowcontent"].forEach(function(value) {
|
||||
let node = document.getAnonymousElementByAttribute(panel, "class", value);
|
||||
if (node) node.style.padding = 0;
|
||||
});
|
||||
}
|
||||
|
||||
function show(panel, width, height, focus, anchor) {
|
||||
// Prevent the panel from getting focus when showing up
|
||||
// if focus is set to false
|
||||
panel.setAttribute("noautofocus", !focus);
|
||||
|
||||
|
||||
let window = anchor && getOwnerBrowserWindow(anchor);
|
||||
let { document } = window ? window : getMostRecentBrowserWindow();
|
||||
attach(panel, document);
|
||||
open(panel, width, height, anchor);
|
||||
}
|
||||
exports.show = show
|
||||
|
||||
function setupPanelFrame(frame) {
|
||||
frame.setAttribute("flex", 1);
|
||||
frame.setAttribute("transparent", "transparent");
|
||||
frame.setAttribute("showcaret", true);
|
||||
frame.setAttribute("autocompleteenabled", true);
|
||||
if (platform === "darwin") {
|
||||
frame.style.borderRadius = "6px";
|
||||
frame.style.padding = "1px";
|
||||
}
|
||||
}
|
||||
|
||||
let EVENT_NAMES = {
|
||||
"popupshowing": "sdk-panel-show",
|
||||
"popuphiding": "sdk-panel-hide",
|
||||
"popupshown": "sdk-panel-shown",
|
||||
"popuphidden": "sdk-panel-hidden",
|
||||
"document-element-inserted": "sdk-panel-content-changed",
|
||||
"DOMContentLoaded": "sdk-panel-content-loaded",
|
||||
"load": "sdk-panel-document-loaded"
|
||||
};
|
||||
|
||||
function make(document) {
|
||||
document = document || getMostRecentBrowserWindow().document;
|
||||
let panel = document.createElementNS(XUL_NS, "panel");
|
||||
panel.setAttribute("type", "arrow");
|
||||
|
||||
// Note that panel is a parent of `viewFrame` who's `docShell` will be
|
||||
// configured at creation time. If `panel` and there for `viewFrame` won't
|
||||
// have an owner document attempt to access `docShell` will throw. There
|
||||
// for we attach panel to a document.
|
||||
attach(panel, document);
|
||||
|
||||
let frameOptions = {
|
||||
allowJavascript: true,
|
||||
allowPlugins: true,
|
||||
allowAuth: true,
|
||||
allowWindowControl: false,
|
||||
// Need to override `nodeName` to use `iframe` as `browsers` save session
|
||||
// history and in consequence do not dispatch "inner-window-destroyed"
|
||||
// notifications.
|
||||
browser: false,
|
||||
// Note that use of this URL let's use swap frame loaders earlier
|
||||
// than if we used default "about:blank".
|
||||
uri: "data:text/plain;charset=utf-8,"
|
||||
};
|
||||
|
||||
let backgroundFrame = createFrame(addonWindow, frameOptions);
|
||||
setupPanelFrame(backgroundFrame);
|
||||
|
||||
let viewFrame = createFrame(panel, frameOptions);
|
||||
setupPanelFrame(viewFrame);
|
||||
|
||||
function onDisplayChange({type}) {
|
||||
try { swapFrameLoaders(backgroundFrame, viewFrame); }
|
||||
catch(error) { console.exception(error); }
|
||||
events.emit(EVENT_NAMES[type], { subject: panel });
|
||||
}
|
||||
|
||||
function onContentReady({target, type}) {
|
||||
if (target === getContentDocument(panel)) {
|
||||
style(panel);
|
||||
events.emit(EVENT_NAMES[type], { subject: panel });
|
||||
}
|
||||
}
|
||||
|
||||
function onContentLoad({target, type}) {
|
||||
if (target === getContentDocument(panel))
|
||||
events.emit(EVENT_NAMES[type], { subject: panel });
|
||||
}
|
||||
|
||||
function onContentChange({subject, type}) {
|
||||
let document = subject;
|
||||
if (document === getContentDocument(panel) && document.defaultView)
|
||||
events.emit(EVENT_NAMES[type], { subject: panel });
|
||||
}
|
||||
|
||||
function onPanelStateChange({type}) {
|
||||
events.emit(EVENT_NAMES[type], { subject: panel })
|
||||
}
|
||||
|
||||
panel.addEventListener("popupshowing", onDisplayChange, false);
|
||||
panel.addEventListener("popuphiding", onDisplayChange, false);
|
||||
panel.addEventListener("popupshown", onPanelStateChange, false);
|
||||
panel.addEventListener("popuphidden", onPanelStateChange, false);
|
||||
|
||||
// Panel content document can be either in panel `viewFrame` or in
|
||||
// a `backgroundFrame` depending on panel state. Listeners are set
|
||||
// on both to avoid setting and removing listeners on panel state changes.
|
||||
|
||||
panel.addEventListener("DOMContentLoaded", onContentReady, true);
|
||||
backgroundFrame.addEventListener("DOMContentLoaded", onContentReady, true);
|
||||
|
||||
panel.addEventListener("load", onContentLoad, true);
|
||||
backgroundFrame.addEventListener("load", onContentLoad, true);
|
||||
|
||||
events.on("document-element-inserted", onContentChange);
|
||||
|
||||
|
||||
panel.backgroundFrame = backgroundFrame;
|
||||
|
||||
// Store event listener on the panel instance so that it won't be GC-ed
|
||||
// while panel is alive.
|
||||
panel.onContentChange = onContentChange;
|
||||
|
||||
return panel;
|
||||
}
|
||||
exports.make = make;
|
||||
|
||||
function attach(panel, document) {
|
||||
document = document || getMostRecentBrowserWindow().document;
|
||||
let container = document.getElementById("mainPopupSet");
|
||||
if (container !== panel.parentNode) {
|
||||
detach(panel);
|
||||
document.getElementById("mainPopupSet").appendChild(panel);
|
||||
}
|
||||
}
|
||||
exports.attach = attach;
|
||||
|
||||
function detach(panel) {
|
||||
if (panel.parentNode) panel.parentNode.removeChild(panel);
|
||||
}
|
||||
exports.detach = detach;
|
||||
|
||||
function dispose(panel) {
|
||||
panel.backgroundFrame.parentNode.removeChild(panel.backgroundFrame);
|
||||
panel.backgroundFrame = null;
|
||||
events.off("document-element-inserted", panel.onContentChange);
|
||||
panel.onContentChange = null;
|
||||
detach(panel);
|
||||
}
|
||||
exports.dispose = dispose;
|
||||
|
||||
function style(panel) {
|
||||
/**
|
||||
Injects default OS specific panel styles into content document that is loaded
|
||||
into given panel. Optionally `document` of the browser window can be
|
||||
given to inherit styles from it, by default it will use either panel owner
|
||||
document or an active browser's document. It should not matter though unless
|
||||
Firefox decides to style windows differently base on profile or mode like
|
||||
chrome for example.
|
||||
**/
|
||||
|
||||
try {
|
||||
let document = panel.ownerDocument;
|
||||
let contentDocument = getContentDocument(panel);
|
||||
let window = document.defaultView;
|
||||
let node = document.getAnonymousElementByAttribute(panel, "class",
|
||||
"panel-arrowcontent") ||
|
||||
// Before bug 764755, anonymous content was different:
|
||||
// TODO: Remove this when targeting FF16+
|
||||
document.getAnonymousElementByAttribute(panel, "class",
|
||||
"panel-inner-arrowcontent");
|
||||
|
||||
let color = window.getComputedStyle(node).getPropertyValue("color");
|
||||
|
||||
let style = contentDocument.createElement("style");
|
||||
style.id = "sdk-panel-style";
|
||||
style.textContent = "body { color: " + color + "; }";
|
||||
|
||||
let container = contentDocument.head ? contentDocument.head :
|
||||
contentDocument.documentElement;
|
||||
|
||||
if (container.firstChild)
|
||||
container.insertBefore(style, container.firstChild);
|
||||
else
|
||||
container.appendChild(style);
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Unable to apply panel style");
|
||||
console.exception(error);
|
||||
}
|
||||
}
|
||||
exports.style = style;
|
||||
|
||||
function getContentFrame(panel) isOpen(panel) ? panel.firstChild :
|
||||
panel.backgroundFrame
|
||||
exports.getContentFrame = getContentFrame;
|
||||
|
||||
function getContentDocument(panel) getContentFrame(panel).contentDocument
|
||||
exports.getContentDocument = getContentDocument;
|
||||
|
||||
function setURL(panel, url) getContentFrame(panel).setAttribute("src", url)
|
||||
exports.setURL = setURL;
|
|
@ -25,6 +25,8 @@ const request = ns();
|
|||
// reuse it.
|
||||
const { validateOptions, validateSingleOption } = new OptionsValidator({
|
||||
url: {
|
||||
// Also converts a URL instance to string, bug 857902
|
||||
map: function (url) url.toString(),
|
||||
ok: isValidURI
|
||||
},
|
||||
headers: {
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const { Class } = require("../core/heritage");
|
||||
const { ns } = require("../core/namespace");
|
||||
const { URL } = require('../url');
|
||||
const events = require("../system/events");
|
||||
const { loadSheet, removeSheet, isTypeValid } = require("./utils");
|
||||
const { isString } = require("../lang/type");
|
||||
const { attachTo, detachFrom, getTargetWindow } = require("../content/mod");
|
||||
|
||||
const { freeze, create } = Object;
|
||||
const LOCAL_URI_SCHEMES = ['resource', 'data'];
|
||||
|
||||
function isLocalURL(item) {
|
||||
try {
|
||||
return LOCAL_URI_SCHEMES.indexOf(URL(item).scheme) > -1;
|
||||
}
|
||||
catch(e) {}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function Style({ source, uri, type }) {
|
||||
source = source == null ? null : freeze([].concat(source));
|
||||
uri = uri == null ? null : freeze([].concat(uri));
|
||||
type = type == null ? "author" : type;
|
||||
|
||||
if (source && !source.every(isString))
|
||||
throw new Error('Style.source must be a string or an array of strings.');
|
||||
|
||||
if (uri && !uri.every(isLocalURL))
|
||||
throw new Error('Style.uri must be a local URL or an array of local URLs');
|
||||
|
||||
if (type && !isTypeValid(type))
|
||||
throw new Error('Style.type must be "agent", "user" or "author"');
|
||||
|
||||
return freeze(create(Style.prototype, {
|
||||
"source": { value: source, enumerable: true },
|
||||
"uri": { value: uri, enumerable: true },
|
||||
"type": { value: type, enumerable: true }
|
||||
}));
|
||||
};
|
||||
|
||||
exports.Style = Style;
|
||||
|
||||
attachTo.define(Style, function (style, window) {
|
||||
if (style.uri) {
|
||||
for (let uri of style.uri)
|
||||
loadSheet(window, uri, style.type);
|
||||
}
|
||||
|
||||
if (style.source) {
|
||||
let uri = "data:text/css;charset=utf-8,";
|
||||
|
||||
uri += encodeURIComponent(style.source.join(""));
|
||||
|
||||
loadSheet(window, uri, style.type);
|
||||
}
|
||||
});
|
||||
|
||||
detachFrom.define(Style, function (style, window) {
|
||||
if (style.uri)
|
||||
for (let uri of style.uri)
|
||||
removeSheet(window, uri);
|
||||
|
||||
if (style.source) {
|
||||
let uri = "data:text/css;charset=utf-8,";
|
||||
|
||||
uri += encodeURIComponent(style.source.join(""));
|
||||
|
||||
removeSheet(window, uri, style.type);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,79 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
|
||||
const io = Cc['@mozilla.org/network/io-service;1'].
|
||||
getService(Ci.nsIIOService);
|
||||
|
||||
const SHEET_TYPE = {
|
||||
"agent": "AGENT_SHEET",
|
||||
"user": "USER_SHEET",
|
||||
"author": "AUTHOR_SHEET"
|
||||
};
|
||||
|
||||
function getDOMWindowUtils(window) {
|
||||
return window.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils);
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously loads a style sheet from `uri` and adds it to the list of
|
||||
* additional style sheets of the document.
|
||||
* The sheets added takes effect immediately, and only on the document of the
|
||||
* `window` given.
|
||||
*/
|
||||
function loadSheet(window, url, type) {
|
||||
if (!(type && type in SHEET_TYPE))
|
||||
type = "author";
|
||||
|
||||
type = SHEET_TYPE[type];
|
||||
|
||||
if (!(url instanceof Ci.nsIURI))
|
||||
url = io.newURI(url, null, null);
|
||||
|
||||
let winUtils = getDOMWindowUtils(window);
|
||||
try {
|
||||
winUtils.loadSheet(url, winUtils[type]);
|
||||
}
|
||||
catch (e) {};
|
||||
};
|
||||
exports.loadSheet = loadSheet;
|
||||
|
||||
/**
|
||||
* Remove the document style sheet at `sheetURI` from the list of additional
|
||||
* style sheets of the document. The removal takes effect immediately.
|
||||
*/
|
||||
function removeSheet(window, url, type) {
|
||||
if (!(type && type in SHEET_TYPE))
|
||||
type = "author";
|
||||
|
||||
type = SHEET_TYPE[type];
|
||||
|
||||
if (!(url instanceof Ci.nsIURI))
|
||||
url = io.newURI(url, null, null);
|
||||
|
||||
let winUtils = getDOMWindowUtils(window);
|
||||
|
||||
try {
|
||||
winUtils.removeSheet(url, winUtils[type]);
|
||||
}
|
||||
catch (e) {};
|
||||
};
|
||||
exports.removeSheet = removeSheet;
|
||||
|
||||
/**
|
||||
* Returns `true` if the `type` given is valid, otherwise `false`.
|
||||
* The values currently accepted are: "agent", "user" and "author".
|
||||
*/
|
||||
function isTypeValid(type) {
|
||||
return type in SHEET_TYPE;
|
||||
}
|
||||
exports.isTypeValid = isTypeValid;
|
|
@ -41,7 +41,10 @@ exports.env = Proxy.create({
|
|||
// New environment variables can be defined just by defining properties
|
||||
// on this object.
|
||||
defineProperty: function(name, { value }) set(name, value),
|
||||
delete: function(name) set(name, null),
|
||||
delete: function(name) {
|
||||
set(name, null);
|
||||
return true;
|
||||
},
|
||||
|
||||
// We present all properties as own, there for we just delegate to `hasOwn`.
|
||||
has: function(name) this.hasOwn(name),
|
||||
|
|
|
@ -101,3 +101,15 @@ function fromIterator(iterator) {
|
|||
return array;
|
||||
}
|
||||
exports.fromIterator = fromIterator;
|
||||
|
||||
|
||||
function find(array, predicate) {
|
||||
var index = 0;
|
||||
var count = array.length;
|
||||
while (index < count) {
|
||||
var value = array[index];
|
||||
if (predicate(value)) return value;
|
||||
else index = index + 1;
|
||||
}
|
||||
}
|
||||
exports.find = find;
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { validateOptions: valid } = require("../deprecated/api-utils");
|
||||
|
||||
// Function takes property validation rules and returns function that given
|
||||
// an `options` object will return validated / normalized options back. If
|
||||
// option(s) are invalid validator will throw exception described by rules.
|
||||
// Returned will also have contain `rules` property with a given validation
|
||||
// rules and `properties` function that can be used to generate validated
|
||||
// property getter and setters can be mixed into prototype. For more details
|
||||
// see `properties` function below.
|
||||
function contract(rules) {
|
||||
function validator(options) {
|
||||
return valid(options || {}, rules);
|
||||
}
|
||||
validator.rules = rules
|
||||
validator.properties = function(modelFor) {
|
||||
return properties(modelFor, rules);
|
||||
}
|
||||
return validator;
|
||||
}
|
||||
exports.contract = contract
|
||||
|
||||
// Function takes `modelFor` instance state model accessor functions and
|
||||
// a property validation rules and generates object with getters and setters
|
||||
// that can be mixed into prototype. Property accessors update model for the
|
||||
// given instance. If you wish to react to property updates you can always
|
||||
// override setters to put specific logic.
|
||||
function properties(modelFor, rules) {
|
||||
let descriptor = Object.keys(rules).reduce(function(descriptor, name) {
|
||||
descriptor[name] = {
|
||||
get: function() { return modelFor(this)[name] },
|
||||
set: function(value) {
|
||||
let change = {};
|
||||
change[name] = value;
|
||||
modelFor(this)[name] = valid(change, rules)[name];
|
||||
}
|
||||
}
|
||||
return descriptor
|
||||
}, {});
|
||||
return Object.create(Object.prototype, descriptor);
|
||||
}
|
||||
exports.properties = properties
|
|
@ -10,11 +10,6 @@ module.metadata = {
|
|||
};
|
||||
|
||||
var { Ci } = require("chrome");
|
||||
|
||||
/**
|
||||
Temporarily emulate method so we don't have to uplift whole method
|
||||
implementation.
|
||||
|
||||
var method = require("method/core");
|
||||
|
||||
// Returns DOM node associated with a view for
|
||||
|
@ -27,20 +22,5 @@ getNodeView.define(function(value) {
|
|||
return value;
|
||||
return null;
|
||||
});
|
||||
**/
|
||||
|
||||
let implementations = new WeakMap();
|
||||
|
||||
function getNodeView(value) {
|
||||
if (value instanceof Ci.nsIDOMNode)
|
||||
return value;
|
||||
if (implementations.has(value))
|
||||
return implementations.get(value)(value);
|
||||
|
||||
return null;
|
||||
}
|
||||
getNodeView.implement = function(value, implementation) {
|
||||
implementations.set(value, implementation)
|
||||
}
|
||||
|
||||
exports.getNodeView = getNodeView;
|
||||
|
|
|
@ -340,3 +340,21 @@ function getFrames(window) {
|
|||
}, [])
|
||||
}
|
||||
exports.getFrames = getFrames;
|
||||
|
||||
function getOwnerBrowserWindow(node) {
|
||||
/**
|
||||
Takes DOM node and returns browser window that contains it.
|
||||
**/
|
||||
|
||||
let window = node.ownerDocument.defaultView.top;
|
||||
// If anchored window is browser then it's target browser window.
|
||||
if (isBrowser(window)) return window;
|
||||
// Otherwise iterate over each browser window and find a one that
|
||||
// contains browser for the anchored window document.
|
||||
let document = window.document;
|
||||
let browsers = windows("navigator:browser", { includePrivate: true });
|
||||
return array.find(browsers, function isTargetBrowser(window) {
|
||||
return !!window.gBrowser.getBrowserForDocument(document);
|
||||
});
|
||||
}
|
||||
exports.getOwnerBrowserWindow = getOwnerBrowserWindow;
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
// This module attempts to hide trait based nature of the worker so that
|
||||
// code depending on workers could be de-trait-ified without changing worker
|
||||
// implementation.
|
||||
|
||||
const { Worker: WorkerTrait } = require("../content/worker");
|
||||
const { Loader } = require("../content/loader");
|
||||
const { merge } = require("../util/object");
|
||||
const { emit } = require("../event/core");
|
||||
|
||||
const LegacyWorker = WorkerTrait.resolve({
|
||||
_setListeners: "__setListeners",
|
||||
}).compose(Loader, {
|
||||
_setListeners: function() {},
|
||||
attach: function(window) this._attach(window),
|
||||
detach: function() this._workerCleanup()
|
||||
});
|
||||
|
||||
// Weak map that stores mapping between regular worker instances and
|
||||
// legacy trait based worker instances.
|
||||
let traits = new WeakMap();
|
||||
|
||||
function traitFor(worker) traits.get(worker, null);
|
||||
|
||||
function WorkerHost(workerFor) {
|
||||
// Define worker properties that just proxy to a wrapped trait.
|
||||
return ["postMessage", "port", "url", "tab"].reduce(function(proto, name) {
|
||||
Object.defineProperty(proto, name, {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
get: function() traitFor(workerFor(this))[name],
|
||||
set: function(value) traitFor(workerFor(this))[name] = value
|
||||
});
|
||||
return proto;
|
||||
}, {});
|
||||
}
|
||||
exports.WorkerHost = WorkerHost;
|
||||
|
||||
// Type representing worker instance.
|
||||
function Worker(options) {
|
||||
let worker = Object.create(Worker.prototype);
|
||||
let trait = new LegacyWorker(options);
|
||||
["pageshow", "pagehide", "detach", "message", "error"].forEach(function(key) {
|
||||
trait.on(key, function() {
|
||||
emit.apply(emit, [worker, key].concat(Array.slice(arguments)));
|
||||
// Workaround lack of ability to listen on all events by emulating
|
||||
// such ability. This will become obsolete once Bug 821065 is fixed.
|
||||
emit.apply(emit, [worker, "*", key].concat(Array.slice(arguments)));
|
||||
});
|
||||
});
|
||||
traits.set(worker, trait);
|
||||
return worker;
|
||||
}
|
||||
exports.Worker = Worker;
|
||||
|
||||
function detach(worker) {
|
||||
let trait = traitFor(worker);
|
||||
if (trait) trait.detach();
|
||||
}
|
||||
exports.detach = detach;
|
||||
|
||||
function attach(worker, window) {
|
||||
let trait = traitFor(worker);
|
||||
// Cleanup the worker before injecting the content script into a new document.
|
||||
trait.attach(window);
|
||||
}
|
||||
exports.attach = attach;
|
|
@ -40,7 +40,7 @@ function startup(data, reason) {
|
|||
try {
|
||||
let QuitObserver = {
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
Services.obs.removeObserver(QuitObserver, "quit-application", false);
|
||||
Services.obs.removeObserver(QuitObserver, "quit-application");
|
||||
dump("MU: APPLICATION-QUIT\n");
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
exports.id = "panel-main";
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"id": "test-panel"
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
exports.id = "page-mod";
|
|
@ -1,5 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
exports.id = "local-panel";
|
|
@ -1040,47 +1040,31 @@ exports.testOnLoadEventWithImage = function(test) {
|
|||
exports.testOnPageShowEvent = function (test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
let firstUrl = 'about:home';
|
||||
let secondUrl = 'about:newtab';
|
||||
let firstUrl = 'data:text/html;charset=utf-8,First';
|
||||
let secondUrl = 'data:text/html;charset=utf-8,Second';
|
||||
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require('sdk/tabs');
|
||||
|
||||
let wait = 500;
|
||||
let counter = 1;
|
||||
tabs.on('pageshow', function setup(tab, persisted) {
|
||||
if (counter === 1)
|
||||
let counter = 0;
|
||||
tabs.on('pageshow', function onPageShow(tab, persisted) {
|
||||
counter++;
|
||||
if (counter === 1) {
|
||||
test.assert(!persisted, 'page should not be cached on initial load');
|
||||
|
||||
if (wait > 5000) {
|
||||
test.fail('Page was not cached after 5s')
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
tab.url = secondUrl;
|
||||
}
|
||||
|
||||
if (tab.url === firstUrl) {
|
||||
// If first page has persisted, pass
|
||||
if (persisted) {
|
||||
tabs.removeListener('pageshow', setup);
|
||||
test.pass('pageshow event called on history.back()');
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
}
|
||||
// On the first run, or if the page wasn't cached
|
||||
// the first time due to not waiting long enough,
|
||||
// try again with a longer delay (this is terrible
|
||||
// and ugly)
|
||||
else {
|
||||
counter++;
|
||||
timer.setTimeout(function () {
|
||||
tab.url = secondUrl;
|
||||
wait *= 2;
|
||||
}, wait);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (counter === 2) {
|
||||
test.assert(!persisted, 'second test page should not be cached either');
|
||||
tab.attach({
|
||||
contentScript: 'setTimeout(function () { window.history.back(); }, 0)'
|
||||
});
|
||||
}
|
||||
else {
|
||||
test.assert(persisted, 'when we get back to the fist page, it has to' +
|
||||
'come from cache');
|
||||
tabs.removeListener('pageshow', onPageShow);
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
}
|
||||
});
|
||||
|
||||
tabs.open({
|
||||
|
|
|
@ -10,16 +10,6 @@ exports['test:contentURL'] = function(test) {
|
|||
let loader = Loader(),
|
||||
value, emitted = 0, changes = 0;
|
||||
|
||||
test.assertRaises(
|
||||
function() loader.contentURL = undefined,
|
||||
'The `contentURL` option must be a valid URL.',
|
||||
'Must throw an exception if `contentURL` is not URL.'
|
||||
);
|
||||
test.assertRaises(
|
||||
function() loader.contentURL = null,
|
||||
'The `contentURL` option must be a valid URL.',
|
||||
'Must throw an exception if `contentURL` is not URL.'
|
||||
);
|
||||
test.assertRaises(
|
||||
function() loader.contentURL = 4,
|
||||
'The `contentURL` option must be a valid URL.',
|
||||
|
|
|
@ -163,4 +163,29 @@ exports["test loader unloads do not affect other loaders"] = function(assert) {
|
|||
assert.equal(disposals, 2, "2 destroy calls");
|
||||
}
|
||||
|
||||
exports["test disposables that throw"] = function(assert) {
|
||||
let loader = Loader(module);
|
||||
let { Disposable } = loader.require("sdk/core/disposable");
|
||||
|
||||
let disposals = 0
|
||||
|
||||
let Foo = Class({
|
||||
extends: Disposable,
|
||||
setup: function setup(a, b) {
|
||||
throw Error("Boom!")
|
||||
},
|
||||
dispose: function dispose() {
|
||||
disposals = disposals + 1
|
||||
}
|
||||
})
|
||||
|
||||
assert.throws(function() {
|
||||
let foo1 = Foo()
|
||||
}, /Boom/, "disposable constructors may throw");
|
||||
|
||||
loader.unload();
|
||||
|
||||
assert.equal(disposals, 0, "no disposal if constructor threw");
|
||||
}
|
||||
|
||||
require('test').run(exports);
|
||||
|
|
|
@ -41,8 +41,8 @@ exports['test set'] = function(assert) {
|
|||
|
||||
exports['test unset'] = function(assert) {
|
||||
env.BLA4 = 'bla';
|
||||
assert.equal(env.BLA4, 'bla', 'BLA4 env varibale is set');
|
||||
delete env.BLA4;
|
||||
assert.equal(env.BLA4, 'bla', 'BLA4 env variable is set');
|
||||
assert.equal(delete env.BLA4, true, 'BLA4 env variable is removed');
|
||||
assert.equal(env.BLA4, undefined, 'BLA4 env variable is unset');
|
||||
assert.equal('BLA4' in env, false, 'BLA4 env variable no longer exists' );
|
||||
};
|
||||
|
|
|
@ -33,6 +33,8 @@ exports.testMatchPatternTestTrue = function(test) {
|
|||
|
||||
ok(/.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
|
||||
ok(/https:.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
|
||||
ok('*.sample.com', 'http://ex.sample.com/foo.html');
|
||||
ok('*.amp.le.com', 'http://ex.amp.le.com');
|
||||
};
|
||||
|
||||
exports.testMatchPatternTestFalse = function(test) {
|
||||
|
@ -70,6 +72,14 @@ exports.testMatchPatternTestFalse = function(test) {
|
|||
ok(/.*zilla/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
|
||||
ok(/.*Zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=655464"); // bug 655464
|
||||
ok(/https:.*zilla/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
|
||||
|
||||
// bug 856913
|
||||
ok('*.ign.com', 'http://www.design.com');
|
||||
ok('*.ign.com', 'http://design.com');
|
||||
ok('*.zilla.com', 'http://bugzilla.mozilla.com');
|
||||
ok('*.zilla.com', 'http://mo-zilla.com');
|
||||
ok('*.amp.le.com', 'http://amp-le.com');
|
||||
ok('*.amp.le.com', 'http://examp.le.com');
|
||||
};
|
||||
|
||||
exports.testMatchPatternErrors = function(test) {
|
||||
|
|
|
@ -556,7 +556,7 @@ exports.testAttachToTabsOnly = function(test) {
|
|||
openToplevelWindow();
|
||||
}
|
||||
else {
|
||||
openBrowserIframe();
|
||||
openBrowserIframe();
|
||||
}
|
||||
}, false);
|
||||
element.setAttribute('src', 'data:text/html;charset=utf-8,foo');
|
||||
|
@ -751,7 +751,7 @@ exports.testPageModCssList = function(test) {
|
|||
"data:text/css;charset=utf-8,div { border: 1px solid black; }",
|
||||
"data:text/css;charset=utf-8,div { border: 10px solid black; }",
|
||||
// Highlight evaluation order between contentStylesheet & contentStylesheetFile
|
||||
"data:text/cs;charset=utf-8s,div { height: 1000px; }",
|
||||
"data:text/css;charset=utf-8s,div { height: 1000px; }",
|
||||
// Highlight precedence between the author and user style sheet
|
||||
"data:text/css;charset=utf-8,div { width: 200px; max-width: 640px!important}",
|
||||
],
|
||||
|
@ -779,13 +779,13 @@ exports.testPageModCssList = function(test) {
|
|||
test.assertEqual(
|
||||
style.width,
|
||||
"320px",
|
||||
"PageMod author/user style sheet precedence works"
|
||||
"PageMod add-on author/page author style sheet precedence works"
|
||||
);
|
||||
|
||||
test.assertEqual(
|
||||
style.maxWidth,
|
||||
"640px",
|
||||
"PageMod author/user style sheet precedence with !important works"
|
||||
"480px",
|
||||
"PageMod add-on author/page author style sheet precedence with !important works"
|
||||
);
|
||||
|
||||
done();
|
||||
|
|
|
@ -15,6 +15,7 @@ const { defer } = require('sdk/core/promise');
|
|||
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
|
||||
const { getWindow } = require('sdk/panel/window');
|
||||
const { pb } = require('./private-browsing/helper');
|
||||
const { URL } = require('sdk/url');
|
||||
|
||||
const SVG_URL = self.data.url('mofo_logo.SVG');
|
||||
|
||||
|
@ -125,7 +126,8 @@ exports["test Document Reload"] = function(assert, done) {
|
|||
"</script>";
|
||||
let messageCount = 0;
|
||||
let panel = Panel({
|
||||
contentURL: "data:text/html;charset=utf-8," + encodeURIComponent(content),
|
||||
// using URL here is intentional, see bug 859009
|
||||
contentURL: URL("data:text/html;charset=utf-8," + encodeURIComponent(content)),
|
||||
contentScript: "self.postMessage(window.location.href)",
|
||||
onMessage: function (message) {
|
||||
messageCount++;
|
||||
|
@ -516,19 +518,10 @@ exports["test Automatic Destroy"] = function(assert) {
|
|||
assert.pass("check automatic destroy");
|
||||
};
|
||||
|
||||
exports["test Wait For Init Then Show Then Destroy"] = makeEventOrderTest({
|
||||
test: function(assert, done, expect, panel) {
|
||||
expect('inited', function() { panel.show(); }).
|
||||
then('show', function() { panel.destroy(); }).
|
||||
then('hide', function() { done(); });
|
||||
}
|
||||
});
|
||||
|
||||
exports["test Show Then Wait For Init Then Destroy"] = makeEventOrderTest({
|
||||
exports["test Show Then Destroy"] = makeEventOrderTest({
|
||||
test: function(assert, done, expect, panel) {
|
||||
panel.show();
|
||||
expect('inited').
|
||||
then('show', function() { panel.destroy(); }).
|
||||
expect('show', function() { panel.destroy(); }).
|
||||
then('hide', function() { done(); });
|
||||
}
|
||||
});
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
const { Request } = require("sdk/request");
|
||||
const { pathFor } = require("sdk/system");
|
||||
const file = require("sdk/io/file");
|
||||
|
||||
const { URL } = require("sdk/url");
|
||||
const { extend } = require("sdk/util/object");
|
||||
const { Loader } = require("sdk/test/loader");
|
||||
const options = require("@test/options");
|
||||
|
||||
|
@ -46,14 +47,13 @@ exports.testOptionsValidator = function(test) {
|
|||
|
||||
exports.testContentValidator = function(test) {
|
||||
test.waitUntilDone();
|
||||
Request({
|
||||
runMultipleURLs(null, test, {
|
||||
url: "data:text/html;charset=utf-8,response",
|
||||
content: { 'key1' : null, 'key2' : 'some value' },
|
||||
onComplete: function(response) {
|
||||
test.assertEqual(response.text, "response?key1=null&key2=some+value");
|
||||
test.done();
|
||||
}
|
||||
}).get();
|
||||
});
|
||||
};
|
||||
|
||||
// This is a request to a file that exists.
|
||||
|
@ -82,15 +82,14 @@ exports.testStatus404 = function (test) {
|
|||
var srv = startServerAsync(port, basePath);
|
||||
|
||||
test.waitUntilDone();
|
||||
Request({
|
||||
runMultipleURLs(srv, test, {
|
||||
// the following URL doesn't exist
|
||||
url: "http://localhost:" + port + "/test-request-404.txt",
|
||||
onComplete: function (response) {
|
||||
test.assertEqual(response.status, 404);
|
||||
test.assertEqual(response.statusText, "Not Found");
|
||||
srv.stop(function() test.done());
|
||||
}
|
||||
}).get();
|
||||
});
|
||||
}
|
||||
|
||||
// a simple file with a known header
|
||||
|
@ -106,13 +105,12 @@ exports.testKnownHeader = function (test) {
|
|||
prepareFile(headerBasename, headerContent);
|
||||
|
||||
test.waitUntilDone();
|
||||
Request({
|
||||
runMultipleURLs(srv, test, {
|
||||
url: "http://localhost:" + port + "/test-request-headers.txt",
|
||||
onComplete: function (response) {
|
||||
test.assertEqual(response.headers["x-jetpack-header"], "Jamba Juice");
|
||||
srv.stop(function() test.done());
|
||||
}
|
||||
}).get();
|
||||
});
|
||||
}
|
||||
|
||||
// complex headers
|
||||
|
@ -132,15 +130,14 @@ exports.testComplexHeader = function (test) {
|
|||
}
|
||||
|
||||
test.waitUntilDone();
|
||||
Request({
|
||||
runMultipleURLs(srv, test, {
|
||||
url: "http://localhost:" + port + "/test-request-complex-headers.sjs",
|
||||
onComplete: function (response) {
|
||||
for (k in headers) {
|
||||
test.assertEqual(response.headers[k], headers[k]);
|
||||
}
|
||||
srv.stop(function() test.done());
|
||||
}
|
||||
}).get();
|
||||
});
|
||||
}
|
||||
|
||||
// Force Allow Third Party cookies
|
||||
|
@ -199,13 +196,12 @@ exports.testSimpleJSON = function (test) {
|
|||
prepareFile(basename, JSON.stringify(json));
|
||||
|
||||
test.waitUntilDone();
|
||||
Request({
|
||||
runMultipleURLs(srv, test, {
|
||||
url: "http://localhost:" + port + "/" + basename,
|
||||
onComplete: function (response) {
|
||||
assertDeepEqual(test, response.json, json);
|
||||
srv.stop(function() test.done());
|
||||
}
|
||||
}).get();
|
||||
});
|
||||
}
|
||||
|
||||
exports.testInvalidJSON = function (test) {
|
||||
|
@ -214,13 +210,26 @@ exports.testInvalidJSON = function (test) {
|
|||
prepareFile(basename, '"this": "isn\'t JSON"');
|
||||
|
||||
test.waitUntilDone();
|
||||
Request({
|
||||
runMultipleURLs(srv, test, {
|
||||
url: "http://localhost:" + port + "/" + basename,
|
||||
onComplete: function (response) {
|
||||
test.assertEqual(response.json, null);
|
||||
srv.stop(function() test.done());
|
||||
}
|
||||
}).get();
|
||||
});
|
||||
}
|
||||
|
||||
function runMultipleURLs (srv, test, options) {
|
||||
let urls = [options.url, URL(options.url)];
|
||||
let cb = options.onComplete;
|
||||
let ran = 0;
|
||||
let onComplete = function (res) {
|
||||
cb(res);
|
||||
if (++ran === urls.length)
|
||||
srv ? srv.stop(function () test.done()) : test.done();
|
||||
}
|
||||
urls.forEach(function (url) {
|
||||
Request(extend(options, { url: url, onComplete: onComplete })).get();
|
||||
});
|
||||
}
|
||||
|
||||
// All tests below here require a network connection. They will be commented out
|
||||
|
|
|
@ -28,7 +28,7 @@ const { setTimeout } = require("sdk/timers");
|
|||
const { Cu } = require("chrome");
|
||||
const { merge } = require("sdk/util/object");
|
||||
const { isPrivate } = require("sdk/private-browsing");
|
||||
|
||||
const events = require("sdk/system/events");
|
||||
// General purpose utility functions
|
||||
|
||||
/**
|
||||
|
@ -161,10 +161,16 @@ function hideAndShowFrame(window) {
|
|||
|
||||
Cu.forceGC();
|
||||
|
||||
setTimeout(function(){
|
||||
iframe.style.display = "";
|
||||
setTimeout(function() {
|
||||
events.on("document-shown", function shown(event) {
|
||||
if (iframe.contentWindow !== event.subject.defaultView)
|
||||
return;
|
||||
|
||||
setTimeout(resolve, 500, window);
|
||||
events.off("document-shown", shown);
|
||||
setTimeout(resolve, 0, window);
|
||||
}, true);
|
||||
|
||||
iframe.style.display = "";
|
||||
}, 0)
|
||||
|
||||
return promise;
|
||||
|
@ -828,6 +834,8 @@ exports["test Selection Listener on frame"] = function(assert, done) {
|
|||
|
||||
selection.once("select", function() {
|
||||
assert.equal(selection.text, "fo");
|
||||
close();
|
||||
loader.unload();
|
||||
done();
|
||||
});
|
||||
|
||||
|
@ -836,8 +844,7 @@ exports["test Selection Listener on frame"] = function(assert, done) {
|
|||
then(getFrameWindow).
|
||||
then(selectContentFirstDiv).
|
||||
then(dispatchSelectionEvent).
|
||||
then(close).
|
||||
then(loader.unload, assert.fail);
|
||||
then(null, assert.fail);
|
||||
};
|
||||
|
||||
exports["test Textarea onSelect Listener on frame"] = function(assert, done) {
|
||||
|
@ -846,6 +853,8 @@ exports["test Textarea onSelect Listener on frame"] = function(assert, done) {
|
|||
|
||||
selection.once("select", function() {
|
||||
assert.equal(selection.text, "noodles");
|
||||
close();
|
||||
loader.unload();
|
||||
done();
|
||||
});
|
||||
|
||||
|
@ -854,8 +863,7 @@ exports["test Textarea onSelect Listener on frame"] = function(assert, done) {
|
|||
then(getFrameWindow).
|
||||
then(selectTextarea).
|
||||
then(dispatchOnSelectEvent).
|
||||
then(close).
|
||||
then(loader.unload, assert.fail);
|
||||
then(null, assert.fail);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -191,7 +191,7 @@ exports["test emit to nsIObserverService observers"] = function(assert) {
|
|||
"event.subject is notification subject");
|
||||
assert.equal(lastData, customData, "event.data is notification data");
|
||||
|
||||
nsIObserverService.removeObserver(nsIObserver, topic, false);
|
||||
nsIObserverService.removeObserver(nsIObserver, topic);
|
||||
|
||||
events.emit(topic, { data: "more data" });
|
||||
|
||||
|
|
|
@ -255,6 +255,12 @@ exports.testIsInvalidURI = function (test) {
|
|||
});
|
||||
};
|
||||
|
||||
exports.testURLFromURL = function(test) {
|
||||
let aURL = url.URL('http://mozilla.org');
|
||||
let bURL = url.URL(aURL);
|
||||
test.assertEqual(aURL.toString(), bURL.toString(), 'Making a URL from a URL works');
|
||||
};
|
||||
|
||||
function validURIs() {
|
||||
return [
|
||||
'http://foo.com/blah_blah',
|
||||
|
|
Загрузка…
Ссылка в новой задаче