Bug 861277 - Uplift addon-sdk's integration branch

This commit is contained in:
Wes Kocher 2013-04-13 17:25:38 -07:00
parent 14ff153bf9
commit 492f5b7afa
51 changed files with 1698 additions and 695 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +0,0 @@
{
"id": "test-panel"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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