mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 10:44:56 +00:00
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:
parent
9788f0c902
commit
4aaca6c799
3
addon-sdk/source/examples/actor-repl/README.md
Normal file
3
addon-sdk/source/examples/actor-repl/README.md
Normal 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
264
addon-sdk/source/examples/actor-repl/data/codemirror.css
Normal file
264
addon-sdk/source/examples/actor-repl/data/codemirror.css
Normal 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;
|
||||
}
|
||||
}
|
108
addon-sdk/source/examples/actor-repl/data/index.html
Normal file
108
addon-sdk/source/examples/actor-repl/data/index.html
Normal 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>
|
117
addon-sdk/source/examples/actor-repl/data/main.css
Normal file
117
addon-sdk/source/examples/actor-repl/data/main.css
Normal 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);
|
||||
}
|
||||
}
|
BIN
addon-sdk/source/examples/actor-repl/data/robot.png
Normal file
BIN
addon-sdk/source/examples/actor-repl/data/robot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
37
addon-sdk/source/examples/actor-repl/index.js
Normal file
37
addon-sdk/source/examples/actor-repl/index.js
Normal 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 }
|
||||
});
|
10
addon-sdk/source/examples/actor-repl/package.json
Normal file
10
addon-sdk/source/examples/actor-repl/package.json
Normal 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"
|
||||
}
|
816
addon-sdk/source/examples/debug-client/data/client.js
Normal file
816
addon-sdk/source/examples/debug-client/data/client.js
Normal 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);
|
||||
|
50
addon-sdk/source/examples/debug-client/data/index.html
Normal file
50
addon-sdk/source/examples/debug-client/data/index.html
Normal 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>
|
BIN
addon-sdk/source/examples/debug-client/data/plugin.png
Normal file
BIN
addon-sdk/source/examples/debug-client/data/plugin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
28
addon-sdk/source/examples/debug-client/data/task.js
Normal file
28
addon-sdk/source/examples/debug-client/data/task.js
Normal 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 = {});
|
33
addon-sdk/source/examples/debug-client/index.js
Normal file
33
addon-sdk/source/examples/debug-client/index.js
Normal 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 }
|
||||
});
|
10
addon-sdk/source/examples/debug-client/package.json
Normal file
10
addon-sdk/source/examples/debug-client/package.json
Normal 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"
|
||||
}
|
95
addon-sdk/source/lib/dev/debuggee.js
Normal file
95
addon-sdk/source/lib/dev/debuggee.js
Normal 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;
|
115
addon-sdk/source/lib/dev/frame-script.js
Normal file
115
addon-sdk/source/lib/dev/frame-script.js
Normal 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);
|
223
addon-sdk/source/lib/dev/panel.js
Normal file
223
addon-sdk/source/lib/dev/panel.js
Normal 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();
|
||||
});
|
64
addon-sdk/source/lib/dev/ports.js
Normal file
64
addon-sdk/source/lib/dev/ports.js
Normal 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;
|
75
addon-sdk/source/lib/dev/toolbox.js
Normal file
75
addon-sdk/source/lib/dev/toolbox.js
Normal 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;
|
38
addon-sdk/source/lib/dev/utils.js
Normal file
38
addon-sdk/source/lib/dev/utils.js
Normal 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;
|
3760
addon-sdk/source/lib/dev/volcan.js
Normal file
3760
addon-sdk/source/lib/dev/volcan.js
Normal file
File diff suppressed because one or more lines are too long
@ -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
|
||||
|
@ -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.'
|
||||
}
|
||||
};
|
||||
|
@ -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')
|
||||
|
@ -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 }) {
|
||||
|
@ -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, {
|
||||
|
@ -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 };
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -66,6 +66,10 @@ function get(key, n, locales) {
|
||||
localized = getKey(locale, key);
|
||||
}
|
||||
|
||||
if (!localized) {
|
||||
localized = getKey(locale, key + '[other]');
|
||||
}
|
||||
|
||||
if (localized) {
|
||||
return localized;
|
||||
}
|
||||
|
12
addon-sdk/source/lib/sdk/messaging.js
Normal file
12
addon-sdk/source/lib/sdk/messaging.js
Normal 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;
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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,";
|
||||
|
@ -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"] },
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
});
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
<script>
|
||||
addon.postMessage("hello addon")
|
||||
</script>
|
@ -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);
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"id": "test-privileged-addon"
|
||||
}
|
@ -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(/^\.\//, "");
|
||||
|
1
addon-sdk/source/test/fixtures/border-style.css
vendored
Normal file
1
addon-sdk/source/test/fixtures/border-style.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
div { border-style: dashed; }
|
@ -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
|
||||
|
@ -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)];
|
||||
|
@ -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,
|
||||
|
@ -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"),
|
||||
|
236
addon-sdk/source/test/test-dev-panel.js
Normal file
236
addon-sdk/source/test/test-dev-panel.js
Normal file
File diff suppressed because one or more lines are too long
@ -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");
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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';
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user