Bug 1040336 - Uplift Add-on SDK to Firefox r=me

--HG--
rename : addon-sdk/source/test/fixtures/css-include-file.css => addon-sdk/source/test/fixtures/include-file.css
This commit is contained in:
Erik Vold 2014-07-17 13:54:21 -07:00
parent 9788f0c902
commit 4aaca6c799
70 changed files with 6705 additions and 274 deletions

View File

@ -0,0 +1,3 @@
# Actor REPL
Simple REPL for a Firefox debugging protocol.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,264 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
}
.CodeMirror-scroll {
/* Set scrolling behaviour here */
overflow: auto;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* CURSOR */
.CodeMirror div.CodeMirror-cursor {
border-left: 1px solid black;
z-index: 3;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
width: auto;
border: 0;
background: #7e7;
z-index: 1;
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
.cm-tab { display: inline-block; }
.CodeMirror-ruler {
border-left: 1px solid #ccc;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable {color: black;}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3 {color: #085;}
.cm-s-default .cm-property {color: black;}
.cm-s-default .cm-operator {color: black;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
line-height: 1;
position: relative;
overflow: hidden;
background: white;
color: black;
}
.CodeMirror-scroll {
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actuall scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
padding-bottom: 30px;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
-moz-box-sizing: content-box;
box-sizing: content-box;
padding-bottom: 30px;
margin-bottom: -32px;
display: inline-block;
/* Hack to make IE7 behave */
*zoom:1;
*display:inline;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-lines {
cursor: text;
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
overflow: auto;
}
.CodeMirror-widget {}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: hidden;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-measure pre { position: static; }
.CodeMirror div.CodeMirror-cursor {
position: absolute;
visibility: hidden;
border-right: none;
width: 0;
}
.CodeMirror-focused div.CodeMirror-cursor {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.cm-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
.CodeMirror span { *vertical-align: text-bottom; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursor {
visibility: hidden;
}
}

View File

@ -0,0 +1,108 @@
<!-- 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/. -->
<html>
<head>
<link rel="stylesheet" href="./codemirror.css">
<link rel="stylesheet" href="./main.css">
<script src="./codemirror-compressed.js"></script>
<views>
<section class="task cm-s-default">
<pre class="request "></pre>
<pre class="response"><span class="one"></span><span class="two"></span><span class="three"></span></pre>
</section>
</views>
</head>
<body>
<pre class="input"></pre>
</body>
<script>
function debounce(fn, ms) {
var id
return function(...args) {
clearTimeout(id)
id = setTimeout(fn, ms, ...args)
}
}
function Try(fn) {
return function(...args) {
try { return fn(...args) }
catch (error) { return null }
}
}
var parse = Try(JSON.parse)
function send(editor) {
var input = editor.getWrapperElement().parentNode
var code = editor.getValue().trim()
var packet = parse(code)
if (packet) {
var task = document.querySelector("views .task").cloneNode(true)
var request = task.querySelector(".request")
var response = task.querySelector(".response")
input.parentNode.insertBefore(task, input)
CodeMirror.runMode(JSON.stringify(packet, 2, 2),
"application/json",
request)
response.classList.add("pending")
editor.setValue("")
document.activeElement.scrollIntoView()
port.postMessage(packet);
}
}
var editor = CodeMirror(document.querySelector(".input"), {
autofocus: true,
mode: "application/json",
matchBrackets: true,
value: '{"to": "root", "type": "requestTypes"}',
extraKeys: {"Cmd-Enter": send,
"Ctrl-Enter": send}
});
editor.on("change", debounce(function(editor) {
var input = editor.getWrapperElement().parentNode;
if (parse(editor.getValue().trim()))
input.classList.remove("invalid")
else
input.classList.add("invalid")
}, 800))
</script>
<script>
window.addEventListener("message", event => {
console.log("REPL", event);
window.port = event.ports[0];
port.onmessage = onMessage;
});
var onMessage = (event) => {
var packet = event.data;
var code = JSON.stringify(packet, 2, 2);
var input = editor.getWrapperElement().parentNode;
var response = document.querySelector(".task .response.pending")
if (!response) {
message = document.querySelector("views .task").cloneNode(true)
response = message.querySelector(".response")
response.classList.add("message")
input.parentNode.insertBefore(message, input);
}
if (packet.error)
response.classList.add("error");
CodeMirror.runMode(code, "application/json", response);
response.classList.remove("pending");
document.activeElement.scrollIntoView()
};
</script>
</html>

View File

@ -0,0 +1,117 @@
/* 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/. */
body
{
position: absolute;
width: 100%;
margin: 0;
padding: 0;
background: white;
}
pre
{
margin: 0;
}
section
{
border-top: 1px solid rgba(150, 150, 150, 0.5);
}
.CodeMirror {
height: auto;
}
.CodeMirror-scroll {
overflow-y: hidden;
overflow-x: auto;
}
.request,
.response,
.input
{
border-left: 5px solid;
padding-left: 10px;
}
.request:not(:empty),
.response.pending
{
padding: 5px;
}
.input
{
padding-left: 6px;
border-color: lightgreen;
}
.input.invalid
{
border-color: orange;
}
.request
{
border-color: lightgrey;
}
.response
{
border-color: grey;
}
.response.error
{
border-color: red;
}
.response.message
{
border-color: lightblue;
}
.response .one,
.response .two,
.response .three
{
width: 0;
height: auto;
}
.response.pending .one,
.response.pending .two,
.response.pending .three
{
width: 10px;
height: 10px;
background-color: rgba(150, 150, 150, 0.5);
border-radius: 100%;
display: inline-block;
animation: bouncedelay 1.4s infinite ease-in-out;
/* Prevent first frame from flickering when animation starts */
animation-fill-mode: both;
}
.response.pending .one
{
animation-delay: -0.32s;
}
.response.pending .two
{
animation-delay: -0.16s;
}
@keyframes bouncedelay {
0%, 80%, 100% {
transform: scale(0.0);
} 40% {
transform: scale(1.0);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Panel } = require("dev/panel");
const { Tool } = require("dev/toolbox");
const { Class } = require("sdk/core/heritage");
const REPLPanel = Class({
extends: Panel,
label: "Actor REPL",
tooltip: "Firefox debugging protocol REPL",
icon: "./robot.png",
url: "./index.html",
setup: function({debuggee}) {
this.debuggee = debuggee;
},
dispose: function() {
this.debuggee = null;
},
onReady: function() {
console.log("repl panel document is interactive");
this.debuggee.start();
this.postMessage("RDP", [this.debuggee]);
},
onLoad: function() {
console.log("repl panel document is fully loaded");
}
});
exports.REPLPanel = REPLPanel;
const replTool = new Tool({
panels: { repl: REPLPanel }
});

View File

@ -0,0 +1,10 @@
{
"name": "actor-repl",
"id": "@actor-repl",
"title": "Actor REPL",
"description": "Actor REPL",
"version": "0.0.1",
"author": "Irakli Gozalishvili",
"main": "./index.js",
"license": "MPL 2.0"
}

View File

@ -0,0 +1,816 @@
/* 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/. */
(function(exports) {
"use strict";
var describe = Object.getOwnPropertyDescriptor;
var Class = fields => {
var constructor = fields.constructor || function() {};
var ancestor = fields.extends || Object;
var descriptor = {};
for (var key of Object.keys(fields))
descriptor[key] = describe(fields, key);
var prototype = Object.create(ancestor.prototype, descriptor);
constructor.prototype = prototype;
prototype.constructor = constructor;
return constructor;
};
var bus = function Bus() {
var parser = new DOMParser();
return parser.parseFromString("<EventTarget/>", "application/xml").documentElement;
}();
var GUID = new WeakMap();
GUID.id = 0;
var guid = x => GUID.get(x);
var setGUID = x => {
GUID.set(x, ++ GUID.id);
};
var Emitter = Class({
extends: EventTarget,
constructor: function() {
this.setupEmitter();
},
setupEmitter: function() {
setGUID(this);
},
addEventListener: function(type, listener, capture) {
bus.addEventListener(type + "@" + guid(this),
listener, capture);
},
removeEventListener: function(type, listener, capture) {
bus.removeEventListener(type + "@" + guid(this),
listener, capture);
}
});
function dispatch(target, type, data) {
var event = new MessageEvent(type + "@" + guid(target), {
bubbles: true,
cancelable: false,
data: data
});
bus.dispatchEvent(event);
}
var supervisedWorkers = new WeakMap();
var supervised = supervisor => {
if (!supervisedWorkers.has(supervisor)) {
supervisedWorkers.set(supervisor, new Map());
supervisor.connection.addActorPool(supervisor);
}
return supervisedWorkers.get(supervisor);
};
var Supervisor = Class({
extends: Emitter,
constructor: function(...params) {
this.setupEmitter(...params);
this.setupSupervisor(...params);
},
Supervisor: function(connection) {
this.connection = connection;
},
/**
* Return the parent pool for this client.
*/
supervisor: function() {
return this.connection.poolFor(this.actorID);
},
/**
* Override this if you want actors returned by this actor
* to belong to a different actor by default.
*/
marshallPool: function() { return this; },
/**
* Add an actor as a child of this pool.
*/
supervise: function(actor) {
if (!actor.actorID)
actor.actorID = this.connection.allocID(actor.actorPrefix ||
actor.typeName);
supervised(this).set(actor.actorID, actor);
return actor;
},
/**
* Remove an actor as a child of this pool.
*/
abandon: function(actor) {
supervised(this).delete(actor.actorID);
},
// true if the given actor ID exists in the pool.
has: function(actorID) {
return supervised(this).has(actorID);
},
// Same as actor, should update debugger connection to use 'actor'
// and then remove this.
get: function(actorID) {
return supervised(this).get(actorID);
},
actor: function(actorID) {
return supervised(this).get(actorID);
},
isEmpty: function() {
return supervised(this).size === 0;
},
/**
* For getting along with the debugger server pools, should be removable
* eventually.
*/
cleanup: function() {
this.destroy();
},
destroy: function() {
var supervisor = this.supervisor();
if (supervisor)
supervisor.abandon(this);
for (var actor of supervised(this).values()) {
if (actor !== this) {
var destroy = actor.destroy;
// Disconnect destroy while we're destroying in case of (misbehaving)
// circular ownership.
if (destroy) {
actor.destroy = null;
destroy.call(actor);
actor.destroy = destroy;
}
}
}
this.connection.removeActorPool(this);
supervised(this).clear();
}
});
var mailbox = new WeakMap();
var clientRequests = new WeakMap();
var inbox = client => mailbox.get(client).inbox;
var outbox = client => mailbox.get(client).outbox;
var requests = client => clientRequests.get(client);
var Receiver = Class({
receive: function(packet) {
if (packet.error)
this.reject(packet.error);
else
this.resolve(this.read(packet));
}
});
var Connection = Class({
constructor: function() {
// Queue of the outgoing messages.
this.outbox = [];
// Map of pending requests.
this.pending = new Map();
this.pools = new Set();
},
isConnected: function() {
return !!this.port
},
connect: function(port) {
this.port = port;
port.addEventListener("message", this);
port.start();
this.flush();
},
addPool: function(pool) {
this.pools.add(pool);
},
removePool: function(pool) {
this.pools.delete(pool);
},
poolFor: function(id) {
for (let pool of this.pools.values()) {
if pool.has(id)
return pool;
}
},
get: function(id) {
var pool = this.poolFor(id);
return pool && pool.get(id);
},
disconnect: function() {
this.port.stop();
this.port = null;
for (var request of this.pending.values()) {
request.catch(new Error("Connection closed"));
}
this.pending.clear();
var requests = this.outbox.splice(0);
for (var request of request) {
requests.catch(new Error("Connection closed"));
}
},
handleEvent: function(event) {
this.receive(event.data);
},
flush: function() {
if (this.isConnected()) {
for (var request of this.outbox) {
if (!this.pending.has(request.to)) {
this.outbox.splice(this.outbox.indexOf(request), 1);
this.pending.set(request.to, request);
this.send(request.packet);
}
}
}
},
send: function(packet) {
this.port.postMessage(packet);
},
request: function(packet) {
return new Promise(function(resolve, reject) {
this.outbox.push({
to: packet.to,
packet: packet,
receive: resolve,
catch: reject
});
this.flush();
});
},
receive: function(packet) {
var { from, type, why } = packet;
var receiver = this.pending.get(from);
if (!receiver) {
console.warn("Unable to handle received packet", data);
} else {
this.pending.delete(from);
if (packet.error)
receiver.catch(packet.error);
else
receiver.receive(packet);
}
this.flush();
},
});
/**
* Base class for client-side actor fronts.
*/
var Client = Class({
extends: Supervisor,
constructor: function(from=null, detail=null, connection=null) {
this.Client(from, detail, connection);
},
Client: function(form, detail, connection) {
this.Supervisor(connection);
if (form) {
this.actorID = form.actor;
this.from(form, detail);
}
},
connect: function(port) {
this.connection = new Connection(port);
},
actorID: null,
actor: function() {
return this.actorID;
},
/**
* Update the actor from its representation.
* Subclasses should override this.
*/
form: function(form) {
},
/**
* Method is invokeid when packet received constitutes an
* event. By default such packets are demarshalled and
* dispatched on the client instance.
*/
dispatch: function(packet) {
},
/**
* Method is invoked when packet is returned in response to
* a request. By default respond delivers response to a first
* request in a queue.
*/
read: function(input) {
throw new TypeError("Subclass must implement read method");
},
write: function(input) {
throw new TypeError("Subclass must implement write method");
},
respond: function(packet) {
var [resolve, reject] = requests(this).shift();
if (packet.error)
reject(packet.error);
else
resolve(this.read(packet));
},
receive: function(packet) {
if (this.isEventPacket(packet)) {
this.dispatch(packet);
}
else if (requests(this).length) {
this.respond(packet);
}
else {
this.catch(packet);
}
},
send: function(packet) {
Promise.cast(packet.to || this.actor()).then(id => {
packet.to = id;
this.connection.send(packet);
})
},
request: function(packet) {
return this.connection.request(packet);
}
});
var Destructor = method => {
return function(...args) {
return method.apply(this, args).then(result => {
this.destroy();
return result;
});
};
};
var Profiled = (method, id) => {
return function(...args) {
var start = new Date();
return method.apply(this, args).then(result => {
var end = new Date();
this.telemetry.add(id, +end - start);
return result;
});
};
};
var Method = (request, response) => {
return response ? new BidirectionalMethod(request, response) :
new UnidirecationalMethod(request);
};
var UnidirecationalMethod = request => {
return function(...args) {
var packet = request.write(args, this);
this.connection.send(packet);
return Promise.resolve(void(0));
};
};
var BidirectionalMethod = (request, response) => {
return function(...args) {
var packet = request.write(args, this);
return this.connection.request(packet).then(packet => {
return response.read(packet, this);
});
};
};
Client.from = ({category, typeName, methods, events}) => {
var proto = {
constructor: function(...args) {
this.Client(...args);
},
extends: Client,
name: typeName
};
methods.forEach(({telemetry, request, response, name, oneway, release}) => {
var [reader, writer] = oneway ? [, new Request(request)] :
[new Request(request), new Response(response)];
var method = new Method(request, response);
var profiler = telemetry ? new Profiler(method) : method;
var destructor = release ? new Destructor(profiler) : profiler;
proto[name] = destructor;
});
return Class(proto);
};
var defineType = (client, descriptor) => {
var type = void(0)
if (typeof(descriptor) === "string") {
if (name.indexOf(":") > 0)
type = makeCompoundType(descriptor);
else if (name.indexOf("#") > 0)
type = new ActorDetail(descriptor);
else if (client.specification[descriptor])
type = makeCategoryType(client.specification[descriptor]);
} else {
type = makeCategoryType(descriptor);
}
if (type)
client.types.set(type.name, type);
else
throw TypeError("Invalid type: " + descriptor);
};
var makeCompoundType = name => {
var index = name.indexOf(":");
var [baseType, subType] = [name.slice(0, index), parts.slice(1)];
return baseType === "array" ? new ArrayOf(subType) :
baseType === "nullable" ? new Maybe(subType) :
null;
};
var makeCategoryType = (descriptor) => {
var { category } = descriptor;
return category === "dict" ? new Dictionary(descriptor) :
category === "actor" ? new Actor(descriptor) :
null;
};
var typeFor = (client, type="primitive") => {
if (!client.types.has(type))
defineType(client, type);
return client.types.get(type);
};
var Client = Class({
constructor: function() {
},
setupTypes: function(specification) {
this.specification = specification;
this.types = new Map();
},
read: function(input, type) {
return typeFor(this, type).read(input, this);
},
write: function(input, type) {
return typeFor(this, type).write(input, this);
}
});
var Type = Class({
get name() {
return this.category ? this.category + ":" + this.type :
this.type;
},
read: function(input, client) {
throw new TypeError("`Type` subclass must implement `read`");
},
write: function(input, client) {
throw new TypeError("`Type` subclass must implement `write`");
}
});
var Primitve = Class({
extends: Type,
constuctor: function(type) {
this.type = type;
},
read: function(input, client) {
return input;
},
write: function(input, client) {
return input;
}
});
var Maybe = Class({
extends: Type,
category: "nullable",
constructor: function(type) {
this.type = type;
},
read: function(input, client) {
return input === null ? null :
input === void(0) ? void(0) :
client.read(input, this.type);
},
write: function(input, client) {
return input === null ? null :
input === void(0) ? void(0) :
client.write(input, this.type);
}
});
var ArrayOf = Class({
extends: Type,
category: "array",
constructor: function(type) {
this.type = type;
},
read: function(input, client) {
return input.map($ => client.read($, this.type));
},
write: function(input, client) {
return input.map($ => client.write($, this.type));
}
});
var Dictionary = Class({
exteds: Type,
category: "dict",
get name() { return this.type; },
constructor: function({typeName, specializations}) {
this.type = typeName;
this.types = specifications;
},
read: function(input, client) {
var output = {};
for (var key in input) {
output[key] = client.read(input[key], this.types[key]);
}
return output;
},
write: function(input, client) {
var output = {};
for (var key in input) {
output[key] = client.write(value, this.types[key]);
}
return output;
}
});
var Actor = Class({
exteds: Type,
category: "actor",
get name() { return this.type; },
constructor: function({typeName}) {
this.type = typeName;
},
read: function(input, client, detail) {
var id = value.actor;
var actor = void(0);
if (client.connection.has(id)) {
return client.connection.get(id).form(input, detail, client);
} else {
actor = Client.from(detail, client);
actor.actorID = id;
client.supervise(actor);
}
},
write: function(input, client, detail) {
if (input instanceof Actor) {
if (!input.actorID) {
client.supervise(input);
}
return input.from(detail);
}
return input.actorID;
}
});
var Root = Client.from({
"category": "actor",
"typeName": "root",
"methods": [
{"name": "listTabs",
"request": {},
"response": {
}
},
{"name": "listAddons"
},
{"name": "echo",
},
{"name": "protocolDescription",
}
]
});
var ActorDetail = Class({
extends: Actor,
constructor: function(name, actor, detail) {
this.detail = detail;
this.actor = actor;
},
read: function(input, client) {
this.actor.read(input, client, this.detail);
},
write: function(input, client) {
this.actor.write(input, client, this.detail);
}
});
var registeredLifetimes = new Map();
var LifeTime = Class({
extends: Type,
category: "lifetime",
constructor: function(lifetime, type) {
this.name = lifetime + ":" + type.name;
this.field = registeredLifetimes.get(lifetime);
},
read: function(input, client) {
return this.type.read(input, client[this.field]);
},
write: function(input, client) {
return this.type.write(input, client[this.field]);
}
});
var primitive = new Primitve("primitive");
var string = new Primitve("string");
var number = new Primitve("number");
var boolean = new Primitve("boolean");
var json = new Primitve("json");
var array = new Primitve("array");
var TypedValue = Class({
extends: Type,
constructor: function(name, type) {
this.TypedValue(name, type);
},
TypedValue: function(name, type) {
this.name = name;
this.type = type;
},
read: function(input, client) {
return this.client.read(input, this.type);
},
write: function(input, client) {
return this.client.write(input, this.type);
}
});
var Return = Class({
extends: TypedValue,
constructor: function(type) {
this.type = type
}
});
var Argument = Class({
extends: TypedValue,
constructor: function(...args) {
this.Argument(...args);
},
Argument: function(index, type) {
this.index = index;
this.TypedValue("argument[" + index + "]", type);
},
read: function(input, client, target) {
return target[this.index] = client.read(input, this.type);
}
});
var Option = Class({
extends: Argument,
constructor: function(...args) {
return this.Argument(...args);
},
read: function(input, client, target, name) {
var param = target[this.index] || (target[this.index] = {});
param[name] = input === void(0) ? input : client.read(input, this.type);
},
write: function(input, client, name) {
var value = input && input[name];
return value === void(0) ? value : client.write(value, this.type);
}
});
var Request = Class({
extends: Type,
constructor: function(template={}) {
this.type = template.type;
this.template = template;
this.params = findPlaceholders(template, Argument);
},
read: function(packet, client) {
var args = [];
for (var param of this.params) {
var {placeholder, path} = param;
var name = path[path.length - 1];
placeholder.read(getPath(packet, path), client, args, name);
// TODO:
// args[placeholder.index] = placeholder.read(query(packet, path), client);
}
return args;
},
write: function(input, client) {
return JSON.parse(JSON.stringify(this.template, (key, value) => {
return value instanceof Argument ? value.write(input[value.index],
client, key) :
value;
}));
}
});
var Response = Class({
extends: Type,
constructor: function(template={}) {
this.template = template;
var [x] = findPlaceholders(template, Return);
var {placeholder, path} = x;
this.return = placeholder;
this.path = path;
},
read: function(packet, client) {
var value = query(packet, this.path);
return this.return.read(value, client);
},
write: function(input, client) {
return JSON.parse(JSON.stringify(this.template, (key, value) => {
return value instanceof Return ? value.write(input) :
input
}));
}
});
// Returns array of values for the given object.
var values = object => Object.keys(object).map(key => object[key]);
// Returns [key, value] pairs for the given object.
var pairs = object => Object.keys(object).map(key => [key, object[key]]);
// Queries an object for the field nested with in it.
var query = (object, path) => path.reduce((object, entry) => object && object[entry],
object);
var Root = Client.from({
"category": "actor",
"typeName": "root",
"methods": [
{
"name": "echo",
"request": {
"string": { "_arg": 0, "type": "string" }
},
"response": {
"string": { "_retval": "string" }
}
},
{
"name": "listTabs",
"request": {},
"response": { "_retval": "tablist" }
},
{
"name": "actorDescriptions",
"request": {},
"response": { "_retval": "json" }
}
],
"events": {
"tabListChanged": {}
}
});
var Tab = Client.from({
"category": "dict",
"typeName": "tab",
"specifications": {
"title": "string",
"url": "string",
"outerWindowID": "number",
"console": "console",
"inspectorActor": "inspector",
"callWatcherActor": "call-watcher",
"canvasActor": "canvas",
"webglActor": "webgl",
"webaudioActor": "webaudio",
"styleSheetsActor": "stylesheets",
"styleEditorActor": "styleeditor",
"storageActor": "storage",
"gcliActor": "gcli",
"memoryActor": "memory",
"eventLoopLag": "eventLoopLag"
"trace": "trace", // missing
}
});
var tablist = Client.from({
"category": "dict",
"typeName": "tablist",
"specializations": {
"selected": "number",
"tabs": "array:tab"
}
});
})(this);

View File

@ -0,0 +1,50 @@
<!-- 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/. -->
<html>
<head>
<script src="resource://sdk/dev/volcan.js"></script>
<script src="./task.js"></script>
</head>
<body>
</body>
<script>
const wait = (target, type, capture) => new Promise((resolve, reject) => {
const listener = event => {
target.removeEventListener(type, listener, capture);
resolve(event);
};
target.addEventListener(type, listener, capture);
});
const display = message =>
document.body.innerHTML += message + "<br/>";
Task.spawn(function*() {
var event = yield wait(window, "message");
var port = event.ports[0];
display("Port received");
var root = yield volcan.connect(port);
display("Connected to a debugger");
var message = yield root.echo("hello")
display("Received echo for: " + message);
var list = yield root.listTabs();
display("You have " + list.tabs.length + " open tabs");
var activeTab = list.tabs[list.selected];
display("Your active tab url is: " + activeTab.url);
var sheets = yield activeTab.styleSheetsActor.getStyleSheets();
display("Page in active tab has " + sheets.length + " stylesheets");
});
</script>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,28 @@
/* 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/. */
(function(exports) {
"use strict";
const spawn = (task, ...args) => {
return new Promise((resolve, reject) => {
try {
const routine = task(...args);
const raise = error => routine.throw(error);
const step = data => {
const { done, value } = routine.next(data);
if (done)
resolve(value);
else
Promise.resolve(value).then(step, raise);
}
step();
} catch(error) {
reject(error);
}
});
}
exports.spawn = spawn;
})(Task = {});

View File

@ -0,0 +1,33 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Panel } = require("dev/panel");
const { Tool } = require("dev/toolbox");
const { Class } = require("sdk/core/heritage");
const LadybugPanel = Class({
extends: Panel,
label: "Ladybug",
tooltip: "Debug client example",
icon: "./plugin.png",
url: "./index.html",
setup: function({debuggee}) {
this.debuggee = debuggee;
},
dispose: function() {
delete this.debuggee;
},
onReady: function() {
this.debuggee.start();
this.postMessage("RDP", [this.debuggee]);
},
});
exports.LadybugPanel = LadybugPanel;
const ladybug = new Tool({
panels: { ladybug: LadybugPanel }
});

View File

@ -0,0 +1,10 @@
{
"name": "debug-client",
"id": "@debug-client",
"title": "Debug client",
"description": "Example debug client",
"version": "0.0.1",
"author": "Irakli Gozalishvili",
"main": "./index.js",
"license": "MPL 2.0"
}

View File

@ -0,0 +1,95 @@
/* 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 { Cu } = require("chrome");
const { Class } = require("../sdk/core/heritage");
const { MessagePort, MessageChannel } = require("../sdk/messaging");
const { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
const outputs = new WeakMap();
const inputs = new WeakMap();
const targets = new WeakMap();
const transports = new WeakMap();
const inputFor = port => inputs.get(port);
const outputFor = port => outputs.get(port);
const transportFor = port => transports.get(port);
const fromTarget = target => {
const debuggee = new Debuggee();
const { port1, port2 } = new MessageChannel();
inputs.set(debuggee, port1);
outputs.set(debuggee, port2);
targets.set(debuggee, target);
return debuggee;
};
exports.fromTarget = fromTarget;
const Debuggee = Class({
extends: MessagePort.prototype,
close: function() {
const server = transportFor(this);
if (server) {
transports.delete(this);
server.close();
}
outputFor(this).close();
},
start: function() {
const target = targets.get(this);
if (target.isLocalTab) {
// Since a remote protocol connection will be made, let's start the
// DebuggerServer here, once and for all tools.
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
transports.set(this, DebuggerServer.connectPipe());
}
// TODO: Implement support for remote connections (See Bug 980421)
else {
throw Error("Remote targets are not yet supported");
}
// pipe messages send to the debuggee to an actual
// server via remote debugging protocol transport.
inputFor(this).addEventListener("message", ({data}) =>
transportFor(this).send(data));
// pipe messages received from the remote debugging
// server transport onto the this debuggee.
transportFor(this).hooks = {
onPacket: packet => inputFor(this).postMessage(packet),
onClosed: () => inputFor(this).close()
};
inputFor(this).start();
outputFor(this).start();
},
postMessage: function(data) {
return outputFor(this).postMessage(data);
},
get onmessage() {
return outputFor(this).onmessage;
},
set onmessage(onmessage) {
outputFor(this).onmessage = onmessage;
},
addEventListener: function(...args) {
return outputFor(this).addEventListener(...args);
},
removeEventListener: function(...args) {
return outputFor(this).removeEventListener(...args);
}
});
exports.Debuggee = Debuggee;

View File

@ -0,0 +1,115 @@
/* 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";
(function({content, sendSyncMessage, addMessageListener, sendAsyncMessage}) {
const Cc = Components.classes;
const Ci = Components.interfaces;
const observerService = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
const channels = new Map();
const handles = new WeakMap();
// Takes remote port handle and creates a local one.
// also set's up a messaging channel between them.
// This is temporary workaround until Bug 914974 is fixed
// and port can be transfered through message manager.
const demarshal = (handle) => {
if (handle.type === "MessagePort") {
if (!channels.has(handle.id)) {
const channel = new content.MessageChannel();
channels.set(handle.id, channel);
handles.set(channel.port1, handle);
channel.port1.onmessage = onOutPort;
}
return channels.get(handle.id).port2;
}
return null;
};
const onOutPort = event => {
const handle = handles.get(event.target);
sendAsyncMessage("sdk/port/message", {
port: handle,
message: event.data
});
};
const onInPort = ({data}) => {
const channel = channels.get(data.port.id);
if (channel)
channel.port1.postMessage(data.message);
};
const onOutEvent = event =>
sendSyncMessage("sdk/event/" + event.type,
{ type: event.type,
data: event.data });
const onInMessage = (message) => {
const {type, data, origin, bubbles, cancelable, ports} = message.data;
const event = new content.MessageEvent(type, {
bubbles: bubbles,
cancelable: cancelable,
data: data,
origin: origin,
target: content,
source: content,
ports: ports.map(demarshal)
});
content.dispatchEvent(event);
};
const onReady = event => {
channels.clear();
};
addMessageListener("sdk/event/message", onInMessage);
addMessageListener("sdk/port/message", onInPort);
const observer = {
observe: (document, topic, data) => {
// When frame associated with message manager is removed from document `docShell`
// is set to `null` but observer is still kept alive. At this point accesing
// `content.document` throws "can't access dead object" exceptions. In order to
// avoid leaking observer and logged errors observer is going to be removed when
// `docShell` is set to `null`.
if (!docShell) {
observerService.removeObserver(observer, topic);
}
else if (document === content.document) {
if (topic === "content-document-interactive") {
sendAsyncMessage("sdk/event/ready", {
type: "ready",
readyState: document.readyState,
uri: document.documentURI
});
}
if (topic === "content-document-loaded") {
sendAsyncMessage("sdk/event/load", {
type: "load",
readyState: document.readyState,
uri: document.documentURI
});
}
if (topic === "content-page-hidden") {
channels.clear();
sendAsyncMessage("sdk/event/unload", {
type: "unload",
readyState: "uninitialized",
uri: document.documentURI
});
}
}
}
};
observerService.addObserver(observer, "content-document-interactive", false);
observerService.addObserver(observer, "content-document-loaded", false);
observerService.addObserver(observer, "content-page-hidden", false);
})(this);

View File

@ -0,0 +1,223 @@
/* 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 { Cu } = require("chrome");
const { Class } = require("../sdk/core/heritage");
const { curry } = require("../sdk/lang/functional");
const { EventTarget } = require("../sdk/event/target");
const { Disposable, setup, dispose } = require("../sdk/core/disposable");
const { emit, off, setListeners } = require("../sdk/event/core");
const { when } = require("../sdk/event/utils");
const { getFrameElement } = require("../sdk/window/utils");
const { contract, validate } = require("../sdk/util/contract");
const { data: { url: resolve }} = require("../sdk/self");
const { identify } = require("../sdk/ui/id");
const { isLocalURL, URL } = require("../sdk/url");
const { defer } = require("../sdk/core/promise");
const { encode } = require("../sdk/base64");
const { marshal, demarshal } = require("./ports");
const { fromTarget } = require("./debuggee");
const { removed } = require("../sdk/dom/events");
const { id: addonID } = require("../sdk/self");
const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html");
const FRAME_SCRIPT = module.uri.replace("/panel.js", "/frame-script.js");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const HTML_NS = "http://www.w3.org/1999/xhtml";
const makeID = name =>
("dev-panel-" + addonID + "-" + name).
split("/").join("-").
split(".").join("-").
split(" ").join("-").
replace(/[^A-Za-z0-9_\-]/g, "");
// Weak mapping between `Panel` instances and their frame's
// `nsIMessageManager`.
const managers = new WeakMap();
// Return `nsIMessageManager` for the given `Panel` instance.
const managerFor = x => managers.get(x);
// Weak mappinging between iframe's and their owner
// `Panel` instances.
const panels = new WeakMap();
const panelFor = frame => panels.get(frame);
// Weak mapping between panels and debugees they're targeting.
const debuggees = new WeakMap();
const debuggeeFor = panel => debuggees.get(panel);
const setAttributes = (node, attributes) => {
for (var key in attributes)
node.setAttribute(key, attributes[key]);
};
const onStateChange = ({target, data}) => {
const panel = panelFor(target);
panel.readyState = data.readyState;
emit(panel, data.type, { target: panel, type: data.type });
};
// port event listener on the message manager that demarshalls
// and forwards to the actual receiver. This is a workaround
// until Bug 914974 is fixed.
const onPortMessage = ({data, target}) => {
const port = demarshal(target, data.port);
if (port)
port.postMessage(data.message);
};
// When frame is removed from the toolbox destroy panel
// associated with it to release all the resources.
const onFrameRemove = frame => {
panelFor(frame).destroy();
};
const onFrameInited = frame => {
frame.style.visibility = "visible";
}
const inited = frame => new Promise(resolve => {
const { messageManager } = frame.frameLoader;
const listener = message => {
messageManager.removeMessageListener("sdk/event/ready", listener);
resolve(frame);
};
messageManager.addMessageListener("sdk/event/ready", listener);
});
const getTarget = ({target}) => target;
const Panel = Class({
extends: Disposable,
implements: [EventTarget],
get id() {
return makeID(this.name || this.label);
},
readyState: "uninitialized",
ready: function() {
const { readyState } = this;
const isReady = readyState === "complete" ||
readyState === "interactive";
return isReady ? Promise.resolve(this) :
when(this, "ready").then(getTarget);
},
loaded: function() {
const { readyState } = this;
const isLoaded = readyState === "complete";
return isLoaded ? Promise.resolve(this) :
when(this, "load").then(getTarget);
},
unloaded: function() {
const { readyState } = this;
const isUninitialized = readyState === "uninitialized";
return isUninitialized ? Promise.resolve(this) :
when(this, "unload").then(getTarget);
},
postMessage: function(data, ports) {
const manager = managerFor(this);
manager.sendAsyncMessage("sdk/event/message", {
type: "message",
bubbles: false,
cancelable: false,
data: data,
origin: this.url,
ports: ports.map(marshal(manager))
});
}
});
exports.Panel = Panel;
validate.define(Panel, contract({
label: {
is: ["string"],
msg: "The `option.label` must be a provided"
},
tooltip: {
is: ["string", "undefined"],
msg: "The `option.tooltip` must be a string"
},
icon: {
is: ["string"],
map: x => x && resolve(x),
ok: x => isLocalURL(x),
msg: "The `options.icon` must be a valid local URI."
},
url: {
map: x => resolve(x.toString()),
is: ["string"],
ok: x => isLocalURL(x),
msg: "The `options.url` must be a valid local URI."
}
}));
setup.define(Panel, (panel, {window, toolbox, url}) => {
// Hack: Given that iframe created by devtools API is no good for us,
// we obtain original iframe and replace it with the one that has
// desired configuration.
const original = getFrameElement(window);
const frame = original.cloneNode(true);
setAttributes(frame, {
"src": url,
"sandbox": "allow-scripts",
// It would be great if we could allow remote iframes for sandboxing
// panel documents in a content process, but for now platform implementation
// is buggy on linux so this is disabled.
// "remote": true,
"type": "content",
"transparent": true,
"seamless": "seamless"
});
original.parentNode.replaceChild(frame, original);
frame.style.visibility = "hidden";
// associate panel model with a frame view.
panels.set(frame, panel);
const debuggee = fromTarget(toolbox.target);
// associate debuggee with a panel.
debuggees.set(panel, debuggee);
// Setup listeners for the frame message manager.
const { messageManager } = frame.frameLoader;
messageManager.addMessageListener("sdk/event/ready", onStateChange);
messageManager.addMessageListener("sdk/event/load", onStateChange);
messageManager.addMessageListener("sdk/event/unload", onStateChange);
messageManager.addMessageListener("sdk/port/message", onPortMessage);
messageManager.loadFrameScript(FRAME_SCRIPT, false);
managers.set(panel, messageManager);
// destroy panel if frame is removed.
removed(frame).then(onFrameRemove);
// show frame when it is initialized.
inited(frame).then(onFrameInited);
// set listeners if there are ones defined on the prototype.
setListeners(panel, Object.getPrototypeOf(panel));
panel.setup({ debuggee: debuggee });
});
dispose.define(Panel, function(panel) {
debuggeeFor(panel).close();
debuggees.delete(panel);
managers.delete(panel);
panel.readyState = "destroyed";
panel.dispose();
});

View File

@ -0,0 +1,64 @@
/* 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"
};
// This module provides `marshal` and `demarshal` functions
// that can be used to send MessagePort's over `nsIFrameMessageManager`
// until Bug 914974 is fixed.
const { add, iterator } = require("../sdk/lang/weak-set");
const { curry } = require("../sdk/lang/functional");
let id = 0;
const ports = new WeakMap();
// Takes `nsIFrameMessageManager` and `MessagePort` instances
// and returns a handle representing given `port`. Messages
// received on given `port` will be forwarded to a message
// manager under `sdk/port/message` and messages like:
// { port: { type: "MessagePort", id: 2}, data: data }
// Where id is an identifier associated with a given `port`
// and `data` is an `event.data` received on port.
const marshal = curry((manager, port) => {
if (!ports.has(port)) {
id = id + 1;
const handle = {type: "MessagePort", id: id};
// Bind id to the given port
ports.set(port, handle);
// Obtain a weak reference to a port.
add(exports, port);
port.onmessage = event => {
manager.sendAsyncMessage("sdk/port/message", {
port: handle,
message: event.data
});
};
return handle;
}
return ports.get(port);
});
exports.marshal = marshal;
// Takes `nsIFrameMessageManager` instance and a handle returned
// `marshal(manager, port)` returning a `port` that was passed
// to it. Note that `port` may be GC-ed in which case returned
// value will be `null`.
const demarshal = curry((manager, {type, id}) => {
if (type === "MessagePort") {
for (let port of iterator(exports)) {
if (id === ports.get(port).id)
return port;
}
}
return null;
});
exports.demarshal = demarshal;

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/. */
"use strict";
module.metadata = {
"stability": "experimental"
};
const { Cu, Cc, Ci } = require("chrome");
const { Class } = require("../sdk/core/heritage");
const { Disposable, setup } = require("../sdk/core/disposable");
const { contract, validate } = require("../sdk/util/contract");
const { each, pairs, values } = require("../sdk/util/sequence");
const { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
// This is temporary workaround to allow loading of the developer tools client - volcan
// into a toolbox panel, this hack won't be necessary as soon as devtools patch will be
// shipped in nightly, after which it can be removed. Bug 1038517
const registerSDKURI = () => {
const ioService = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);
const resourceHandler = ioService.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
const uri = module.uri.replace("dev/toolbox.js", "");
resourceHandler.setSubstitution("sdk", ioService.newURI(uri, null, null));
};
registerSDKURI();
const Tool = Class({
extends: Disposable,
setup: function(params={}) {
const { panels } = validate(this, params);
this.panels = panels;
each(([key, Panel]) => {
const { url, label, tooltip, icon } = validate(Panel.prototype);
const { id } = Panel.prototype;
gDevTools.registerTool({
id: id,
url: "about:blank",
label: label,
tooltip: tooltip,
icon: icon,
isTargetSupported: target => target.isLocalTab,
build: (window, toolbox) => {
const panel = new Panel();
setup(panel, { window: window,
toolbox: toolbox,
url: url });
return panel.ready();
}
});
}, pairs(panels));
},
dispose: function() {
each(Panel => gDevTools.unregisterTool(Panel.prototype.id),
values(this.panels));
}
});
validate.define(Tool, contract({
panels: {
is: ["object", "undefined"]
}
}));
exports.Tool = Tool;

View File

@ -0,0 +1,38 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cu } = require("chrome");
const { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const { getActiveTab } = require("../sdk/tabs/utils");
const { getMostRecentBrowserWindow } = require("../sdk/window/utils");
const targetFor = target => {
target = target || getActiveTab(getMostRecentBrowserWindow());
return devtools.TargetFactory.forTab(target);
};
const getCurrentPanel = toolbox => toolbox.getCurrentPanel();
exports.getCurrentPanel = getCurrentPanel;
const openToolbox = (id, tab) => {
id = id.prototype.id || id.id || id;
return gDevTools.showToolbox(targetFor(tab), id);
};
exports.openToolbox = openToolbox;
const closeToolbox = tab => gDevTools.closeToolbox(targetFor(tab));
exports.closeToolbox = closeToolbox;
const getToolbox = tab => gDevTools.getToolbox(targetFor(tab));
exports.getToolbox = getToolbox;
const openToolboxPanel = (id, tab) => {
id = id.prototype.id || id.id || id;
return gDevTools.showToolbox(targetFor(tab), id).then(getCurrentPanel);
};
exports.openToolboxPanel = openToolboxPanel;

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,7 @@ module.metadata = {
};
const { Cc, Ci } = require('chrome');
const { isNative } = require('@loader/options');
const { descriptor, Sandbox, evaluate, main, resolveURI } = require('toolkit/loader');
const { once } = require('../system/events');
const { exit, env, staticArgs } = require('../system');
@ -104,7 +105,8 @@ function startup(reason, options) {
require('../l10n/loader').
load(rootURI).
then(null, function failure(error) {
console.info("Error while loading localization: " + error.message);
if (!isNative)
console.info("Error while loading localization: " + error.message);
}).
then(function onLocalizationReady(data) {
// Exports data to a pseudo module so that api-utils/l10n/core

View File

@ -9,81 +9,50 @@ module.metadata = {
};
const { EventEmitter } = require('../deprecated/events');
const { validateOptions } = require('../deprecated/api-utils');
const { isValidURI, URL } = require('../url');
const { isValidURI, isLocalURL, URL } = require('../url');
const file = require('../io/file');
const { contract } = require('../util/contract');
const { isString, instanceOf } = require('../lang/type');
const { isString, isNil, instanceOf } = require('../lang/type');
const { validateOptions,
string, array, object, either, required } = require('../deprecated/api-utils');
const LOCAL_URI_SCHEMES = ['resource', 'data'];
const isJSONable = (value) => {
try {
JSON.parse(JSON.stringify(value));
} catch (e) {
return false;
}
return true;
};
// Returns `null` if `value` is `null` or `undefined`, otherwise `value`.
function ensureNull(value) value == null ? null : value
const isValidScriptFile = (value) =>
(isString(value) || instanceOf(value, URL)) && isLocalURL(value);
// map of property validations
const valid = {
contentURL: {
map: function(url) !url ? ensureNull(url) : url.toString(),
is: ['undefined', 'null', 'string'],
ok: function (url) {
if (url === null)
return true;
return isValidURI(url);
},
is: either(string, object),
ok: url => isNil(url) || isLocalURL(url) || isValidURI(url),
msg: 'The `contentURL` option must be a valid URL.'
},
contentScriptFile: {
is: ['undefined', 'null', 'string', 'array', 'object'],
map: ensureNull,
ok: function(value) {
if (value === null)
return true;
value = [].concat(value);
// Make sure every item is a string or an
// URL instance, and also a local file URL.
return value.every(function (item) {
if (!isString(item) && !(item instanceof URL))
return false;
try {
return ~LOCAL_URI_SCHEMES.indexOf(URL(item).scheme);
}
catch(e) {
return false;
}
});
},
is: either(string, object, array),
ok: value => isNil(value) || [].concat(value).every(isValidScriptFile),
msg: 'The `contentScriptFile` option must be a local URL or an array of URLs.'
},
contentScript: {
is: ['undefined', 'null', 'string', 'array'],
map: ensureNull,
ok: function(value) {
return !Array.isArray(value) || value.every(
function(item) { return typeof item === 'string' }
);
},
is: either(string, array),
ok: value => isNil(value) || [].concat(value).every(isString),
msg: 'The `contentScript` option must be a string or an array of strings.'
},
contentScriptWhen: {
is: ['string'],
ok: function(value) { return ~['start', 'ready', 'end'].indexOf(value) },
map: function(value) {
return value || 'end';
},
is: required(string),
map: value => value || 'end',
ok: value => ~['start', 'ready', 'end'].indexOf(value),
msg: 'The `contentScriptWhen` option must be either "start", "ready" or "end".'
},
contentScriptOptions: {
ok: function(value) {
if ( value === undefined ) { return true; }
try { JSON.parse( JSON.stringify( value ) ); } catch(e) { return false; }
return true;
},
map: function(value) 'undefined' === getTypeOf(value) ? null : value,
ok: value => isNil(value) || isJSONable(value),
msg: 'The contentScriptOptions should be a jsonable value.'
}
};

View File

@ -20,7 +20,7 @@ const { merge } = require('../util/object');
const { getTabForContentWindow } = require('../tabs/utils');
const { getInnerId } = require('../window/utils');
const { PlainTextConsole } = require('../console/plain-text');
const { data } = require('../self');
// WeakMap of sandboxes so we can access private values
const sandboxes = new WeakMap();
@ -247,9 +247,12 @@ const WorkerSandbox = Class({
// The order of `contentScriptFile` and `contentScript` evaluation is
// intentional, so programs can load libraries like jQuery from script URLs
// and use them in scripts.
let contentScriptFile = ('contentScriptFile' in worker) ? worker.contentScriptFile
let contentScriptFile = ('contentScriptFile' in worker)
? worker.contentScriptFile
: null,
contentScript = ('contentScript' in worker) ? worker.contentScript : null;
contentScript = ('contentScript' in worker)
? worker.contentScript
: null;
if (contentScriptFile)
importScripts.apply(null, [this].concat(contentScriptFile));
@ -285,7 +288,8 @@ exports.WorkerSandbox = WorkerSandbox;
function importScripts (workerSandbox, ...urls) {
let { worker, sandbox } = modelFor(workerSandbox);
for (let i in urls) {
let contentScriptFile = urls[i];
let contentScriptFile = data.url(urls[i]);
try {
let uri = URL(contentScriptFile);
if (uri.scheme === 'resource')

View File

@ -8,13 +8,14 @@ module.metadata = {
};
let { merge } = require('../util/object');
let assetsURI = require('../self').data.url();
let { data } = require('../self');
let assetsURI = data.url();
let isArray = Array.isArray;
let method = require('../../method/core');
function isAddonContent({ contentURL }) {
return typeof(contentURL) === 'string' && contentURL.indexOf(assetsURI) === 0;
}
const isAddonContent = ({ contentURL }) =>
contentURL && data.url(contentURL).startsWith(assetsURI);
exports.isAddonContent = isAddonContent;
function hasContentScript({ contentScript, contentScriptFile }) {

View File

@ -25,6 +25,7 @@ const { EventTarget } = require("./event/target");
const { emit } = require('./event/core');
const { when } = require('./system/unload');
const selection = require('./selection');
const { contract: loaderContract } = require('./content/loader');
// All user items we add have this class.
const ITEM_CLASS = "addon-context-menu-item";
@ -257,7 +258,7 @@ function populateCallbackNodeData(node) {
}
data.selectionText = selection.text;
data.srcURL = node.src || null;
data.value = node.value || null;
@ -265,7 +266,7 @@ function populateCallbackNodeData(node) {
data.linkURL = node.href || null;
node = node.parentNode;
}
return data;
}
@ -298,30 +299,11 @@ let baseItemRules = {
msg: "The 'context' option must be a Context object or an array of " +
"Context objects."
},
contentScript: {
is: ["string", "array", "undefined"],
ok: function (v) {
return !Array.isArray(v) ||
v.every(function (s) typeof(s) === "string");
}
},
contentScriptFile: {
is: ["string", "array", "undefined"],
ok: function (v) {
if (!v)
return true;
let arr = Array.isArray(v) ? v : [v];
return arr.every(function (s) {
return getTypeOf(s) === "string" &&
getScheme(s) === 'resource';
});
},
msg: "The 'contentScriptFile' option must be a local file URL or " +
"an array of local file URLs."
},
onMessage: {
is: ["function", "undefined"]
}
},
contentScript: loaderContract.rules.contentScript,
contentScriptFile: loaderContract.rules.contentScriptFile
};
let labelledItemRules = mix(baseItemRules, {

View File

@ -172,6 +172,9 @@ exports.boolean = boolean;
let object = { is: ['object', 'undefined', 'null'] };
exports.object = object;
let array = { is: ['array', 'undefined', 'null'] };
exports.array = array;
let isTruthyType = type => !(type === 'undefined' || type === 'null');
let findTypes = v => { while (!isArray(v) && v.is) v = v.is; return v };

View File

@ -143,11 +143,17 @@ TestFinder.prototype = {
suiteModule = cuddlefish.main(loader, suite);
}
catch (e) {
// If `Unsupported Application` error thrown during test,
// skip the test suite
suiteModule = {
'test suite skipped': assert => assert.pass(e.message)
};
if (/Unsupported Application/i.test(e.message)) {
// If `Unsupported Application` error thrown during test,
// skip the test suite
suiteModule = {
'test suite skipped': assert => assert.pass(e.message)
};
}
else {
console.exception(e);
throw e;
}
}
if (this.testInProcess) {

View File

@ -33,7 +33,7 @@ function getInitializerName(category) {
* [event type](https://developer.mozilla.org/en/DOM/event.type) to
* listen for.
* @param {Function} listener
* Function that is called whenever an event of the specified `type`
* Function that is called whenever an event of the specified `type`
* occurs.
* @param {Boolean} capture
* If true, indicates that the user wishes to initiate capture. After
@ -62,7 +62,7 @@ exports.on = on;
* [event type](https://developer.mozilla.org/en/DOM/event.type) to
* listen for.
* @param {Function} listener
* Function that is called whenever an event of the specified `type`
* Function that is called whenever an event of the specified `type`
* occurs.
* @param {Boolean} capture
* If true, indicates that the user wishes to initiate capture. After
@ -92,7 +92,7 @@ exports.once = once;
* [event type](https://developer.mozilla.org/en/DOM/event.type) to
* listen for.
* @param {Function} listener
* Function that is called whenever an event of the specified `type`
* Function that is called whenever an event of the specified `type`
* occurs.
* @param {Boolean} capture
* If true, indicates that the user wishes to initiate capture. After
@ -137,3 +137,33 @@ function emit(element, type, { category, initializer, settings }) {
element.dispatchEvent(event);
};
exports.emit = emit;
// Takes DOM `element` and returns promise which is resolved
// when given element is removed from it's parent node.
const removed = element => {
return new Promise(resolve => {
const { MutationObserver } = element.ownerDocument.defaultView;
const observer = new MutationObserver(mutations => {
for (let mutation of mutations) {
for (let node of mutation.removedNodes || []) {
if (node === element) {
observer.disconnect();
resolve(element);
}
}
}
});
observer.observe(element.parentNode, {childList: true});
});
};
exports.removed = removed;
const when = (element, eventName, capture=false) => new Promise(resolve => {
const listener = event => {
element.removeEventListener(eventName, listener, capture);
resolve(event);
};
element.addEventListener(eventName, listener, capture);
});
exports.when = when;

View File

@ -260,7 +260,7 @@ exports.Reactor = Reactor;
* Takes an object used as options with potential keys like 'onMessage',
* used to be called `require('sdk/event/core').setListeners` on.
* This strips all keys that would trigger a listener to be set.
*
*
* @params {Object} object
* @return {Object}
*/
@ -273,3 +273,8 @@ function stripListeners (object) {
}, {});
}
exports.stripListeners = stripListeners;
const when = (target, type) => new Promise(resolve => {
once(target, type, resolve);
});
exports.when = when;

View File

@ -34,7 +34,8 @@ exports.get = function get(k) {
if (arguments.length <= 1)
return localized;
let args = arguments;
let args = Array.slice(arguments);
let placeholders = [null, ...args.slice(typeof(n) === "number" ? 2 : 1)];
if (typeof localized == "object" && "other" in localized) {
// # Plural form:
@ -76,11 +77,15 @@ exports.get = function get(k) {
// in translation.
// * In case of plural form, we has `%d` instead of `%s`.
let offset = 1;
localized = localized.replace(/%(\d*)(s|d)/g, function (v, n) {
let rv = args[n != "" ? n : offset];
offset++;
return rv;
});
if (placeholders.length > 1) {
args = placeholders;
}
localized = localized.replace(/%(\d*)[sd]/g, (v, n) => {
let rv = args[n != "" ? n : offset];
offset++;
return rv;
});
return localized;
}

View File

@ -28,6 +28,7 @@ exports.get = function get(k) {
exports.locale = function locale() {
return bestMatchingLocale;
}
// Returns the short locale code: ja, en, fr
exports.language = function language() {
return bestMatchingLocale ? bestMatchingLocale.split("-")[0].toLowerCase()

View File

@ -66,6 +66,10 @@ function get(key, n, locales) {
localized = getKey(locale, key);
}
if (!localized) {
localized = getKey(locale, key + '[other]');
}
if (localized) {
return localized;
}

View File

@ -0,0 +1,12 @@
/* 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 { window } = require("sdk/addon/window");
exports.MessageChannel = window.MessageChannel;
exports.MessagePort = window.MessagePort;

View File

@ -12,7 +12,8 @@ const { Cc, Ci, Cr } = require("chrome");
const apiUtils = require("./deprecated/api-utils");
const errors = require("./deprecated/errors");
const { isString, isUndefined, instanceOf } = require('./lang/type');
const { URL } = require('./url');
const { URL, isLocalURL } = require('./url');
const { data } = require('./self');
const NOTIFICATION_DIRECTIONS = ["auto", "ltr", "rtl"];
@ -39,7 +40,10 @@ exports.notify = function notifications_notify(options) {
}
};
function notifyWithOpts(notifyFn) {
notifyFn(valOpts.iconURL, valOpts.title, valOpts.text, !!clickObserver,
let { iconURL } = valOpts;
iconURL = iconURL && isLocalURL(iconURL) ? data.url(iconURL) : iconURL;
notifyFn(iconURL, valOpts.title, valOpts.text, !!clickObserver,
valOpts.data, clickObserver, valOpts.tag, valOpts.dir, valOpts.lang);
}
try {
@ -47,7 +51,7 @@ exports.notify = function notifications_notify(options) {
}
catch (err) {
if (err instanceof Ci.nsIException && err.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
console.warn("The notification icon named by " + valOpts.iconURL +
console.warn("The notification icon named by " + iconURL +
" does not exist. A default icon will be used instead.");
delete valOpts.iconURL;
notifyWithOpts(notify);

View File

@ -26,6 +26,7 @@ const { contract: loaderContract } = require('./content/loader');
const { has } = require('./util/array');
const { Rules } = require('./util/rules');
const { merge } = require('./util/object');
const { data } = require('./self');
const views = WeakMap();
const workers = WeakMap();
@ -92,10 +93,13 @@ const Page = Class({
setup: function Page(options) {
let page = this;
options = pageContract(options);
let uri = options.contentURL;
let view = makeFrame(window.document, {
nodeName: 'iframe',
type: 'content',
uri: options.contentURL,
uri: uri ? data.url(uri) : '',
allowJavascript: options.allow.script,
allowPlugins: true,
allowAuth: true
@ -124,12 +128,19 @@ const Page = Class({
let allowJavascript = pageContract({ allow: value }).allow.script;
return allowJavascript ? enableScript(this) : disableScript(this);
},
get contentURL() { return viewFor(this).getAttribute('src'); },
get contentURL() { return viewFor(this).getAttribute("data-src") },
set contentURL(value) {
if (!isValidURL(this, value)) return;
let view = viewFor(this);
let contentURL = pageContract({ contentURL: value }).contentURL;
view.setAttribute('src', contentURL);
// page-worker doesn't have a model like other APIs, so to be consitent
// with the behavior "what you set is what you get", we need to store
// the original `contentURL` given.
// Even if XUL elements doesn't support `dataset`, properties, to
// indicate that is a custom attribute the syntax "data-*" is used.
view.setAttribute('data-src', contentURL);
view.setAttribute('src', data.url(contentURL));
},
dispose: function () {
if (isDisposed(this)) return;

View File

@ -307,13 +307,13 @@ on(hides, "data", ({target}) => emit(panelFor(target), "hide"));
on(ready, "data", ({target}) => {
let panel = panelFor(target);
let window = domPanel.getContentDocument(target).defaultView;
workerFor(panel).attach(window);
});
on(start, "data", ({target}) => {
let panel = panelFor(target);
let window = domPanel.getContentDocument(target).defaultView;
attach(styleFor(panel), window);
});

View File

@ -17,6 +17,8 @@ const { getMostRecentBrowserWindow, getOwnerBrowserWindow,
const { create: createFrame, swapFrameLoaders } = require("../frame/utils");
const { window: addonWindow } = require("../addon/window");
const { isNil } = require("../lang/type");
const { data } = require('../self');
const events = require("../system/events");
@ -402,7 +404,10 @@ exports.getContentFrame = getContentFrame;
function getContentDocument(panel) getContentFrame(panel).contentDocument
exports.getContentDocument = getContentDocument;
function setURL(panel, url) getContentFrame(panel).setAttribute("src", url)
function setURL(panel, url) {
getContentFrame(panel).setAttribute("src", url ? data.url(url) : url);
}
exports.setURL = setURL;
function allowContextMenu(panel, allow) {

View File

@ -28,7 +28,7 @@ const permissions = metadata.permissions || {};
const isPacked = rootURI && rootURI.indexOf("jar:") === 0;
const uri = (path="") =>
path.contains(":") ? path : addonDataURI + path;
path.contains(":") ? path : addonDataURI + path.replace(/^\.\//, "");
// Some XPCOM APIs require valid URIs as an argument for certain operations

View File

@ -9,24 +9,14 @@ module.metadata = {
const { Cc, Ci } = require("chrome");
const { Class } = require("../core/heritage");
const { ns } = require("../core/namespace");
const { URL } = require('../url');
const { URL, isLocalURL } = 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 { data } = require('../self');
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));
@ -54,7 +44,7 @@ exports.Style = Style;
attachTo.define(Style, function (style, window) {
if (style.uri) {
for (let uri of style.uri)
loadSheet(window, uri, style.type);
loadSheet(window, data.url(uri), style.type);
}
if (style.source) {
@ -69,7 +59,7 @@ attachTo.define(Style, function (style, window) {
detachFrom.define(Style, function (style, window) {
if (style.uri)
for (let uri of style.uri)
removeSheet(window, uri);
removeSheet(window, data.url(uri));
if (style.source) {
let uri = "data:text/css;charset=utf-8,";

View File

@ -3,16 +3,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const { validateOptions } = require('../deprecated/api-utils');
const { validateOptions } = require("../deprecated/api-utils");
const { data } = require("../self");
function Options(options) {
if ('string' === typeof options)
options = { url: options };
return validateOptions(options, {
url: { is: ["string"] },
url: {
is: ["string"],
map: (v) => v ? data.url(v) : v
},
inBackground: {
map: function(v) !!v,
map: Boolean,
is: ["undefined", "boolean"]
},
isPinned: { is: ["undefined", "boolean"] },

View File

@ -17,7 +17,7 @@ const { off, emit, setListeners } = require('../event/core');
const { EventTarget } = require('../event/target');
const { URL } = require('../url');
const { add, remove, has, clear, iterator } = require('../lang/weak-set');
const { id: addonID } = require('../self');
const { id: addonID, data } = require('../self');
const { WindowTracker } = require('../deprecated/window-utils');
const { isShowing } = require('./sidebar/utils');
const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = require('../window/utils');
@ -35,6 +35,8 @@ const { identify } = require('./id');
const { uuid } = require('../util/uuid');
const { viewFor } = require('../view/core');
const resolveURL = (url) => url ? data.url(url) : url;
const sidebarNS = ns();
const WEB_PANEL_BROWSER_ID = 'web-panels-browser';
@ -107,7 +109,7 @@ const Sidebar = Class({
let sbTitle = window.document.getElementById('sidebar-title');
function onWebPanelSidebarCreated() {
if (panelBrowser.contentWindow.location != model.url ||
if (panelBrowser.contentWindow.location != resolveURL(model.url) ||
sbTitle.value != model.title) {
return;
}
@ -264,6 +266,8 @@ const Sidebar = Class({
exports.Sidebar = Sidebar;
function validateTitleAndURLCombo(sidebar, title, url) {
url = resolveURL(url);
if (sidebar.title == title && sidebar.url == url) {
return false;
}

View File

@ -14,11 +14,13 @@ const { models, buttons, views, viewsFor, modelFor } = require('./namespace');
const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = require('../../window/utils');
const { setStateFor } = require('../state');
const { defer } = require('../../core/promise');
const { isPrivateBrowsingSupported } = require('../../self');
const { isPrivateBrowsingSupported, data } = require('../../self');
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const WEB_PANEL_BROWSER_ID = 'web-panels-browser';
const resolveURL = (url) => url ? data.url(url) : url;
function create(window, details) {
let id = makeID(details.id);
let { document } = window;
@ -29,7 +31,7 @@ function create(window, details) {
let menuitem = document.createElementNS(XUL_NS, 'menuitem');
menuitem.setAttribute('id', id);
menuitem.setAttribute('label', details.title);
menuitem.setAttribute('sidebarurl', details.sidebarurl);
menuitem.setAttribute('sidebarurl', resolveURL(details.sidebarurl));
menuitem.setAttribute('checked', 'false');
menuitem.setAttribute('type', 'checkbox');
menuitem.setAttribute('group', 'sidebar');
@ -74,6 +76,8 @@ exports.updateTitle = updateTitle;
function updateURL(sidebar, url) {
let eleID = makeID(sidebar.id);
url = resolveURL(url);
for (let window of windows(null, { includePrivate: true })) {
// update the menuitem
let mi = window.document.getElementById(eleID);
@ -111,8 +115,10 @@ function isSidebarShowing(window, sidebar) {
}
if (sidebarTitle.value == modelFor(sidebar).title) {
let url = resolveURL(modelFor(sidebar).url);
// checks if the sidebar is loading
if (win.gWebPanelURI == modelFor(sidebar).url) {
if (win.gWebPanelURI == url) {
return true;
}
@ -122,11 +128,11 @@ function isSidebarShowing(window, sidebar) {
return false;
}
if (ele.getAttribute('cachedurl') == modelFor(sidebar).url) {
if (ele.getAttribute('cachedurl') == url) {
return true;
}
if (ele && ele.contentWindow && ele.contentWindow.location == modelFor(sidebar).url) {
if (ele && ele.contentWindow && ele.contentWindow.location == url) {
return true;
}
}
@ -154,7 +160,7 @@ function showSidebar(window, sidebar, newURL) {
let menuitem = window.document.getElementById(makeID(model.id));
menuitem.setAttribute('checked', true);
window.openWebPanel(model.title, newURL || model.url);
window.openWebPanel(model.title, resolveURL(newURL || model.url));
}
return promise;

View File

@ -9,6 +9,7 @@ module.metadata = {
};
const { validateOptions: valid } = require("../deprecated/api-utils");
const method = require("method/core");
// Function takes property validation rules and returns function that given
// an `options` object will return validated / normalized options back. If
@ -18,9 +19,9 @@ const { validateOptions: valid } = require("../deprecated/api-utils");
// 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);
}
const validator = (instance, options) => {
return valid(options || instance || {}, rules);
};
validator.rules = rules
validator.properties = function(modelFor) {
return properties(modelFor, rules);
@ -48,4 +49,7 @@ function properties(modelFor, rules) {
}, {});
return Object.create(Object.prototype, descriptor);
}
exports.properties = properties
exports.properties = properties;
const validate = method("contract/validate");
exports.validate = validate;

View File

@ -19,7 +19,7 @@ const { EventTarget } = require('../event/target');
const { when: unload } = require('../system/unload');
const { windowIterator } = require('../deprecated/window-utils');
const { List, addListItem, removeListItem } = require('../util/list');
const { isPrivateBrowsingSupported } = require('../self');
const { isPrivateBrowsingSupported, data } = require('../self');
const { isTabPBSupported, ignoreWindow } = require('../private-browsing/utils');
const mainWindow = windowNS(browserWindows.activeWindow).window;
@ -55,7 +55,8 @@ const Tabs = Class({
console.error(ERR_FENNEC_MSG); // TODO
}
let rawTab = openTab(windowNS(activeWin).window, options.url, {
let url = options.url ? data.url(options.url) : options.url;
let rawTab = openTab(windowNS(activeWin).window, url, {
inBackground: options.inBackground,
isPrivate: supportPrivateTabs && options.isPrivate
});

View File

@ -17,7 +17,7 @@ const { getOwnerWindow, getActiveTab, getTabs,
const { Options } = require("../tabs/common");
const { observer: tabsObserver } = require("../tabs/observer");
const { ignoreWindow } = require("../private-browsing/utils");
const { when: unload } = require('../system/unload');
const { when: unload } = require("../system/unload");
const TAB_BROWSER = "tabbrowser";

View File

@ -27,6 +27,9 @@ skip = [
"packages/api-utils/tests/test-querystring.js", # MIT
"packages/api-utils/lib/promise.js", # MIT
"packages/api-utils/tests/test-promise.js", # MIT
"examples/actor-repl/README.md", # It's damn readme file
"examples/actor-repl/data/codemirror-compressed.js", # MIT
"examples/actor-repl/data/codemirror.css", # MIT
]
absskip = [from_sdk_top(os.path.join(*fn.split("/"))) for fn in skip]

View File

@ -20,3 +20,9 @@ explicitPlural[other]=other
# file parser)
unicodeEscape = \u0020\u0040\u0020
# this string equals to " @ "
# bug 1033309 plurals with multiple placeholders
first_identifier[one]=first entry is %s and the second one is %s.
first_identifier=the entries are %s and %s.
second_identifier[other]=first entry is %s and the second one is %s.
third_identifier=first entry is %s and the second one is %s.

View File

@ -163,6 +163,20 @@ exports.testEnUsLocaleName = createTest("en-GB", function(assert, loader, done)
"other",
"PluralForm form can be omitting generic key [i.e. without ...[other] at end of key)");
assert.equal(_("first_identifier", "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier no count");
assert.equal(_("first_identifier", 0, "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier with count = 0");
assert.equal(_("first_identifier", 1, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "first_identifier with count = 1");
assert.equal(_("first_identifier", 2, "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier with count = 2");
assert.equal(_("second_identifier", "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with no count");
assert.equal(_("second_identifier", 0, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 0");
assert.equal(_("second_identifier", 1, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 1");
assert.equal(_("second_identifier", 2, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 2");
assert.equal(_("third_identifier", "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with no count");
assert.equal(_("third_identifier", 0, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with count = 0");
assert.equal(_("third_identifier", 2, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with count = 2");
done();
});

View File

@ -20,3 +20,9 @@ explicitPlural[other]=other
# file parser)
unicodeEscape = \u0020\u0040\u0020
# this string equals to " @ "
# bug 1033309 plurals with multiple placeholders
first_identifier[one]=first entry is %s and the second one is %s.
first_identifier=the entries are %s and %s.
second_identifier[other]=first entry is %s and the second one is %s.
third_identifier=first entry is %s and the second one is %s.

View File

@ -87,7 +87,6 @@ exports.testExactMatching = createTest("fr-FR", function(assert, loader, done) {
});
exports.testHtmlLocalization = createTest("en-GB", function(assert, loader, done) {
// Ensure initing html component that watch document creations
// Note that this module is automatically initialized in
// cuddlefish.js:Loader.main in regular addons. But it isn't for unit tests.
@ -165,6 +164,20 @@ exports.testEnUsLocaleName = createTest("en-US", function(assert, loader, done)
"other",
"PluralForm form can be omitting generic key [i.e. without ...[other] at end of key)");
assert.equal(_("first_identifier", "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier no count");
assert.equal(_("first_identifier", 0, "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier with count = 0");
assert.equal(_("first_identifier", 1, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "first_identifier with count = 1");
assert.equal(_("first_identifier", 2, "ONE", "TWO"), "the entries are ONE and TWO.", "first_identifier with count = 2");
assert.equal(_("second_identifier", "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with no count");
assert.equal(_("second_identifier", 0, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 0");
assert.equal(_("second_identifier", 1, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 1");
assert.equal(_("second_identifier", 2, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "second_identifier with count = 2");
assert.equal(_("third_identifier", "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with no count");
assert.equal(_("third_identifier", 0, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with count = 0");
assert.equal(_("third_identifier", 2, "ONE", "TWO"), "first entry is ONE and the second one is TWO.", "third_identifier with count = 2");
done();
});

View File

@ -1,3 +0,0 @@
<script>
addon.postMessage("hello addon")
</script>

View File

@ -1,34 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const app = require("sdk/system/xul-app");
exports["test addon globa"] = app.is("Firefox") ? testAddonGlobal : unsupported;
function testAddonGlobal (assert, done) {
const { Panel } = require("sdk/panel")
const { data } = require("sdk/self")
let panel = Panel({
contentURL: //"data:text/html,now?",
data.url("./index.html"),
onMessage: function(message) {
assert.pass("got message from panel script");
panel.destroy();
done();
},
onError: function(error) {
assert.fail(Error("failed to recieve message"));
done();
}
});
};
function unsupported (assert) {
assert.pass("privileged-panel unsupported on platform");
}
require("sdk/test/runner").runTestsFromModule(module);

View File

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

View File

@ -2,5 +2,9 @@
* 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.url = path =>
module.uri.substr(0, module.uri.lastIndexOf("/") + 1) + "fixtures/" + path
const { uri } = module;
const prefix = uri.substr(0, uri.lastIndexOf("/") + 1) + "fixtures/";
exports.url = (path="") => path && path.contains(":")
? path
: prefix + path.replace(/^\.\//, "");

View File

@ -0,0 +1 @@
div { border-style: dashed; }

View File

@ -1,10 +1,12 @@
<script>
addon.port.on('X', function(msg) {
// last message
addon.port.emit('X', msg + '3');
});
if ("addon" in window) {
addon.port.on('X', function(msg) {
// last message
addon.port.emit('X', msg + '3');
});
// start messaging chain
addon.port.emit('Y', '1');
// start messaging chain
addon.port.emit('Y', '1');
}
</script>
SIDEBAR TEST

View File

@ -9,6 +9,8 @@ const timer = require("sdk/timers");
const xulApp = require("sdk/system/xul-app");
const { Loader } = require("sdk/test/loader");
const { openTab, getBrowserForTab, closeTab } = require("sdk/tabs/utils");
const self = require("sdk/self");
const { merge } = require("sdk/util/object");
/**
* A helper function that creates a PageMod, then opens the specified URL
@ -31,7 +33,13 @@ exports.testPageMod = function testPageMod(assert, done, testURL, pageModOptions
return null;
}
let loader = Loader(module);
let loader = Loader(module, null, null, {
modules: {
"sdk/self": merge({}, self, {
data: merge({}, self.data, require("./fixtures"))
})
}
});
let pageMod = loader.require("sdk/page-mod");
var pageMods = [new pageMod.PageMod(opts) for each(opts in pageModOptions)];

View File

@ -12,13 +12,13 @@ exports['test:contentURL'] = function(assert) {
let loader = Loader(),
value, emitted = 0, changes = 0;
assert.throws(
function() loader.contentURL = 4,
assert.throws(() =>
loader.contentURL = 4,
/The `contentURL` option must be a valid URL./,
'Must throw an exception if `contentURL` is not URL.'
);
assert.throws(
function() loader.contentURL = { toString: function() 'Oops' },
assert.throws(() =>
loader.contentURL = { toString: function() 'Oops' },
/The `contentURL` option must be a valid URL./,
'Must throw an exception if `contentURL` is not URL.'
);
@ -78,6 +78,24 @@ exports['test:contentURL'] = function(assert) {
'must not emit `propertyChange` if same value is set'
);
loader.contentURL = value = './index.html';
assert.equal(
value,
'' + loader.contentURL,
'value must be set'
);
assert.equal(
++ changes,
emitted,
'had to emit `propertyChange`'
);
loader.contentURL = value;
assert.equal(
changes,
emitted,
'must not emit `propertyChange` if same value is set'
);
loader.removeListener('propertyChange', listener);
loader.contentURL = value = 'about:blank';
assert.equal(
@ -133,39 +151,37 @@ exports['test:contentScriptWhen'] = function(assert) {
exports['test:contentScript'] = function(assert) {
let loader = Loader(), value;
assert.equal(
null,
loader.contentScript,
'`contentScript` defaults to `null`'
);
loader.contentScript = value = 'let test = {};';
assert.equal(
value,
loader.contentScript
);
try {
loader.contentScript = { 1: value }
test.fail('must throw when wrong value is set');
} catch(e) {
assert.equal(
'The `contentScript` option must be a string or an array of strings.',
e.message
);
}
try {
loader.contentScript = ['oue', 2]
test.fail('must throw when wrong value is set');
} catch(e) {
assert.equal(
'The `contentScript` option must be a string or an array of strings.',
e.message
);
}
assert.throws(() =>
loader.contentScript = { 1: value },
/The `contentScript` option must be a string or an array of strings/,
'must throw when wrong value is set'
);
assert.throws(() =>
loader.contentScript = ['oue', 2],
/The `contentScript` option must be a string or an array of strings/,
'must throw when wrong value is set'
);
loader.contentScript = undefined;
assert.equal(
null,
loader.contentScript
);
loader.contentScript = value = ["1;", "2;"];
assert.equal(
value,
@ -185,36 +201,25 @@ exports['test:contentScriptFile'] = function(assert) {
value,
loader.contentScriptFile
);
try {
loader.contentScriptFile = { 1: uri }
test.fail('must throw when wrong value is set');
} catch(e) {
assert.equal(
'The `contentScriptFile` option must be a local URL or an array of URLs.',
e.message
);
}
try {
loader.contentScriptFile = [ 'oue', uri ]
test.fail('must throw when wrong value is set');
} catch(e) {
assert.equal(
'The `contentScriptFile` option must be a local URL or an array of URLs.',
e.message
);
}
assert.throws(() =>
loader.contentScriptFile = { 1: uri },
/The `contentScriptFile` option must be a local URL or an array of URLs/,
'must throw when wrong value is set'
);
assert.throws(() =>
loader.contentScriptFile = [ 'oue', uri ],
/The `contentScriptFile` option must be a local URL or an array of URLs/,
'must throw when wrong value is set'
);
let data = 'data:text/html,test';
try {
loader.contentScriptFile = [ { toString: () => data } ];
test.fail('must throw when non-URL object is set');
} catch(e) {
assert.equal(
'The `contentScriptFile` option must be a local URL or an array of URLs.',
e.message
);
}
assert.throws(() =>
loader.contentScriptFile = [ { toString: () => data } ],
/The `contentScriptFile` option must be a local URL or an array of URLs/,
'must throw when wrong value is set'
);
loader.contentScriptFile = new URL(data);
assert.ok(
@ -222,11 +227,18 @@ exports['test:contentScriptFile'] = function(assert) {
'must be able to set `contentScriptFile` to an instance of URL'
);
assert.equal(
data,
data,
loader.contentScriptFile.toString(),
'setting `contentScriptFile` to an instance of URL should preserve the url'
);
loader.contentScriptFile = './index.html';
assert.equal(
'./index.html',
loader.contentScriptFile,
'setting `contentScriptFile` to relative path is allowed'
);
loader.contentScriptFile = undefined;
assert.equal(
null,

View File

@ -879,6 +879,10 @@ exports.testContentContextMatchString = function (assert, done) {
exports.testContentScriptFile = function (assert, done) {
let test = new TestHelper(assert, done);
let loader = test.newLoader();
let { defer, all } = require("sdk/core/promise");
let itemScript = [defer(), defer()];
let menuShown = defer();
let menuPromises = itemScript.concat(menuShown).map(({promise}) => promise);
// Reject remote files
assert.throws(function() {
@ -887,20 +891,37 @@ exports.testContentScriptFile = function (assert, done) {
contentScriptFile: "http://mozilla.com/context-menu.js"
});
},
new RegExp("The 'contentScriptFile' option must be a local file URL " +
"or an array of local file URLs."),
/The `contentScriptFile` option must be a local URL or an array of URLs/,
"Item throws when contentScriptFile is a remote URL");
// But accept files from data folder
let item = new loader.cm.Item({
label: "item",
contentScriptFile: data.url("test-context-menu.js")
contentScriptFile: data.url("test-contentScriptFile.js"),
onMessage: (message) => {
assert.equal(message, "msg from contentScriptFile",
"contentScriptFile loaded with absolute url");
itemScript[0].resolve();
}
});
test.showMenu(null, function (popup) {
test.checkMenu([item], [], []);
test.done();
let item2 = new loader.cm.Item({
label: "item2",
contentScriptFile: "./test-contentScriptFile.js",
onMessage: (message) => {
assert.equal(message, "msg from contentScriptFile",
"contentScriptFile loaded with relative url");
itemScript[1].resolve();
}
});
console.log(item.contentScriptFile, item2.contentScriptFile);
test.showMenu(null, function (popup) {
test.checkMenu([item, item2], [], []);
menuShown.resolve();
});
all(menuPromises).then(() => test.done());
};
@ -3944,7 +3965,15 @@ TestHelper.prototype = {
// function that unloads the loader and associated resources.
newLoader: function () {
const self = this;
let loader = Loader(module);
const selfModule = require('sdk/self');
let loader = Loader(module, null, null, {
modules: {
"sdk/self": merge({}, selfModule, {
data: merge({}, selfModule.data, require("./fixtures"))
})
}
});
let wrapper = {
loader: loader,
cm: loader.require("sdk/context-menu"),

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { getRulesForLocale } = require("sdk/l10n/plural-rules");

View File

@ -1028,20 +1028,20 @@ exports.testPageModCss = function(assert, done) {
'data:text/html;charset=utf-8,<div style="background: silver">css test</div>', [{
include: ["*", "data:*"],
contentStyle: "div { height: 100px; }",
contentStyleFile: data.url("css-include-file.css")
contentStyleFile: [data.url("include-file.css"), "./border-style.css"]
}],
function(win, done) {
let div = win.document.querySelector("div");
assert.equal(
div.clientHeight,
100,
"PageMod contentStyle worked"
);
assert.equal(
div.offsetHeight,
120,
"PageMod contentStyleFile worked"
);
assert.equal(div.clientHeight, 100,
"PageMod contentStyle worked");
assert.equal(div.offsetHeight, 120,
"PageMod contentStyleFile worked");
assert.equal(win.getComputedStyle(div).borderTopStyle, "dashed",
"PageMod contentStyleFile with relative path worked");
done();
}
);
@ -1165,6 +1165,21 @@ exports.testPageModCssAutomaticDestroy = function(assert, done) {
});
};
exports.testPageModContentScriptFile = function(assert, done) {
testPageMod(assert, done, "about:license", [{
include: "about:*",
contentScriptWhen: "start",
contentScriptFile: "./test-contentScriptFile.js",
onMessage: message => {
assert.equal(message, "msg from contentScriptFile",
"PageMod contentScriptFile with relative path worked");
}
}],
(win, done) => done()
);
};
exports.testPageModTimeout = function(assert, done) {
let tab = null

View File

@ -268,6 +268,33 @@ exports.testLoadContentPage = function(assert, done) {
});
}
exports.testLoadContentPageRelativePath = function(assert, done) {
const self = require("sdk/self");
const { merge } = require("sdk/util/object");
let loader = Loader(module, null, null, {
modules: {
"sdk/self": merge({}, self, {
data: merge({}, self.data, fixtures)
})
}
});
let page = loader.require("sdk/page-worker").Page({
onMessage: function(message) {
// The message is an array whose first item is the test method to call
// and the rest of whose items are arguments to pass it.
let msg = message.shift();
if (msg == "done")
return done();
assert[msg].apply(assert, message);
},
contentURL: "./test-page-worker.html",
contentScriptFile: "./test-page-worker.js",
contentScriptWhen: "ready"
});
}
exports.testAllowScriptDefault = function(assert, done) {
let page = Page({
onMessage: function(message) {

View File

@ -27,7 +27,6 @@ const { wait } = require('./event/helpers');
const fixtures = require('./fixtures')
const SVG_URL = fixtures.url('mofo_logo.SVG');
const CSS_URL = fixtures.url('css-include-file.css');
const Isolate = fn => '(' + fn + ')()';
@ -979,7 +978,16 @@ exports['test panel can be constructed without any arguments'] = function (asser
};
exports['test panel CSS'] = function(assert, done) {
const loader = Loader(module);
const { merge } = require("sdk/util/object");
let loader = Loader(module, null, null, {
modules: {
"sdk/self": merge({}, self, {
data: merge({}, self.data, fixtures)
})
}
});
const { Panel } = loader.require('sdk/panel');
const { getActiveView } = loader.require('sdk/view/core');
@ -991,13 +999,19 @@ exports['test panel CSS'] = function(assert, done) {
contentURL: 'data:text/html;charset=utf-8,' +
'<div style="background: silver">css test</div>',
contentStyle: 'div { height: 100px; }',
contentStyleFile: CSS_URL,
contentStyleFile: [fixtures.url("include-file.css"), "./border-style.css"],
onShow: () => {
ready(getContentWindow(panel)).then(({ document }) => {
ready(getContentWindow(panel)).then(({ window, document }) => {
let div = document.querySelector('div');
assert.equal(div.clientHeight, 100, 'Panel contentStyle worked');
assert.equal(div.offsetHeight, 120, 'Panel contentStyleFile worked');
assert.equal(div.clientHeight, 100,
"Panel contentStyle worked");
assert.equal(div.offsetHeight, 120,
"Panel contentStyleFile worked");
assert.equal(window.getComputedStyle(div).borderTopStyle, "dashed",
"Panel contentStyleFile with relative path worked");
loader.unload();
done();
@ -1008,6 +1022,53 @@ exports['test panel CSS'] = function(assert, done) {
panel.show();
};
exports['test panel contentScriptFile'] = function(assert, done) {
const { merge } = require("sdk/util/object");
let loader = Loader(module, null, null, {
modules: {
"sdk/self": merge({}, self, {
data: merge({}, self.data, {url: fixtures.url})
})
}
});
const { Panel } = loader.require('sdk/panel');
const { getActiveView } = loader.require('sdk/view/core');
const getContentWindow = panel =>
getActiveView(panel).querySelector('iframe').contentWindow;
let whenMessage = defer();
let whenShown = defer();
let panel = Panel({
contentURL: './test.html',
contentScriptFile: "./test-contentScriptFile.js",
onMessage: (message) => {
assert.equal(message, "msg from contentScriptFile",
"Panel contentScriptFile with relative path worked");
whenMessage.resolve();
},
onShow: () => {
ready(getContentWindow(panel)).then(({ document }) => {
assert.equal(document.title, 'foo',
"Panel contentURL with relative path worked");
whenShown.resolve();
});
}
});
all([whenMessage.promise, whenShown.promise]).
then(loader.unload).
then(done, assert.fail);
panel.show();
};
exports['test panel CSS list'] = function(assert, done) {
const loader = Loader(module);
const { Panel } = loader.require('sdk/panel');
@ -1186,6 +1247,36 @@ exports['test panel contextmenu disabled'] = function*(assert) {
assert.equal(contextmenu.state, 'closed',
'contextmenu was never open');
loader.unload();
}
exports["test panel addon global object"] = function*(assert) {
const { merge } = require("sdk/util/object");
let loader = Loader(module, null, null, {
modules: {
"sdk/self": merge({}, self, {
data: merge({}, self.data, {url: fixtures.url})
})
}
});
const { Panel } = loader.require('sdk/panel');
let panel = Panel({
contentURL: "./test-trusted-document.html"
});
panel.show();
yield wait(panel, "show");
panel.port.emit('addon-to-document', 'ok');
yield wait(panel.port, "document-to-addon");
assert.pass("Received an event from the document");
loader.unload();
}

View File

@ -40,6 +40,38 @@ exports.testTabCounts = function(assert, done) {
});
};
exports.testTabRelativePath = function(assert, done) {
const { merge } = require("sdk/util/object");
const self = require("sdk/self");
let loader = Loader(module, null, null, {
modules: {
"sdk/self": merge({}, self, {
data: merge({}, self.data, require("./fixtures"))
})
}
});
let tabs = loader.require("sdk/tabs");
tabs.open({
url: "./test.html",
onReady: (tab) => {
assert.equal(tab.title, "foo",
"tab opened a document with relative path");
tab.attach({
contentScriptFile: "./test-contentScriptFile.js",
onMessage: (message) => {
assert.equal(message, "msg from contentScriptFile",
"Tab attach a contentScriptFile with relative path worked");
tab.close(done);
}
});
}
});
};
// TEST: tabs.activeTab getter
exports.testActiveTab_getter = function(assert, done) {

View File

@ -370,6 +370,48 @@ exports.testSidebarUnload = function(assert, done) {
assert.pass('showing the sidebar');
}
exports.testRelativeURL = function(assert, done) {
const { merge } = require('sdk/util/object');
const self = require('sdk/self');
let loader = Loader(module, null, null, {
modules: {
'sdk/self': merge({}, self, {
data: merge({}, self.data, require('./fixtures'))
})
}
});
const { Sidebar } = loader.require('sdk/ui/sidebar');
let testName = 'testRelativeURL';
let sidebar = Sidebar({
id: testName,
title: testName,
url: './test-sidebar-addon-global.html'
});
sidebar.on('attach', function(worker) {
assert.pass('sidebar was attached');
assert.ok(!!worker, 'attach event has worker');
worker.port.once('Y', function(msg) {
assert.equal(msg, '1', 'got event from worker');
worker.port.on('X', function(msg) {
assert.equal(msg, '123', 'the final message is correct');
sidebar.destroy();
done();
});
worker.port.emit('X', msg + '2');
})
});
sidebar.show();
}
exports.testRemoteContent = function(assert) {
const { Sidebar } = require('sdk/ui/sidebar');
let testName = 'testRemoteContent';

View File

@ -36,8 +36,6 @@ function openNewWindowTab(url, options) {
if (options.onLoad) {
options.onLoad({ target: { defaultView: window } })
}
return newTab;
});
}
@ -538,7 +536,7 @@ exports.testConstructor = function(assert, done) {
assert.equal(widgetCount2(), widgetStartCount2, "2nd window has correct number of child elements after second destroy");
close(browserWindow).then(doneTest);
}});
}}).catch(assert.fail);
});
// test window closing
@ -634,7 +632,7 @@ exports.testConstructor = function(assert, done) {
browserWindow.setToolbarVisibility(container(), true);
close(browserWindow2).then(doneTest);
}});
}}).catch(assert.fail);
});
}
@ -1179,7 +1177,7 @@ exports.testReinsertion = function(assert, done) {
loader.unload();
done();
});
}});
}}).catch(assert.fail);
};
exports.testWideWidget = function testWideWidget(assert) {

View File

@ -42,6 +42,33 @@ exports.testOpenAndCloseWindow = function(assert, done) {
});
};
exports.testOpenRelativePathWindow = function(assert, done) {
assert.equal(browserWindows.length, 1, "Only one window open");
const { merge } = require("sdk/util/object");
const self = require("sdk/self");
let loader = Loader(module, null, null, {
modules: {
"sdk/self": merge({}, self, {
data: merge({}, self.data, require("./../fixtures"))
})
}
});
loader.require("sdk/windows").browserWindows.open({
url: "./test.html",
onOpen: (window) => {
window.tabs.activeTab.once("ready", (tab) => {
assert.equal(tab.title, "foo",
"tab opened a document with relative path");
window.close(done);
});
}
})
}
exports.testAutomaticDestroy = function(assert, done) {
let windows = browserWindows;