mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-20 15:27:32 +09:00
Move Add-on SDK source to toolkit/jetpack
This commit is contained in:
parent
0a03067ee3
commit
23b67db438
993 changed files with 484 additions and 100663 deletions
|
|
@ -1,427 +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";
|
||||
|
||||
// The panel module currently supports only Firefox and SeaMonkey.
|
||||
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
|
||||
module.metadata = {
|
||||
"stability": "stable",
|
||||
"engines": {
|
||||
"Firefox": "*",
|
||||
"SeaMonkey": "*"
|
||||
}
|
||||
};
|
||||
|
||||
const { Cu, Ci } = require("chrome");
|
||||
const { setTimeout } = require('./timers');
|
||||
const { Class } = require("./core/heritage");
|
||||
const { merge } = require("./util/object");
|
||||
const { WorkerHost } = require("./content/utils");
|
||||
const { Worker } = require("./deprecated/sync-worker");
|
||||
const { Disposable } = require("./core/disposable");
|
||||
const { WeakReference } = require('./core/reference');
|
||||
const { contract: loaderContract } = require("./content/loader");
|
||||
const { contract } = require("./util/contract");
|
||||
const { on, off, emit, setListeners } = require("./event/core");
|
||||
const { EventTarget } = require("./event/target");
|
||||
const domPanel = require("./panel/utils");
|
||||
const { getDocShell } = require('./frame/utils');
|
||||
const { events } = require("./panel/events");
|
||||
const systemEvents = require("./system/events");
|
||||
const { filter, pipe, stripListeners } = require("./event/utils");
|
||||
const { getNodeView, getActiveView } = require("./view/core");
|
||||
const { isNil, isObject, isNumber } = require("./lang/type");
|
||||
const { getAttachEventType } = require("./content/utils");
|
||||
const { number, boolean, object } = require('./deprecated/api-utils');
|
||||
const { Style } = require("./stylesheet/style");
|
||||
const { attach, detach } = require("./content/mod");
|
||||
|
||||
var isRect = ({top, right, bottom, left}) => [top, right, bottom, left].
|
||||
some(value => isNumber(value) && !isNaN(value));
|
||||
|
||||
var isSDKObj = obj => obj instanceof Class;
|
||||
|
||||
var rectContract = contract({
|
||||
top: number,
|
||||
right: number,
|
||||
bottom: number,
|
||||
left: number
|
||||
});
|
||||
|
||||
var position = {
|
||||
is: object,
|
||||
map: v => (isNil(v) || isSDKObj(v) || !isObject(v)) ? v : rectContract(v),
|
||||
ok: v => isNil(v) || isSDKObj(v) || (isObject(v) && isRect(v)),
|
||||
msg: 'The option "position" must be a SDK object registered as anchor; ' +
|
||||
'or an object with one or more of the following keys set to numeric ' +
|
||||
'values: top, right, bottom, left.'
|
||||
}
|
||||
|
||||
var displayContract = contract({
|
||||
width: number,
|
||||
height: number,
|
||||
focus: boolean,
|
||||
position: position
|
||||
});
|
||||
|
||||
var panelContract = contract(merge({
|
||||
// contentStyle* / contentScript* are sharing the same validation constraints,
|
||||
// so they can be mostly reused, except for the messages.
|
||||
contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
|
||||
msg: 'The `contentStyle` option must be a string or an array of strings.'
|
||||
}),
|
||||
contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
|
||||
msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
|
||||
}),
|
||||
contextMenu: boolean,
|
||||
allow: {
|
||||
is: ['object', 'undefined', 'null'],
|
||||
map: function (allow) { return { script: !allow || allow.script !== false }}
|
||||
},
|
||||
}, displayContract.rules, loaderContract.rules));
|
||||
|
||||
function Allow(panel) {
|
||||
return {
|
||||
get script() { return getDocShell(viewFor(panel).backgroundFrame).allowJavascript; },
|
||||
set script(value) { return setScriptState(panel, value); },
|
||||
};
|
||||
}
|
||||
|
||||
function setScriptState(panel, value) {
|
||||
let view = viewFor(panel);
|
||||
getDocShell(view.backgroundFrame).allowJavascript = value;
|
||||
getDocShell(view.viewFrame).allowJavascript = value;
|
||||
view.setAttribute("sdkscriptenabled", "" + value);
|
||||
}
|
||||
|
||||
function isDisposed(panel) {
|
||||
return !views.has(panel);
|
||||
}
|
||||
|
||||
var panels = new WeakMap();
|
||||
var models = new WeakMap();
|
||||
var views = new WeakMap();
|
||||
var workers = new WeakMap();
|
||||
var styles = new WeakMap();
|
||||
|
||||
const viewFor = (panel) => views.get(panel);
|
||||
const modelFor = (panel) => models.get(panel);
|
||||
const panelFor = (view) => panels.get(view);
|
||||
const workerFor = (panel) => workers.get(panel);
|
||||
const styleFor = (panel) => styles.get(panel);
|
||||
|
||||
function getPanelFromWeakRef(weakRef) {
|
||||
if (!weakRef) {
|
||||
return null;
|
||||
}
|
||||
let panel = weakRef.get();
|
||||
if (!panel) {
|
||||
return null;
|
||||
}
|
||||
if (isDisposed(panel)) {
|
||||
return null;
|
||||
}
|
||||
return panel;
|
||||
}
|
||||
|
||||
var SinglePanelManager = {
|
||||
visiblePanel: null,
|
||||
enqueuedPanel: null,
|
||||
enqueuedPanelCallback: null,
|
||||
// Calls |callback| with no arguments when the panel may be shown.
|
||||
requestOpen: function(panelToOpen, callback) {
|
||||
let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel);
|
||||
if (currentPanel || SinglePanelManager.enqueuedPanel) {
|
||||
SinglePanelManager.enqueuedPanel = Cu.getWeakReference(panelToOpen);
|
||||
SinglePanelManager.enqueuedPanelCallback = callback;
|
||||
if (currentPanel && currentPanel.isShowing) {
|
||||
currentPanel.hide();
|
||||
}
|
||||
} else {
|
||||
SinglePanelManager.notifyPanelCanOpen(panelToOpen, callback);
|
||||
}
|
||||
},
|
||||
notifyPanelCanOpen: function(panel, callback) {
|
||||
let view = viewFor(panel);
|
||||
// Can't pass an arrow function as the event handler because we need to be
|
||||
// able to call |removeEventListener| later.
|
||||
view.addEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true);
|
||||
view.addEventListener("popupshown", SinglePanelManager.onVisiblePanelShown, false);
|
||||
SinglePanelManager.enqueuedPanel = null;
|
||||
SinglePanelManager.enqueuedPanelCallback = null;
|
||||
SinglePanelManager.visiblePanel = Cu.getWeakReference(panel);
|
||||
callback();
|
||||
},
|
||||
onVisiblePanelShown: function(event) {
|
||||
let panel = panelFor(event.target);
|
||||
if (SinglePanelManager.enqueuedPanel) {
|
||||
// Another panel started waiting for |panel| to close before |panel| was
|
||||
// even done opening.
|
||||
panel.hide();
|
||||
}
|
||||
},
|
||||
onVisiblePanelHidden: function(event) {
|
||||
let view = event.target;
|
||||
let panel = panelFor(view);
|
||||
let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel);
|
||||
if (currentPanel && currentPanel != panel) {
|
||||
return;
|
||||
}
|
||||
SinglePanelManager.visiblePanel = null;
|
||||
view.removeEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true);
|
||||
view.removeEventListener("popupshown", SinglePanelManager.onVisiblePanelShown, false);
|
||||
let nextPanel = getPanelFromWeakRef(SinglePanelManager.enqueuedPanel);
|
||||
let nextPanelCallback = SinglePanelManager.enqueuedPanelCallback;
|
||||
if (nextPanel) {
|
||||
SinglePanelManager.notifyPanelCanOpen(nextPanel, nextPanelCallback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Panel = Class({
|
||||
implements: [
|
||||
// Generate accessors for the validated properties that update model on
|
||||
// set and return values from model on get.
|
||||
panelContract.properties(modelFor),
|
||||
EventTarget,
|
||||
Disposable,
|
||||
WeakReference
|
||||
],
|
||||
extends: WorkerHost(workerFor),
|
||||
setup: function setup(options) {
|
||||
let model = merge({
|
||||
defaultWidth: 320,
|
||||
defaultHeight: 240,
|
||||
focus: true,
|
||||
position: Object.freeze({}),
|
||||
contextMenu: false
|
||||
}, panelContract(options));
|
||||
model.ready = false;
|
||||
models.set(this, model);
|
||||
|
||||
if (model.contentStyle || model.contentStyleFile) {
|
||||
styles.set(this, Style({
|
||||
uri: model.contentStyleFile,
|
||||
source: model.contentStyle
|
||||
}));
|
||||
}
|
||||
|
||||
// Setup view
|
||||
let viewOptions = {allowJavascript: !model.allow || (model.allow.script !== false)};
|
||||
let view = domPanel.make(null, viewOptions);
|
||||
panels.set(view, this);
|
||||
views.set(this, view);
|
||||
|
||||
// Load panel content.
|
||||
domPanel.setURL(view, model.contentURL);
|
||||
|
||||
// Allow context menu
|
||||
domPanel.allowContextMenu(view, model.contextMenu);
|
||||
|
||||
// Setup listeners.
|
||||
setListeners(this, options);
|
||||
let worker = new Worker(stripListeners(options));
|
||||
workers.set(this, worker);
|
||||
|
||||
// pipe events from worker to a panel.
|
||||
pipe(worker, this);
|
||||
},
|
||||
dispose: function dispose() {
|
||||
this.hide();
|
||||
off(this);
|
||||
|
||||
workerFor(this).destroy();
|
||||
detach(styleFor(this));
|
||||
|
||||
domPanel.dispose(viewFor(this));
|
||||
|
||||
// Release circular reference between view and panel instance. This
|
||||
// way view will be GC-ed. And panel as well once all the other refs
|
||||
// will be removed from it.
|
||||
views.delete(this);
|
||||
},
|
||||
/* Public API: Panel.width */
|
||||
get width() {
|
||||
return modelFor(this).width;
|
||||
},
|
||||
set width(value) {
|
||||
this.resize(value, this.height);
|
||||
},
|
||||
/* Public API: Panel.height */
|
||||
get height() {
|
||||
return modelFor(this).height;
|
||||
},
|
||||
set height(value) {
|
||||
this.resize(this.width, value);
|
||||
},
|
||||
|
||||
/* Public API: Panel.focus */
|
||||
get focus() {
|
||||
return modelFor(this).focus;
|
||||
},
|
||||
|
||||
/* Public API: Panel.position */
|
||||
get position() {
|
||||
return modelFor(this).position;
|
||||
},
|
||||
|
||||
/* Public API: Panel.contextMenu */
|
||||
get contextMenu() {
|
||||
return modelFor(this).contextMenu;
|
||||
},
|
||||
set contextMenu(allow) {
|
||||
let model = modelFor(this);
|
||||
model.contextMenu = panelContract({ contextMenu: allow }).contextMenu;
|
||||
domPanel.allowContextMenu(viewFor(this), model.contextMenu);
|
||||
},
|
||||
|
||||
get contentURL() {
|
||||
return modelFor(this).contentURL;
|
||||
},
|
||||
set contentURL(value) {
|
||||
let model = modelFor(this);
|
||||
model.contentURL = panelContract({ contentURL: value }).contentURL;
|
||||
domPanel.setURL(viewFor(this), model.contentURL);
|
||||
// Detach worker so that messages send will be queued until it's
|
||||
// reatached once panel content is ready.
|
||||
workerFor(this).detach();
|
||||
},
|
||||
|
||||
get allow() { return Allow(this); },
|
||||
set allow(value) {
|
||||
let allowJavascript = panelContract({ allow: value }).allow.script;
|
||||
return setScriptState(this, value);
|
||||
},
|
||||
|
||||
/* Public API: Panel.isShowing */
|
||||
get isShowing() {
|
||||
return !isDisposed(this) && domPanel.isOpen(viewFor(this));
|
||||
},
|
||||
|
||||
/* Public API: Panel.show */
|
||||
show: function show(options={}, anchor) {
|
||||
SinglePanelManager.requestOpen(this, () => {
|
||||
if (options instanceof Ci.nsIDOMElement) {
|
||||
[anchor, options] = [options, null];
|
||||
}
|
||||
|
||||
if (anchor instanceof Ci.nsIDOMElement) {
|
||||
console.warn(
|
||||
"Passing a DOM node to Panel.show() method is an unsupported " +
|
||||
"feature that will be soon replaced. " +
|
||||
"See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877"
|
||||
);
|
||||
}
|
||||
|
||||
let model = modelFor(this);
|
||||
let view = viewFor(this);
|
||||
let anchorView = getNodeView(anchor || options.position || model.position);
|
||||
|
||||
options = merge({
|
||||
position: model.position,
|
||||
width: model.width,
|
||||
height: model.height,
|
||||
defaultWidth: model.defaultWidth,
|
||||
defaultHeight: model.defaultHeight,
|
||||
focus: model.focus,
|
||||
contextMenu: model.contextMenu
|
||||
}, displayContract(options));
|
||||
|
||||
if (!isDisposed(this)) {
|
||||
domPanel.show(view, options, anchorView);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/* Public API: Panel.hide */
|
||||
hide: function hide() {
|
||||
// Quit immediately if panel is disposed or there is no state change.
|
||||
domPanel.close(viewFor(this));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/* Public API: Panel.resize */
|
||||
resize: function resize(width, height) {
|
||||
let model = modelFor(this);
|
||||
let view = viewFor(this);
|
||||
let change = panelContract({
|
||||
width: width || model.width || model.defaultWidth,
|
||||
height: height || model.height || model.defaultHeight
|
||||
});
|
||||
|
||||
model.width = change.width
|
||||
model.height = change.height
|
||||
|
||||
domPanel.resize(view, model.width, model.height);
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
exports.Panel = Panel;
|
||||
|
||||
// Note must be defined only after value to `Panel` is assigned.
|
||||
getActiveView.define(Panel, viewFor);
|
||||
|
||||
// Filter panel events to only panels that are create by this module.
|
||||
var panelEvents = filter(events, ({target}) => panelFor(target));
|
||||
|
||||
// Panel events emitted after panel has being shown.
|
||||
var shows = filter(panelEvents, ({type}) => type === "popupshown");
|
||||
|
||||
// Panel events emitted after panel became hidden.
|
||||
var hides = filter(panelEvents, ({type}) => type === "popuphidden");
|
||||
|
||||
// Panel events emitted after content inside panel is ready. For different
|
||||
// panels ready may mean different state based on `contentScriptWhen` attribute.
|
||||
// Weather given event represents readyness is detected by `getAttachEventType`
|
||||
// helper function.
|
||||
var ready = filter(panelEvents, ({type, target}) =>
|
||||
getAttachEventType(modelFor(panelFor(target))) === type);
|
||||
|
||||
// Panel event emitted when the contents of the panel has been loaded.
|
||||
var readyToShow = filter(panelEvents, ({type}) => type === "DOMContentLoaded");
|
||||
|
||||
// Styles should be always added as soon as possible, and doesn't makes them
|
||||
// depends on `contentScriptWhen`
|
||||
var start = filter(panelEvents, ({type}) => type === "document-element-inserted");
|
||||
|
||||
// Forward panel show / hide events to panel's own event listeners.
|
||||
on(shows, "data", ({target}) => {
|
||||
let panel = panelFor(target);
|
||||
if (modelFor(panel).ready)
|
||||
emit(panel, "show");
|
||||
});
|
||||
|
||||
on(hides, "data", ({target}) => {
|
||||
let panel = panelFor(target);
|
||||
if (modelFor(panel).ready)
|
||||
emit(panel, "hide");
|
||||
});
|
||||
|
||||
on(ready, "data", ({target}) => {
|
||||
let panel = panelFor(target);
|
||||
let window = domPanel.getContentDocument(target).defaultView;
|
||||
|
||||
workerFor(panel).attach(window);
|
||||
});
|
||||
|
||||
on(readyToShow, "data", ({target}) => {
|
||||
let panel = panelFor(target);
|
||||
|
||||
if (!modelFor(panel).ready) {
|
||||
modelFor(panel).ready = true;
|
||||
|
||||
if (viewFor(panel).state == "open")
|
||||
emit(panel, "show");
|
||||
}
|
||||
});
|
||||
|
||||
on(start, "data", ({target}) => {
|
||||
let panel = panelFor(target);
|
||||
let window = domPanel.getContentDocument(target).defaultView;
|
||||
|
||||
attach(styleFor(panel), window);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue