summaryrefslogtreecommitdiffstats
path: root/addon-sdk/source/lib/sdk/ui
diff options
context:
space:
mode:
Diffstat (limited to 'addon-sdk/source/lib/sdk/ui')
-rw-r--r--addon-sdk/source/lib/sdk/ui/button/action.js114
-rw-r--r--addon-sdk/source/lib/sdk/ui/button/contract.js73
-rw-r--r--addon-sdk/source/lib/sdk/ui/button/toggle.js127
-rw-r--r--addon-sdk/source/lib/sdk/ui/button/view.js243
-rw-r--r--addon-sdk/source/lib/sdk/ui/button/view/events.js18
-rw-r--r--addon-sdk/source/lib/sdk/ui/component.js182
-rw-r--r--addon-sdk/source/lib/sdk/ui/frame.js16
-rw-r--r--addon-sdk/source/lib/sdk/ui/frame/model.js154
-rw-r--r--addon-sdk/source/lib/sdk/ui/frame/view.html18
-rw-r--r--addon-sdk/source/lib/sdk/ui/frame/view.js150
-rw-r--r--addon-sdk/source/lib/sdk/ui/id.js27
-rw-r--r--addon-sdk/source/lib/sdk/ui/sidebar.js311
-rw-r--r--addon-sdk/source/lib/sdk/ui/sidebar/actions.js10
-rw-r--r--addon-sdk/source/lib/sdk/ui/sidebar/contract.js27
-rw-r--r--addon-sdk/source/lib/sdk/ui/sidebar/namespace.js15
-rw-r--r--addon-sdk/source/lib/sdk/ui/sidebar/utils.js8
-rw-r--r--addon-sdk/source/lib/sdk/ui/sidebar/view.js214
-rw-r--r--addon-sdk/source/lib/sdk/ui/state.js239
-rw-r--r--addon-sdk/source/lib/sdk/ui/state/events.js18
-rw-r--r--addon-sdk/source/lib/sdk/ui/toolbar.js16
-rw-r--r--addon-sdk/source/lib/sdk/ui/toolbar/model.js151
-rw-r--r--addon-sdk/source/lib/sdk/ui/toolbar/view.js248
22 files changed, 0 insertions, 2379 deletions
diff --git a/addon-sdk/source/lib/sdk/ui/button/action.js b/addon-sdk/source/lib/sdk/ui/button/action.js
deleted file mode 100644
index dfb092d0c..000000000
--- a/addon-sdk/source/lib/sdk/ui/button/action.js
+++ /dev/null
@@ -1,114 +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';
-
-module.metadata = {
- 'stability': 'experimental',
- 'engines': {
- 'Firefox': '> 28'
- }
-};
-
-const { Class } = require('../../core/heritage');
-const { merge } = require('../../util/object');
-const { Disposable } = require('../../core/disposable');
-const { on, off, emit, setListeners } = require('../../event/core');
-const { EventTarget } = require('../../event/target');
-const { getNodeView } = require('../../view/core');
-
-const view = require('./view');
-const { buttonContract, stateContract } = require('./contract');
-const { properties, render, state, register, unregister,
- getDerivedStateFor } = require('../state');
-const { events: stateEvents } = require('../state/events');
-const { events: viewEvents } = require('./view/events');
-const events = require('../../event/utils');
-
-const { getActiveTab } = require('../../tabs/utils');
-
-const { id: addonID } = require('../../self');
-const { identify } = require('../id');
-
-const buttons = new Map();
-
-const toWidgetId = id =>
- ('action-button--' + addonID.toLowerCase()+ '-' + id).
- replace(/[^a-z0-9_-]/g, '');
-
-const ActionButton = Class({
- extends: EventTarget,
- implements: [
- properties(stateContract),
- state(stateContract),
- Disposable
- ],
- setup: function setup(options) {
- let state = merge({
- disabled: false
- }, buttonContract(options));
-
- let id = toWidgetId(options.id);
-
- register(this, state);
-
- // Setup listeners.
- setListeners(this, options);
-
- buttons.set(id, this);
-
- view.create(merge({}, state, { id: id }));
- },
-
- dispose: function dispose() {
- let id = toWidgetId(this.id);
- buttons.delete(id);
-
- off(this);
-
- view.dispose(id);
-
- unregister(this);
- },
-
- get id() {
- return this.state().id;
- },
-
- click: function click() { view.click(toWidgetId(this.id)) }
-});
-exports.ActionButton = ActionButton;
-
-identify.define(ActionButton, ({id}) => toWidgetId(id));
-
-getNodeView.define(ActionButton, button =>
- view.nodeFor(toWidgetId(button.id))
-);
-
-var actionButtonStateEvents = events.filter(stateEvents,
- e => e.target instanceof ActionButton);
-
-var actionButtonViewEvents = events.filter(viewEvents,
- e => buttons.has(e.target));
-
-var clickEvents = events.filter(actionButtonViewEvents, e => e.type === 'click');
-var updateEvents = events.filter(actionButtonViewEvents, e => e.type === 'update');
-
-on(clickEvents, 'data', ({target: id, window}) => {
- let button = buttons.get(id);
- let state = getDerivedStateFor(button, getActiveTab(window));
-
- emit(button, 'click', state);
-});
-
-on(updateEvents, 'data', ({target: id, window}) => {
- render(buttons.get(id), window);
-});
-
-on(actionButtonStateEvents, 'data', ({target, window, state}) => {
- let id = toWidgetId(target.id);
- view.setIcon(id, window, state.icon);
- view.setLabel(id, window, state.label);
- view.setDisabled(id, window, state.disabled);
- view.setBadge(id, window, state.badge, state.badgeColor);
-});
diff --git a/addon-sdk/source/lib/sdk/ui/button/contract.js b/addon-sdk/source/lib/sdk/ui/button/contract.js
deleted file mode 100644
index ce6e33d95..000000000
--- a/addon-sdk/source/lib/sdk/ui/button/contract.js
+++ /dev/null
@@ -1,73 +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 { contract } = require('../../util/contract');
-const { isLocalURL } = require('../../url');
-const { isNil, isObject, isString } = require('../../lang/type');
-const { required, either, string, boolean, object, number } = require('../../deprecated/api-utils');
-const { merge } = require('../../util/object');
-const { freeze } = Object;
-
-const isIconSet = (icons) =>
- Object.keys(icons).
- every(size => String(size >>> 0) === size && isLocalURL(icons[size]));
-
-var iconSet = {
- is: either(object, string),
- map: v => isObject(v) ? freeze(merge({}, v)) : v,
- ok: v => (isString(v) && isLocalURL(v)) || (isObject(v) && isIconSet(v)),
- msg: 'The option "icon" must be a local URL or an object with ' +
- 'numeric keys / local URL values pair.'
-}
-
-var id = {
- is: string,
- ok: v => /^[a-z-_][a-z0-9-_]*$/i.test(v),
- msg: 'The option "id" must be a valid alphanumeric id (hyphens and ' +
- 'underscores are allowed).'
-};
-
-var label = {
- is: string,
- ok: v => isNil(v) || v.trim().length > 0,
- msg: 'The option "label" must be a non empty string'
-}
-
-var badge = {
- is: either(string, number),
- msg: 'The option "badge" must be a string or a number'
-}
-
-var badgeColor = {
- is: string,
- msg: 'The option "badgeColor" must be a string'
-}
-
-var stateContract = contract({
- label: label,
- icon: iconSet,
- disabled: boolean,
- badge: badge,
- badgeColor: badgeColor
-});
-
-exports.stateContract = stateContract;
-
-var buttonContract = contract(merge({}, stateContract.rules, {
- id: required(id),
- label: required(label),
- icon: required(iconSet)
-}));
-
-exports.buttonContract = buttonContract;
-
-exports.toggleStateContract = contract(merge({
- checked: boolean
-}, stateContract.rules));
-
-exports.toggleButtonContract = contract(merge({
- checked: boolean
-}, buttonContract.rules));
-
diff --git a/addon-sdk/source/lib/sdk/ui/button/toggle.js b/addon-sdk/source/lib/sdk/ui/button/toggle.js
deleted file mode 100644
index a226b3212..000000000
--- a/addon-sdk/source/lib/sdk/ui/button/toggle.js
+++ /dev/null
@@ -1,127 +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';
-
-module.metadata = {
- 'stability': 'experimental',
- 'engines': {
- 'Firefox': '> 28'
- }
-};
-
-const { Class } = require('../../core/heritage');
-const { merge } = require('../../util/object');
-const { Disposable } = require('../../core/disposable');
-const { on, off, emit, setListeners } = require('../../event/core');
-const { EventTarget } = require('../../event/target');
-const { getNodeView } = require('../../view/core');
-
-const view = require('./view');
-const { toggleButtonContract, toggleStateContract } = require('./contract');
-const { properties, render, state, register, unregister,
- setStateFor, getStateFor, getDerivedStateFor } = require('../state');
-const { events: stateEvents } = require('../state/events');
-const { events: viewEvents } = require('./view/events');
-const events = require('../../event/utils');
-
-const { getActiveTab } = require('../../tabs/utils');
-
-const { id: addonID } = require('../../self');
-const { identify } = require('../id');
-
-const buttons = new Map();
-
-const toWidgetId = id =>
- ('toggle-button--' + addonID.toLowerCase()+ '-' + id).
- replace(/[^a-z0-9_-]/g, '');
-
-const ToggleButton = Class({
- extends: EventTarget,
- implements: [
- properties(toggleStateContract),
- state(toggleStateContract),
- Disposable
- ],
- setup: function setup(options) {
- let state = merge({
- disabled: false,
- checked: false
- }, toggleButtonContract(options));
-
- let id = toWidgetId(options.id);
-
- register(this, state);
-
- // Setup listeners.
- setListeners(this, options);
-
- buttons.set(id, this);
-
- view.create(merge({ type: 'checkbox' }, state, { id: id }));
- },
-
- dispose: function dispose() {
- let id = toWidgetId(this.id);
- buttons.delete(id);
-
- off(this);
-
- view.dispose(id);
-
- unregister(this);
- },
-
- get id() {
- return this.state().id;
- },
-
- click: function click() {
- return view.click(toWidgetId(this.id));
- }
-});
-exports.ToggleButton = ToggleButton;
-
-identify.define(ToggleButton, ({id}) => toWidgetId(id));
-
-getNodeView.define(ToggleButton, button =>
- view.nodeFor(toWidgetId(button.id))
-);
-
-var toggleButtonStateEvents = events.filter(stateEvents,
- e => e.target instanceof ToggleButton);
-
-var toggleButtonViewEvents = events.filter(viewEvents,
- e => buttons.has(e.target));
-
-var clickEvents = events.filter(toggleButtonViewEvents, e => e.type === 'click');
-var updateEvents = events.filter(toggleButtonViewEvents, e => e.type === 'update');
-
-on(toggleButtonStateEvents, 'data', ({target, window, state}) => {
- let id = toWidgetId(target.id);
-
- view.setIcon(id, window, state.icon);
- view.setLabel(id, window, state.label);
- view.setDisabled(id, window, state.disabled);
- view.setChecked(id, window, state.checked);
- view.setBadge(id, window, state.badge, state.badgeColor);
-});
-
-on(clickEvents, 'data', ({target: id, window, checked }) => {
- let button = buttons.get(id);
- let windowState = getStateFor(button, window);
-
- let newWindowState = merge({}, windowState, { checked: checked });
-
- setStateFor(button, window, newWindowState);
-
- let state = getDerivedStateFor(button, getActiveTab(window));
-
- emit(button, 'click', state);
-
- emit(button, 'change', state);
-});
-
-on(updateEvents, 'data', ({target: id, window}) => {
- render(buttons.get(id), window);
-});
diff --git a/addon-sdk/source/lib/sdk/ui/button/view.js b/addon-sdk/source/lib/sdk/ui/button/view.js
deleted file mode 100644
index 63b7aea31..000000000
--- a/addon-sdk/source/lib/sdk/ui/button/view.js
+++ /dev/null
@@ -1,243 +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';
-
-module.metadata = {
- 'stability': 'experimental',
- 'engines': {
- 'Firefox': '> 28'
- }
-};
-
-const { Cu } = require('chrome');
-const { on, off, emit } = require('../../event/core');
-
-const { data } = require('sdk/self');
-
-const { isObject, isNil } = require('../../lang/type');
-
-const { getMostRecentBrowserWindow } = require('../../window/utils');
-const { ignoreWindow } = require('../../private-browsing/utils');
-const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
-const { AREA_PANEL, AREA_NAVBAR } = CustomizableUI;
-
-const { events: viewEvents } = require('./view/events');
-
-const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
-
-const views = new Map();
-const customizedWindows = new WeakMap();
-
-const buttonListener = {
- onCustomizeStart: window => {
- for (let [id, view] of views) {
- setIcon(id, window, view.icon);
- setLabel(id, window, view.label);
- }
-
- customizedWindows.set(window, true);
- },
- onCustomizeEnd: window => {
- customizedWindows.delete(window);
-
- for (let [id, ] of views) {
- let placement = CustomizableUI.getPlacementOfWidget(id);
-
- if (placement)
- emit(viewEvents, 'data', { type: 'update', target: id, window: window });
- }
- },
- onWidgetAfterDOMChange: (node, nextNode, container) => {
- let { id } = node;
- let view = views.get(id);
- let window = node.ownerDocument.defaultView;
-
- if (view) {
- emit(viewEvents, 'data', { type: 'update', target: id, window: window });
- }
- }
-};
-
-CustomizableUI.addListener(buttonListener);
-
-require('../../system/unload').when( _ =>
- CustomizableUI.removeListener(buttonListener)
-);
-
-function getNode(id, window) {
- return !views.has(id) || ignoreWindow(window)
- ? null
- : CustomizableUI.getWidget(id).forWindow(window).node
-};
-
-function isInToolbar(id) {
- let placement = CustomizableUI.getPlacementOfWidget(id);
-
- return placement && CustomizableUI.getAreaType(placement.area) === 'toolbar';
-}
-
-
-function getImage(icon, isInToolbar, pixelRatio) {
- let targetSize = (isInToolbar ? 18 : 32) * pixelRatio;
- let bestSize = 0;
- let image = icon;
-
- if (isObject(icon)) {
- for (let size of Object.keys(icon)) {
- size = +size;
- let offset = targetSize - size;
-
- if (offset === 0) {
- bestSize = size;
- break;
- }
-
- let delta = Math.abs(offset) - Math.abs(targetSize - bestSize);
-
- if (delta < 0)
- bestSize = size;
- }
-
- image = icon[bestSize];
- }
-
- if (image.indexOf('./') === 0)
- return data.url(image.substr(2));
-
- return image;
-}
-
-function nodeFor(id, window=getMostRecentBrowserWindow()) {
- return customizedWindows.has(window) ? null : getNode(id, window);
-};
-exports.nodeFor = nodeFor;
-
-function create(options) {
- let { id, label, icon, type, badge } = options;
-
- if (views.has(id))
- throw new Error('The ID "' + id + '" seems already used.');
-
- CustomizableUI.createWidget({
- id: id,
- type: 'custom',
- removable: true,
- defaultArea: AREA_NAVBAR,
- allowedAreas: [ AREA_PANEL, AREA_NAVBAR ],
-
- onBuild: function(document) {
- let window = document.defaultView;
-
- let node = document.createElementNS(XUL_NS, 'toolbarbutton');
-
- let image = getImage(icon, true, window.devicePixelRatio);
-
- if (ignoreWindow(window))
- node.style.display = 'none';
-
- node.setAttribute('id', this.id);
- node.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional badged-button');
- node.setAttribute('type', type);
- node.setAttribute('label', label);
- node.setAttribute('tooltiptext', label);
- node.setAttribute('image', image);
- node.setAttribute('constrain-size', 'true');
-
- views.set(id, {
- area: this.currentArea,
- icon: icon,
- label: label
- });
-
- node.addEventListener('command', function(event) {
- if (views.has(id)) {
- emit(viewEvents, 'data', {
- type: 'click',
- target: id,
- window: event.view,
- checked: node.checked
- });
- }
- });
-
- return node;
- }
- });
-};
-exports.create = create;
-
-function dispose(id) {
- if (!views.has(id)) return;
-
- views.delete(id);
- CustomizableUI.destroyWidget(id);
-}
-exports.dispose = dispose;
-
-function setIcon(id, window, icon) {
- let node = getNode(id, window);
-
- if (node) {
- icon = customizedWindows.has(window) ? views.get(id).icon : icon;
- let image = getImage(icon, isInToolbar(id), window.devicePixelRatio);
-
- node.setAttribute('image', image);
- }
-}
-exports.setIcon = setIcon;
-
-function setLabel(id, window, label) {
- let node = nodeFor(id, window);
-
- if (node) {
- node.setAttribute('label', label);
- node.setAttribute('tooltiptext', label);
- }
-}
-exports.setLabel = setLabel;
-
-function setDisabled(id, window, disabled) {
- let node = nodeFor(id, window);
-
- if (node)
- node.disabled = disabled;
-}
-exports.setDisabled = setDisabled;
-
-function setChecked(id, window, checked) {
- let node = nodeFor(id, window);
-
- if (node)
- node.checked = checked;
-}
-exports.setChecked = setChecked;
-
-function setBadge(id, window, badge, color) {
- let node = nodeFor(id, window);
-
- if (node) {
- // `Array.from` is needed to handle unicode symbol properly:
- // '𝐀𝐁'.length is 4 where Array.from('𝐀𝐁').length is 2
- let text = isNil(badge)
- ? ''
- : Array.from(String(badge)).slice(0, 4).join('');
-
- node.setAttribute('badge', text);
-
- let badgeNode = node.ownerDocument.getAnonymousElementByAttribute(node,
- 'class', 'toolbarbutton-badge');
-
- if (badgeNode)
- badgeNode.style.backgroundColor = isNil(color) ? '' : color;
- }
-}
-exports.setBadge = setBadge;
-
-function click(id) {
- let node = nodeFor(id);
-
- if (node)
- node.click();
-}
-exports.click = click;
diff --git a/addon-sdk/source/lib/sdk/ui/button/view/events.js b/addon-sdk/source/lib/sdk/ui/button/view/events.js
deleted file mode 100644
index 98909656a..000000000
--- a/addon-sdk/source/lib/sdk/ui/button/view/events.js
+++ /dev/null
@@ -1,18 +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';
-
-module.metadata = {
- 'stability': 'experimental',
- 'engines': {
- 'Firefox': '*',
- 'SeaMonkey': '*',
- 'Thunderbird': '*'
- }
-};
-
-var channel = {};
-
-exports.events = channel;
diff --git a/addon-sdk/source/lib/sdk/ui/component.js b/addon-sdk/source/lib/sdk/ui/component.js
deleted file mode 100644
index d1f12c95e..000000000
--- a/addon-sdk/source/lib/sdk/ui/component.js
+++ /dev/null
@@ -1,182 +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";
-
-// Internal properties not exposed to the public.
-const cache = Symbol("component/cache");
-const writer = Symbol("component/writer");
-const isFirstWrite = Symbol("component/writer/first-write?");
-const currentState = Symbol("component/state/current");
-const pendingState = Symbol("component/state/pending");
-const isWriting = Symbol("component/writing?");
-
-const isntNull = x => x !== null;
-
-const Component = function(options, children) {
- this[currentState] = null;
- this[pendingState] = null;
- this[writer] = null;
- this[cache] = null;
- this[isFirstWrite] = true;
-
- this[Component.construct](options, children);
-}
-Component.Component = Component;
-// Constructs component.
-Component.construct = Symbol("component/construct");
-// Called with `options` and `children` and must return
-// initial state back.
-Component.initial = Symbol("component/initial");
-
-// Function patches current `state` with a given update.
-Component.patch = Symbol("component/patch");
-// Function that replaces current `state` with a passed state.
-Component.reset = Symbol("component/reset");
-
-// Function that must return render tree from passed state.
-Component.render = Symbol("component/render");
-
-// Path of the component with in the mount point.
-Component.path = Symbol("component/path");
-
-Component.isMounted = component => !!component[writer];
-Component.isWriting = component => !!component[isWriting];
-
-// Internal method that mounts component to a writer.
-// Mounts component to a writer.
-Component.mount = (component, write) => {
- if (Component.isMounted(component)) {
- throw Error("Can not mount already mounted component");
- }
-
- component[writer] = write;
- Component.write(component);
-
- if (component[Component.mounted]) {
- component[Component.mounted]();
- }
-}
-
-// Unmounts component from a writer.
-Component.unmount = (component) => {
- if (Component.isMounted(component)) {
- component[writer] = null;
- if (component[Component.unmounted]) {
- component[Component.unmounted]();
- }
- } else {
- console.warn("Unmounting component that is not mounted is redundant");
- }
-};
- // Method invoked once after inital write occurs.
-Component.mounted = Symbol("component/mounted");
-// Internal method that unmounts component from the writer.
-Component.unmounted = Symbol("component/unmounted");
-// Function that must return true if component is changed
-Component.isUpdated = Symbol("component/updated?");
-Component.update = Symbol("component/update");
-Component.updated = Symbol("component/updated");
-
-const writeChild = base => (child, index) => Component.write(child, base, index)
-Component.write = (component, base, index) => {
- if (component === null) {
- return component;
- }
-
- if (!(component instanceof Component)) {
- const path = base ? `${base}${component.key || index}/` : `/`;
- return Object.assign({}, component, {
- [Component.path]: path,
- children: component.children && component.children.
- map(writeChild(path)).
- filter(isntNull)
- });
- }
-
- component[isWriting] = true;
-
- try {
-
- const current = component[currentState];
- const pending = component[pendingState] || current;
- const isUpdated = component[Component.isUpdated];
- const isInitial = component[isFirstWrite];
-
- if (isUpdated(current, pending) || isInitial) {
- if (!isInitial && component[Component.update]) {
- component[Component.update](pending, current)
- }
-
- // Note: [Component.update] could have caused more updates so can't use
- // `pending` as `component[pendingState]` may have changed.
- component[currentState] = component[pendingState] || current;
- component[pendingState] = null;
-
- const tree = component[Component.render](component[currentState]);
- component[cache] = Component.write(tree, base, index);
- if (component[writer]) {
- component[writer].call(null, component[cache]);
- }
-
- if (!isInitial && component[Component.updated]) {
- component[Component.updated](current, pending);
- }
- }
-
- component[isFirstWrite] = false;
-
- return component[cache];
- } finally {
- component[isWriting] = false;
- }
-};
-
-Component.prototype = Object.freeze({
- constructor: Component,
-
- [Component.mounted]: null,
- [Component.unmounted]: null,
- [Component.update]: null,
- [Component.updated]: null,
-
- get state() {
- return this[pendingState] || this[currentState];
- },
-
-
- [Component.construct](settings, items) {
- const initial = this[Component.initial];
- const base = initial(settings, items);
- const options = Object.assign(Object.create(null), base.options, settings);
- const children = base.children || items || null;
- const state = Object.assign(Object.create(null), base, {options, children});
- this[currentState] = state;
-
- if (this.setup) {
- this.setup(state);
- }
- },
- [Component.initial](options, children) {
- return Object.create(null);
- },
- [Component.patch](update) {
- this[Component.reset](Object.assign({}, this.state, update));
- },
- [Component.reset](state) {
- this[pendingState] = state;
- if (Component.isMounted(this) && !Component.isWriting(this)) {
- Component.write(this);
- }
- },
-
- [Component.isUpdated](before, after) {
- return before != after
- },
-
- [Component.render](state) {
- throw Error("Component must implement [Component.render] member");
- }
-});
-
-module.exports = Component;
diff --git a/addon-sdk/source/lib/sdk/ui/frame.js b/addon-sdk/source/lib/sdk/ui/frame.js
deleted file mode 100644
index 566353cdf..000000000
--- a/addon-sdk/source/lib/sdk/ui/frame.js
+++ /dev/null
@@ -1,16 +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";
-
-module.metadata = {
- "stability": "experimental",
- "engines": {
- "Firefox": "> 28"
- }
-};
-
-require("./frame/view");
-const { Frame } = require("./frame/model");
-
-exports.Frame = Frame;
diff --git a/addon-sdk/source/lib/sdk/ui/frame/model.js b/addon-sdk/source/lib/sdk/ui/frame/model.js
deleted file mode 100644
index 627310874..000000000
--- a/addon-sdk/source/lib/sdk/ui/frame/model.js
+++ /dev/null
@@ -1,154 +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";
-
-module.metadata = {
- "stability": "experimental",
- "engines": {
- "Firefox": "> 28"
- }
-};
-
-const { Class } = require("../../core/heritage");
-const { EventTarget } = require("../../event/target");
-const { emit, off, setListeners } = require("../../event/core");
-const { Reactor, foldp, send, merges } = require("../../event/utils");
-const { Disposable } = require("../../core/disposable");
-const { OutputPort } = require("../../output/system");
-const { InputPort } = require("../../input/system");
-const { identify } = require("../id");
-const { pairs, object, map, each } = require("../../util/sequence");
-const { patch, diff } = require("diffpatcher/index");
-const { isLocalURL } = require("../../url");
-const { compose } = require("../../lang/functional");
-const { contract } = require("../../util/contract");
-const { id: addonID, data: { url: resolve }} = require("../../self");
-const { Frames } = require("../../input/frame");
-
-
-const output = new OutputPort({ id: "frame-change" });
-const mailbox = new OutputPort({ id: "frame-mailbox" });
-const input = Frames;
-
-
-const makeID = url =>
- ("frame-" + addonID + "-" + url).
- split("/").join("-").
- split(".").join("-").
- replace(/[^A-Za-z0-9_\-]/g, "");
-
-const validate = contract({
- name: {
- is: ["string", "undefined"],
- ok: x => /^[a-z][a-z0-9-_]+$/i.test(x),
- msg: "The `option.name` must be a valid alphanumeric string (hyphens and " +
- "underscores are allowed) starting with letter."
- },
- url: {
- map: x => x.toString(),
- is: ["string"],
- ok: x => isLocalURL(x),
- msg: "The `options.url` must be a valid local URI."
- }
-});
-
-const Source = function({id, ownerID}) {
- this.id = id;
- this.ownerID = ownerID;
-};
-Source.postMessage = ({id, ownerID}, data, origin) => {
- send(mailbox, object([id, {
- inbox: {
- target: {id: id, ownerID: ownerID},
- timeStamp: Date.now(),
- data: data,
- origin: origin
- }
- }]));
-};
-Source.prototype.postMessage = function(data, origin) {
- Source.postMessage(this, data, origin);
-};
-
-const Message = function({type, data, source, origin, timeStamp}) {
- this.type = type;
- this.data = data;
- this.origin = origin;
- this.timeStamp = timeStamp;
- this.source = new Source(source);
-};
-
-
-const frames = new Map();
-const sources = new Map();
-
-const Frame = Class({
- extends: EventTarget,
- implements: [Disposable, Source],
- initialize: function(params={}) {
- const options = validate(params);
- const id = makeID(options.name || options.url);
-
- if (frames.has(id))
- throw Error("Frame with this id already exists: " + id);
-
- const initial = { id: id, url: resolve(options.url) };
- this.id = id;
-
- setListeners(this, params);
-
- frames.set(this.id, this);
-
- send(output, object([id, initial]));
- },
- get url() {
- const state = reactor.value[this.id];
- return state && state.url;
- },
- destroy: function() {
- send(output, object([this.id, null]));
- frames.delete(this.id);
- off(this);
- },
- // `JSON.stringify` serializes objects based of the return
- // value of this method. For convinienc we provide this method
- // to serialize actual state data.
- toJSON: function() {
- return { id: this.id, url: this.url };
- }
-});
-identify.define(Frame, frame => frame.id);
-
-exports.Frame = Frame;
-
-const reactor = new Reactor({
- onStep: (present, past) => {
- const delta = diff(past, present);
-
- each(([id, update]) => {
- const frame = frames.get(id);
- if (update) {
- if (!past[id])
- emit(frame, "register");
-
- if (update.outbox)
- emit(frame, "message", new Message(present[id].outbox));
-
- each(([ownerID, state]) => {
- const readyState = state ? state.readyState : "detach";
- const type = readyState === "loading" ? "attach" :
- readyState === "interactive" ? "ready" :
- readyState === "complete" ? "load" :
- readyState;
-
- // TODO: Cache `Source` instances somewhere to preserve
- // identity.
- emit(frame, type, {type: type,
- source: new Source({id: id, ownerID: ownerID})});
- }, pairs(update.owners));
- }
- }, pairs(delta));
- }
-});
-reactor.run(input);
diff --git a/addon-sdk/source/lib/sdk/ui/frame/view.html b/addon-sdk/source/lib/sdk/ui/frame/view.html
deleted file mode 100644
index 2a405b583..000000000
--- a/addon-sdk/source/lib/sdk/ui/frame/view.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <script>
- // HACK: This is not an ideal way to deliver chrome messages
- // to an inner frame content but seems only way that would
- // make `event.source` this (outer frame) window.
- window.onmessage = function(event) {
- var frame = document.querySelector("iframe");
- var content = frame.contentWindow;
- // If message is posted from chrome it has no `event.source`.
- if (event.source === null)
- content.postMessage(event.data, "*");
- };
- </script>
- </head>
- <body style="overflow: hidden"></body>
-</html>
diff --git a/addon-sdk/source/lib/sdk/ui/frame/view.js b/addon-sdk/source/lib/sdk/ui/frame/view.js
deleted file mode 100644
index 2eb4df2b7..000000000
--- a/addon-sdk/source/lib/sdk/ui/frame/view.js
+++ /dev/null
@@ -1,150 +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";
-
-module.metadata = {
- "stability": "experimental",
- "engines": {
- "Firefox": "> 28"
- }
-};
-
-const { Cu, Ci } = require("chrome");
-const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
-const { subscribe, send, Reactor, foldp, lift, merges, keepIf } = require("../../event/utils");
-const { InputPort } = require("../../input/system");
-const { OutputPort } = require("../../output/system");
-const { LastClosed } = require("../../input/browser");
-const { pairs, keys, object, each } = require("../../util/sequence");
-const { curry, compose } = require("../../lang/functional");
-const { getFrameElement, getOuterId,
- getByOuterId, getOwnerBrowserWindow } = require("../../window/utils");
-const { patch, diff } = require("diffpatcher/index");
-const { encode } = require("../../base64");
-const { Frames } = require("../../input/frame");
-
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const HTML_NS = "http://www.w3.org/1999/xhtml";
-const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html");
-
-const mailbox = new OutputPort({ id: "frame-mailbox" });
-
-const frameID = frame => frame.id.replace("outer-", "");
-const windowID = compose(getOuterId, getOwnerBrowserWindow);
-
-const getOuterFrame = (windowID, frameID) =>
- getByOuterId(windowID).document.getElementById("outer-" + frameID);
-
-const listener = ({target, source, data, origin, timeStamp}) => {
- // And sent received message to outbox so that frame API model
- // will deal with it.
- if (source && source !== target) {
- const frame = getFrameElement(target);
- const id = frameID(frame);
- send(mailbox, object([id, {
- outbox: {type: "message",
- source: {id: id, ownerID: windowID(frame)},
- data: data,
- origin: origin,
- timeStamp: timeStamp}}]));
- }
-};
-
-// Utility function used to create frame with a given `state` and
-// inject it into given `window`.
-const registerFrame = ({id, url}) => {
- CustomizableUI.createWidget({
- id: id,
- type: "custom",
- removable: true,
- onBuild: document => {
- let view = document.createElementNS(XUL_NS, "toolbaritem");
- view.setAttribute("id", id);
- view.setAttribute("flex", 2);
-
- let outerFrame = document.createElementNS(XUL_NS, "iframe");
- outerFrame.setAttribute("src", OUTER_FRAME_URI);
- outerFrame.setAttribute("id", "outer-" + id);
- outerFrame.setAttribute("data-is-sdk-outer-frame", true);
- outerFrame.setAttribute("type", "content");
- outerFrame.setAttribute("transparent", true);
- outerFrame.setAttribute("flex", 2);
- outerFrame.setAttribute("style", "overflow: hidden;");
- outerFrame.setAttribute("scrolling", "no");
- outerFrame.setAttribute("disablehistory", true);
- outerFrame.setAttribute("seamless", "seamless");
- outerFrame.addEventListener("load", function onload() {
- outerFrame.removeEventListener("load", onload, true);
-
- let doc = outerFrame.contentDocument;
-
- let innerFrame = doc.createElementNS(HTML_NS, "iframe");
- innerFrame.setAttribute("id", id);
- innerFrame.setAttribute("src", url);
- innerFrame.setAttribute("seamless", "seamless");
- innerFrame.setAttribute("sandbox", "allow-scripts");
- innerFrame.setAttribute("scrolling", "no");
- innerFrame.setAttribute("data-is-sdk-inner-frame", true);
- innerFrame.setAttribute("style", [ "border:none",
- "position:absolute", "width:100%", "top: 0",
- "left: 0", "overflow: hidden"].join(";"));
-
- doc.body.appendChild(innerFrame);
- }, true);
-
- view.appendChild(outerFrame);
-
- return view;
- }
- });
-};
-
-const unregisterFrame = CustomizableUI.destroyWidget;
-
-const deliverMessage = curry((frameID, data, windowID) => {
- const frame = getOuterFrame(windowID, frameID);
- const content = frame && frame.contentWindow;
-
- if (content)
- content.postMessage(data, content.location.origin);
-});
-
-const updateFrame = (id, {inbox, owners}, present) => {
- if (inbox) {
- const { data, target:{ownerID}, source } = present[id].inbox;
- if (ownerID)
- deliverMessage(id, data, ownerID);
- else
- each(deliverMessage(id, data), keys(present[id].owners));
- }
-
- each(setupView(id), pairs(owners));
-};
-
-const setupView = curry((frameID, [windowID, state]) => {
- if (state && state.readyState === "loading") {
- const frame = getOuterFrame(windowID, frameID);
- // Setup a message listener on contentWindow.
- frame.contentWindow.addEventListener("message", listener);
- }
-});
-
-
-const reactor = new Reactor({
- onStep: (present, past) => {
- const delta = diff(past, present);
-
- // Apply frame changes
- each(([id, update]) => {
- if (update === null)
- unregisterFrame(id);
- else if (past[id])
- updateFrame(id, update, present);
- else
- registerFrame(update);
- }, pairs(delta));
- },
- onEnd: state => each(unregisterFrame, keys(state))
-});
-reactor.run(Frames);
diff --git a/addon-sdk/source/lib/sdk/ui/id.js b/addon-sdk/source/lib/sdk/ui/id.js
deleted file mode 100644
index d17eb0a4e..000000000
--- a/addon-sdk/source/lib/sdk/ui/id.js
+++ /dev/null
@@ -1,27 +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';
-
-module.metadata = {
- 'stability': 'experimental'
-};
-
-const method = require('../../method/core');
-const { uuid } = require('../util/uuid');
-
-// NOTE: use lang/functional memoize when it is updated to use WeakMap
-function memoize(f) {
- const memo = new WeakMap();
-
- return function memoizer(o) {
- let key = o;
- if (!memo.has(key))
- memo.set(key, f.apply(this, arguments));
- return memo.get(key);
- };
-}
-
-var identify = method('identify');
-identify.define(Object, memoize(function() { return uuid(); }));
-exports.identify = identify;
diff --git a/addon-sdk/source/lib/sdk/ui/sidebar.js b/addon-sdk/source/lib/sdk/ui/sidebar.js
deleted file mode 100644
index 59e35ea11..000000000
--- a/addon-sdk/source/lib/sdk/ui/sidebar.js
+++ /dev/null
@@ -1,311 +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';
-
-module.metadata = {
- 'stability': 'experimental',
- 'engines': {
- 'Firefox': '*'
- }
-};
-
-const { Class } = require('../core/heritage');
-const { merge } = require('../util/object');
-const { Disposable } = require('../core/disposable');
-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, data } = require('../self');
-const { WindowTracker } = require('../deprecated/window-utils');
-const { isShowing } = require('./sidebar/utils');
-const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = require('../window/utils');
-const { ns } = require('../core/namespace');
-const { remove: removeFromArray } = require('../util/array');
-const { show, hide, toggle } = require('./sidebar/actions');
-const { Worker } = require('../deprecated/sync-worker');
-const { contract: sidebarContract } = require('./sidebar/contract');
-const { create, dispose, updateTitle, updateURL, isSidebarShowing, showSidebar, hideSidebar } = require('./sidebar/view');
-const { defer } = require('../core/promise');
-const { models, views, viewsFor, modelFor } = require('./sidebar/namespace');
-const { isLocalURL } = require('../url');
-const { ensure } = require('../system/unload');
-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';
-
-var sidebars = {};
-
-const Sidebar = Class({
- implements: [ Disposable ],
- extends: EventTarget,
- setup: function(options) {
- // inital validation for the model information
- let model = sidebarContract(options);
-
- // save the model information
- models.set(this, model);
-
- // generate an id if one was not provided
- model.id = model.id || addonID + '-' + uuid();
-
- // further validation for the title and url
- validateTitleAndURLCombo({}, this.title, this.url);
-
- const self = this;
- const internals = sidebarNS(self);
- const windowNS = internals.windowNS = ns();
-
- // see bug https://bugzilla.mozilla.org/show_bug.cgi?id=886148
- ensure(this, 'destroy');
-
- setListeners(this, options);
-
- let bars = [];
- internals.tracker = WindowTracker({
- onTrack: function(window) {
- if (!isBrowser(window))
- return;
-
- let sidebar = window.document.getElementById('sidebar');
- let sidebarBox = window.document.getElementById('sidebar-box');
-
- let bar = create(window, {
- id: self.id,
- title: self.title,
- sidebarurl: self.url
- });
- bars.push(bar);
- windowNS(window).bar = bar;
-
- bar.addEventListener('command', function() {
- if (isSidebarShowing(window, self)) {
- hideSidebar(window, self).catch(() => {});
- return;
- }
-
- showSidebar(window, self);
- }, false);
-
- function onSidebarLoad() {
- // check if the sidebar is ready
- let isReady = sidebar.docShell && sidebar.contentDocument;
- if (!isReady)
- return;
-
- // check if it is a web panel
- let panelBrowser = sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
- if (!panelBrowser) {
- bar.removeAttribute('checked');
- return;
- }
-
- let sbTitle = window.document.getElementById('sidebar-title');
- function onWebPanelSidebarCreated() {
- if (panelBrowser.contentWindow.location != resolveURL(model.url) ||
- sbTitle.value != model.title) {
- return;
- }
-
- let worker = windowNS(window).worker = Worker({
- window: panelBrowser.contentWindow,
- injectInDocument: true
- });
-
- function onWebPanelSidebarUnload() {
- windowNS(window).onWebPanelSidebarUnload = null;
-
- // uncheck the associated menuitem
- bar.setAttribute('checked', 'false');
-
- emit(self, 'hide', {});
- emit(self, 'detach', worker);
- windowNS(window).worker = null;
- }
- windowNS(window).onWebPanelSidebarUnload = onWebPanelSidebarUnload;
- panelBrowser.contentWindow.addEventListener('unload', onWebPanelSidebarUnload, true);
-
- // check the associated menuitem
- bar.setAttribute('checked', 'true');
-
- function onWebPanelSidebarReady() {
- panelBrowser.contentWindow.removeEventListener('DOMContentLoaded', onWebPanelSidebarReady, false);
- windowNS(window).onWebPanelSidebarReady = null;
-
- emit(self, 'ready', worker);
- }
- windowNS(window).onWebPanelSidebarReady = onWebPanelSidebarReady;
- panelBrowser.contentWindow.addEventListener('DOMContentLoaded', onWebPanelSidebarReady, false);
-
- function onWebPanelSidebarLoad() {
- panelBrowser.contentWindow.removeEventListener('load', onWebPanelSidebarLoad, true);
- windowNS(window).onWebPanelSidebarLoad = null;
-
- // TODO: decide if returning worker is acceptable..
- //emit(self, 'show', { worker: worker });
- emit(self, 'show', {});
- }
- windowNS(window).onWebPanelSidebarLoad = onWebPanelSidebarLoad;
- panelBrowser.contentWindow.addEventListener('load', onWebPanelSidebarLoad, true);
-
- emit(self, 'attach', worker);
- }
- windowNS(window).onWebPanelSidebarCreated = onWebPanelSidebarCreated;
- panelBrowser.addEventListener('DOMWindowCreated', onWebPanelSidebarCreated, true);
- }
- windowNS(window).onSidebarLoad = onSidebarLoad;
- sidebar.addEventListener('load', onSidebarLoad, true); // removed properly
- },
- onUntrack: function(window) {
- if (!isBrowser(window))
- return;
-
- // hide the sidebar if it is showing
- hideSidebar(window, self).catch(() => {});
-
- // kill the menu item
- let { bar } = windowNS(window);
- if (bar) {
- removeFromArray(viewsFor(self), bar);
- dispose(bar);
- }
-
- // kill listeners
- let sidebar = window.document.getElementById('sidebar');
-
- if (windowNS(window).onSidebarLoad) {
- sidebar && sidebar.removeEventListener('load', windowNS(window).onSidebarLoad, true)
- windowNS(window).onSidebarLoad = null;
- }
-
- let panelBrowser = sidebar && sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
- if (windowNS(window).onWebPanelSidebarCreated) {
- panelBrowser && panelBrowser.removeEventListener('DOMWindowCreated', windowNS(window).onWebPanelSidebarCreated, true);
- windowNS(window).onWebPanelSidebarCreated = null;
- }
-
- if (windowNS(window).onWebPanelSidebarReady) {
- panelBrowser && panelBrowser.contentWindow.removeEventListener('DOMContentLoaded', windowNS(window).onWebPanelSidebarReady, false);
- windowNS(window).onWebPanelSidebarReady = null;
- }
-
- if (windowNS(window).onWebPanelSidebarLoad) {
- panelBrowser && panelBrowser.contentWindow.removeEventListener('load', windowNS(window).onWebPanelSidebarLoad, true);
- windowNS(window).onWebPanelSidebarLoad = null;
- }
-
- if (windowNS(window).onWebPanelSidebarUnload) {
- panelBrowser && panelBrowser.contentWindow.removeEventListener('unload', windowNS(window).onWebPanelSidebarUnload, true);
- windowNS(window).onWebPanelSidebarUnload();
- }
- }
- });
-
- views.set(this, bars);
-
- add(sidebars, this);
- },
- get id() {
- return (modelFor(this) || {}).id;
- },
- get title() {
- return (modelFor(this) || {}).title;
- },
- set title(v) {
- // destroyed?
- if (!modelFor(this))
- return;
- // validation
- if (typeof v != 'string')
- throw Error('title must be a string');
- validateTitleAndURLCombo(this, v, this.url);
- // do update
- updateTitle(this, v);
- return modelFor(this).title = v;
- },
- get url() {
- return (modelFor(this) || {}).url;
- },
- set url(v) {
- // destroyed?
- if (!modelFor(this))
- return;
-
- // validation
- if (!isLocalURL(v))
- throw Error('the url must be a valid local url');
-
- validateTitleAndURLCombo(this, this.title, v);
-
- // do update
- updateURL(this, v);
- modelFor(this).url = v;
- },
- show: function(window) {
- return showSidebar(viewFor(window), this);
- },
- hide: function(window) {
- return hideSidebar(viewFor(window), this);
- },
- dispose: function() {
- const internals = sidebarNS(this);
-
- off(this);
-
- remove(sidebars, this);
-
- // stop tracking windows
- if (internals.tracker) {
- internals.tracker.unload();
- }
-
- internals.tracker = null;
- internals.windowNS = null;
-
- views.delete(this);
- models.delete(this);
- }
-});
-exports.Sidebar = Sidebar;
-
-function validateTitleAndURLCombo(sidebar, title, url) {
- url = resolveURL(url);
-
- if (sidebar.title == title && sidebar.url == url) {
- return false;
- }
-
- for (let window of windows(null, { includePrivate: true })) {
- let sidebar = window.document.querySelector('menuitem[sidebarurl="' + url + '"][label="' + title + '"]');
- if (sidebar) {
- throw Error('The provided title and url combination is invalid (already used).');
- }
- }
-
- return false;
-}
-
-isShowing.define(Sidebar, isSidebarShowing.bind(null, null));
-show.define(Sidebar, showSidebar.bind(null, null));
-hide.define(Sidebar, hideSidebar.bind(null, null));
-
-identify.define(Sidebar, function(sidebar) {
- return sidebar.id;
-});
-
-function toggleSidebar(window, sidebar) {
- // TODO: make sure this is not private
- window = window || getMostRecentBrowserWindow();
- if (isSidebarShowing(window, sidebar)) {
- return hideSidebar(window, sidebar);
- }
- return showSidebar(window, sidebar);
-}
-toggle.define(Sidebar, toggleSidebar.bind(null, null));
diff --git a/addon-sdk/source/lib/sdk/ui/sidebar/actions.js b/addon-sdk/source/lib/sdk/ui/sidebar/actions.js
deleted file mode 100644
index 4a52984c9..000000000
--- a/addon-sdk/source/lib/sdk/ui/sidebar/actions.js
+++ /dev/null
@@ -1,10 +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 method = require('../../../method/core');
-
-exports.show = method('show');
-exports.hide = method('hide');
-exports.toggle = method('toggle');
diff --git a/addon-sdk/source/lib/sdk/ui/sidebar/contract.js b/addon-sdk/source/lib/sdk/ui/sidebar/contract.js
deleted file mode 100644
index b59c37c0b..000000000
--- a/addon-sdk/source/lib/sdk/ui/sidebar/contract.js
+++ /dev/null
@@ -1,27 +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 { contract } = require('../../util/contract');
-const { isValidURI, URL, isLocalURL } = require('../../url');
-const { isNil, isObject, isString } = require('../../lang/type');
-
-exports.contract = contract({
- id: {
- is: [ 'string', 'undefined' ],
- ok: v => /^[a-z0-9-_]+$/i.test(v),
- msg: 'The option "id" must be a valid alphanumeric id (hyphens and ' +
- 'underscores are allowed).'
- },
- title: {
- is: [ 'string' ],
- ok: v => v.length
- },
- url: {
- is: [ 'string' ],
- ok: v => isLocalURL(v),
- map: v => v.toString(),
- msg: 'The option "url" must be a valid local URI.'
- }
-});
diff --git a/addon-sdk/source/lib/sdk/ui/sidebar/namespace.js b/addon-sdk/source/lib/sdk/ui/sidebar/namespace.js
deleted file mode 100644
index d79725d1a..000000000
--- a/addon-sdk/source/lib/sdk/ui/sidebar/namespace.js
+++ /dev/null
@@ -1,15 +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 models = exports.models = new WeakMap();
-const views = exports.views = new WeakMap();
-exports.buttons = new WeakMap();
-
-exports.viewsFor = function viewsFor(sidebar) {
- return views.get(sidebar);
-};
-exports.modelFor = function modelFor(sidebar) {
- return models.get(sidebar);
-};
diff --git a/addon-sdk/source/lib/sdk/ui/sidebar/utils.js b/addon-sdk/source/lib/sdk/ui/sidebar/utils.js
deleted file mode 100644
index d6145c32e..000000000
--- a/addon-sdk/source/lib/sdk/ui/sidebar/utils.js
+++ /dev/null
@@ -1,8 +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 method = require('../../../method/core');
-
-exports.isShowing = method('isShowing');
diff --git a/addon-sdk/source/lib/sdk/ui/sidebar/view.js b/addon-sdk/source/lib/sdk/ui/sidebar/view.js
deleted file mode 100644
index c91e69d3d..000000000
--- a/addon-sdk/source/lib/sdk/ui/sidebar/view.js
+++ /dev/null
@@ -1,214 +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';
-
-module.metadata = {
- 'stability': 'unstable',
- 'engines': {
- 'Firefox': '*'
- }
-};
-
-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, 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;
-
- if (document.getElementById(id))
- throw new Error('The ID "' + details.id + '" seems already used.');
-
- let menuitem = document.createElementNS(XUL_NS, 'menuitem');
- menuitem.setAttribute('id', id);
- menuitem.setAttribute('label', details.title);
- menuitem.setAttribute('sidebarurl', resolveURL(details.sidebarurl));
- menuitem.setAttribute('checked', 'false');
- menuitem.setAttribute('type', 'checkbox');
- menuitem.setAttribute('group', 'sidebar');
- menuitem.setAttribute('autoCheck', 'false');
-
- document.getElementById('viewSidebarMenu').appendChild(menuitem);
-
- return menuitem;
-}
-exports.create = create;
-
-function dispose(menuitem) {
- menuitem.parentNode.removeChild(menuitem);
-}
-exports.dispose = dispose;
-
-function updateTitle(sidebar, title) {
- let button = buttons.get(sidebar);
-
- for (let window of windows(null, { includePrivate: true })) {
- let { document } = window;
-
- // update the button
- if (button) {
- setStateFor(button, window, { label: title });
- }
-
- // update the menuitem
- let mi = document.getElementById(makeID(sidebar.id));
- if (mi) {
- mi.setAttribute('label', title)
- }
-
- // update sidebar, if showing
- if (isSidebarShowing(window, sidebar)) {
- document.getElementById('sidebar-title').setAttribute('value', title);
- }
- }
-}
-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);
- if (mi) {
- mi.setAttribute('sidebarurl', url)
- }
-
- // update sidebar, if showing
- if (isSidebarShowing(window, sidebar)) {
- showSidebar(window, sidebar, url);
- }
- }
-}
-exports.updateURL = updateURL;
-
-function isSidebarShowing(window, sidebar) {
- let win = window || getMostRecentBrowserWindow();
-
- // make sure there is a window
- if (!win) {
- return false;
- }
-
- // make sure there is a sidebar for the window
- let sb = win.document.getElementById('sidebar');
- let sidebarTitle = win.document.getElementById('sidebar-title');
- if (!(sb && sidebarTitle)) {
- return false;
- }
-
- // checks if the sidebar box is hidden
- let sbb = win.document.getElementById('sidebar-box');
- if (!sbb || sbb.hidden) {
- return false;
- }
-
- if (sidebarTitle.value == modelFor(sidebar).title) {
- let url = resolveURL(modelFor(sidebar).url);
-
- // checks if the sidebar is loading
- if (win.gWebPanelURI == url) {
- return true;
- }
-
- // checks if the sidebar loaded already
- let ele = sb.contentDocument && sb.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
- if (!ele) {
- return false;
- }
-
- if (ele.getAttribute('cachedurl') == url) {
- return true;
- }
-
- if (ele && ele.contentWindow && ele.contentWindow.location == url) {
- return true;
- }
- }
-
- // default
- return false;
-}
-exports.isSidebarShowing = isSidebarShowing;
-
-function showSidebar(window, sidebar, newURL) {
- window = window || getMostRecentBrowserWindow();
-
- let { promise, resolve, reject } = defer();
- let model = modelFor(sidebar);
-
- if (!newURL && isSidebarShowing(window, sidebar)) {
- resolve({});
- }
- else if (!isPrivateBrowsingSupported && isWindowPrivate(window)) {
- reject(Error('You cannot show a sidebar on private windows'));
- }
- else {
- sidebar.once('show', resolve);
-
- let menuitem = window.document.getElementById(makeID(model.id));
- menuitem.setAttribute('checked', true);
-
- window.openWebPanel(model.title, resolveURL(newURL || model.url));
- }
-
- return promise;
-}
-exports.showSidebar = showSidebar;
-
-
-function hideSidebar(window, sidebar) {
- window = window || getMostRecentBrowserWindow();
-
- let { promise, resolve, reject } = defer();
-
- if (!isSidebarShowing(window, sidebar)) {
- reject(Error('The sidebar is already hidden'));
- }
- else {
- sidebar.once('hide', resolve);
-
- // Below was taken from http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#4775
- // the code for window.todggleSideBar()..
- let { document } = window;
- let sidebarEle = document.getElementById('sidebar');
- let sidebarTitle = document.getElementById('sidebar-title');
- let sidebarBox = document.getElementById('sidebar-box');
- let sidebarSplitter = document.getElementById('sidebar-splitter');
- let commandID = sidebarBox.getAttribute('sidebarcommand');
- let sidebarBroadcaster = document.getElementById(commandID);
-
- sidebarBox.hidden = true;
- sidebarSplitter.hidden = true;
-
- sidebarEle.setAttribute('src', 'about:blank');
- //sidebarEle.docShell.createAboutBlankContentViewer(null);
-
- sidebarBroadcaster.removeAttribute('checked');
- sidebarBox.setAttribute('sidebarcommand', '');
- sidebarTitle.value = '';
- sidebarBox.hidden = true;
- sidebarSplitter.hidden = true;
-
- // TODO: perhaps this isn't necessary if the window is not most recent?
- window.gBrowser.selectedBrowser.focus();
- }
-
- return promise;
-}
-exports.hideSidebar = hideSidebar;
-
-function makeID(id) {
- return 'jetpack-sidebar-' + id;
-}
diff --git a/addon-sdk/source/lib/sdk/ui/state.js b/addon-sdk/source/lib/sdk/ui/state.js
deleted file mode 100644
index 152ce696d..000000000
--- a/addon-sdk/source/lib/sdk/ui/state.js
+++ /dev/null
@@ -1,239 +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 Button module currently supports only Firefox.
-// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
-module.metadata = {
- 'stability': 'experimental',
- 'engines': {
- 'Firefox': '*',
- 'SeaMonkey': '*',
- 'Thunderbird': '*'
- }
-};
-
-const { Ci } = require('chrome');
-
-const events = require('../event/utils');
-const { events: browserEvents } = require('../browser/events');
-const { events: tabEvents } = require('../tab/events');
-const { events: stateEvents } = require('./state/events');
-
-const { windows, isInteractive, getFocusedBrowser } = require('../window/utils');
-const { getActiveTab, getOwnerWindow } = require('../tabs/utils');
-
-const { ignoreWindow } = require('../private-browsing/utils');
-
-const { freeze } = Object;
-const { merge } = require('../util/object');
-const { on, off, emit } = require('../event/core');
-
-const { add, remove, has, clear, iterator } = require('../lang/weak-set');
-const { isNil } = require('../lang/type');
-
-const { viewFor } = require('../view/core');
-
-const components = new WeakMap();
-
-const ERR_UNREGISTERED = 'The state cannot be set or get. ' +
- 'The object may be not be registered, or may already have been unloaded.';
-
-const ERR_INVALID_TARGET = 'The state cannot be set or get for this target.' +
- 'Only window, tab and registered component are valid targets.';
-
-const isWindow = thing => thing instanceof Ci.nsIDOMWindow;
-const isTab = thing => thing.tagName && thing.tagName.toLowerCase() === 'tab';
-const isActiveTab = thing => isTab(thing) && thing === getActiveTab(getOwnerWindow(thing));
-const isEnumerable = window => !ignoreWindow(window);
-const browsers = _ =>
- windows('navigator:browser', { includePrivate: true }).filter(isInteractive);
-const getMostRecentTab = _ => getActiveTab(getFocusedBrowser());
-
-function getStateFor(component, target) {
- if (!isRegistered(component))
- throw new Error(ERR_UNREGISTERED);
-
- if (!components.has(component))
- return null;
-
- let states = components.get(component);
-
- if (target) {
- if (isTab(target) || isWindow(target) || target === component)
- return states.get(target) || null;
- else
- throw new Error(ERR_INVALID_TARGET);
- }
-
- return null;
-}
-exports.getStateFor = getStateFor;
-
-function getDerivedStateFor(component, target) {
- if (!isRegistered(component))
- throw new Error(ERR_UNREGISTERED);
-
- if (!components.has(component))
- return null;
-
- let states = components.get(component);
-
- let componentState = states.get(component);
- let windowState = null;
- let tabState = null;
-
- if (target) {
- // has a target
- if (isTab(target)) {
- windowState = states.get(getOwnerWindow(target), null);
-
- if (states.has(target)) {
- // we have a tab state
- tabState = states.get(target);
- }
- }
- else if (isWindow(target) && states.has(target)) {
- // we have a window state
- windowState = states.get(target);
- }
- }
-
- return freeze(merge({}, componentState, windowState, tabState));
-}
-exports.getDerivedStateFor = getDerivedStateFor;
-
-function setStateFor(component, target, state) {
- if (!isRegistered(component))
- throw new Error(ERR_UNREGISTERED);
-
- let isComponentState = target === component;
- let targetWindows = isWindow(target) ? [target] :
- isActiveTab(target) ? [getOwnerWindow(target)] :
- isComponentState ? browsers() :
- isTab(target) ? [] :
- null;
-
- if (!targetWindows)
- throw new Error(ERR_INVALID_TARGET);
-
- // initialize the state's map
- if (!components.has(component))
- components.set(component, new WeakMap());
-
- let states = components.get(component);
-
- if (state === null && !isComponentState) // component state can't be deleted
- states.delete(target);
- else {
- let base = isComponentState ? states.get(target) : null;
- states.set(target, freeze(merge({}, base, state)));
- }
-
- render(component, targetWindows);
-}
-exports.setStateFor = setStateFor;
-
-function render(component, targetWindows) {
- targetWindows = targetWindows ? [].concat(targetWindows) : browsers();
-
- for (let window of targetWindows.filter(isEnumerable)) {
- let tabState = getDerivedStateFor(component, getActiveTab(window));
-
- emit(stateEvents, 'data', {
- type: 'render',
- target: component,
- window: window,
- state: tabState
- });
-
- }
-}
-exports.render = render;
-
-function properties(contract) {
- let { rules } = contract;
- let descriptor = Object.keys(rules).reduce(function(descriptor, name) {
- descriptor[name] = {
- get: function() { return getDerivedStateFor(this)[name] },
- set: function(value) {
- let changed = {};
- changed[name] = value;
-
- setStateFor(this, this, contract(changed));
- }
- }
- return descriptor;
- }, {});
-
- return Object.create(Object.prototype, descriptor);
-}
-exports.properties = properties;
-
-function state(contract) {
- return {
- state: function state(target, state) {
- let nativeTarget = target === 'window' ? getFocusedBrowser()
- : target === 'tab' ? getMostRecentTab()
- : target === this ? null
- : viewFor(target);
-
- if (!nativeTarget && target !== this && !isNil(target))
- throw new Error(ERR_INVALID_TARGET);
-
- target = nativeTarget || target;
-
- // jquery style
- return arguments.length < 2
- ? getDerivedStateFor(this, target)
- : setStateFor(this, target, contract(state))
- }
- }
-}
-exports.state = state;
-
-const register = (component, state) => {
- add(components, component);
- setStateFor(component, component, state);
-}
-exports.register = register;
-
-const unregister = component => {
- remove(components, component);
-}
-exports.unregister = unregister;
-
-const isRegistered = component => has(components, component);
-exports.isRegistered = isRegistered;
-
-var tabSelect = events.filter(tabEvents, e => e.type === 'TabSelect');
-var tabClose = events.filter(tabEvents, e => e.type === 'TabClose');
-var windowOpen = events.filter(browserEvents, e => e.type === 'load');
-var windowClose = events.filter(browserEvents, e => e.type === 'close');
-
-var close = events.merge([tabClose, windowClose]);
-var activate = events.merge([windowOpen, tabSelect]);
-
-on(activate, 'data', ({target}) => {
- let [window, tab] = isWindow(target)
- ? [target, getActiveTab(target)]
- : [getOwnerWindow(target), target];
-
- if (ignoreWindow(window)) return;
-
- for (let component of iterator(components)) {
- emit(stateEvents, 'data', {
- type: 'render',
- target: component,
- window: window,
- state: getDerivedStateFor(component, tab)
- });
- }
-});
-
-on(close, 'data', function({target}) {
- for (let component of iterator(components)) {
- components.get(component).delete(target);
- }
-});
diff --git a/addon-sdk/source/lib/sdk/ui/state/events.js b/addon-sdk/source/lib/sdk/ui/state/events.js
deleted file mode 100644
index 98909656a..000000000
--- a/addon-sdk/source/lib/sdk/ui/state/events.js
+++ /dev/null
@@ -1,18 +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';
-
-module.metadata = {
- 'stability': 'experimental',
- 'engines': {
- 'Firefox': '*',
- 'SeaMonkey': '*',
- 'Thunderbird': '*'
- }
-};
-
-var channel = {};
-
-exports.events = channel;
diff --git a/addon-sdk/source/lib/sdk/ui/toolbar.js b/addon-sdk/source/lib/sdk/ui/toolbar.js
deleted file mode 100644
index c1becab2d..000000000
--- a/addon-sdk/source/lib/sdk/ui/toolbar.js
+++ /dev/null
@@ -1,16 +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";
-
-module.metadata = {
- "stability": "experimental",
- "engines": {
- "Firefox": "> 28"
- }
-};
-
-const { Toolbar } = require("./toolbar/model");
-require("./toolbar/view");
-
-exports.Toolbar = Toolbar;
diff --git a/addon-sdk/source/lib/sdk/ui/toolbar/model.js b/addon-sdk/source/lib/sdk/ui/toolbar/model.js
deleted file mode 100644
index 5c5428606..000000000
--- a/addon-sdk/source/lib/sdk/ui/toolbar/model.js
+++ /dev/null
@@ -1,151 +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";
-
-module.metadata = {
- "stability": "experimental",
- "engines": {
- "Firefox": "> 28"
- }
-};
-
-const { Class } = require("../../core/heritage");
-const { EventTarget } = require("../../event/target");
-const { off, setListeners, emit } = require("../../event/core");
-const { Reactor, foldp, merges, send } = require("../../event/utils");
-const { Disposable } = require("../../core/disposable");
-const { InputPort } = require("../../input/system");
-const { OutputPort } = require("../../output/system");
-const { identify } = require("../id");
-const { pairs, object, map, each } = require("../../util/sequence");
-const { patch, diff } = require("diffpatcher/index");
-const { contract } = require("../../util/contract");
-const { id: addonID } = require("../../self");
-
-// Input state is accumulated from the input received form the toolbar
-// view code & local output. Merging local output reflects local state
-// changes without complete roundloop.
-const input = foldp(patch, {}, new InputPort({ id: "toolbar-changed" }));
-const output = new OutputPort({ id: "toolbar-change" });
-
-// Takes toolbar title and normalizes is to an
-// identifier, also prefixes with add-on id.
-const titleToId = title =>
- ("toolbar-" + addonID + "-" + title).
- toLowerCase().
- replace(/\s/g, "-").
- replace(/[^A-Za-z0-9_\-]/g, "");
-
-const validate = contract({
- title: {
- is: ["string"],
- ok: x => x.length > 0,
- msg: "The `option.title` string must be provided"
- },
- items: {
- is:["undefined", "object", "array"],
- msg: "The `options.items` must be iterable sequence of items"
- },
- hidden: {
- is: ["boolean", "undefined"],
- msg: "The `options.hidden` must be boolean"
- }
-});
-
-// Toolbars is a mapping between `toolbar.id` & `toolbar` instances,
-// which is used to find intstance for dispatching events.
-var toolbars = new Map();
-
-const Toolbar = Class({
- extends: EventTarget,
- implements: [Disposable],
- initialize: function(params={}) {
- const options = validate(params);
- const id = titleToId(options.title);
-
- if (toolbars.has(id))
- throw Error("Toolbar with this id already exists: " + id);
-
- // Set of the items in the toolbar isn't mutable, as a matter of fact
- // it just defines desired set of items, actual set is under users
- // control. Conver test to an array and freeze to make sure users won't
- // try mess with it.
- const items = Object.freeze(options.items ? [...options.items] : []);
-
- const initial = {
- id: id,
- title: options.title,
- // By default toolbars are visible when add-on is installed, unless
- // add-on authors decides it should be hidden. From that point on
- // user is in control.
- collapsed: !!options.hidden,
- // In terms of state only identifiers of items matter.
- items: items.map(identify)
- };
-
- this.id = id;
- this.items = items;
-
- toolbars.set(id, this);
- setListeners(this, params);
-
- // Send initial state to the host so it can reflect it
- // into a user interface.
- send(output, object([id, initial]));
- },
-
- get title() {
- const state = reactor.value[this.id];
- return state && state.title;
- },
- get hidden() {
- const state = reactor.value[this.id];
- return state && state.collapsed;
- },
-
- destroy: function() {
- send(output, object([this.id, null]));
- },
- // `JSON.stringify` serializes objects based of the return
- // value of this method. For convinienc we provide this method
- // to serialize actual state data. Note: items will also be
- // serialized so they should probably implement `toJSON`.
- toJSON: function() {
- return {
- id: this.id,
- title: this.title,
- hidden: this.hidden,
- items: this.items
- };
- }
-});
-exports.Toolbar = Toolbar;
-identify.define(Toolbar, toolbar => toolbar.id);
-
-const dispose = toolbar => {
- toolbars.delete(toolbar.id);
- emit(toolbar, "detach");
- off(toolbar);
-};
-
-const reactor = new Reactor({
- onStep: (present, past) => {
- const delta = diff(past, present);
-
- each(([id, update]) => {
- const toolbar = toolbars.get(id);
-
- // Remove
- if (!update)
- dispose(toolbar);
- // Add
- else if (!past[id])
- emit(toolbar, "attach");
- // Update
- else
- emit(toolbar, update.collapsed ? "hide" : "show", toolbar);
- }, pairs(delta));
- }
-});
-reactor.run(input);
diff --git a/addon-sdk/source/lib/sdk/ui/toolbar/view.js b/addon-sdk/source/lib/sdk/ui/toolbar/view.js
deleted file mode 100644
index 4ef0c3d46..000000000
--- a/addon-sdk/source/lib/sdk/ui/toolbar/view.js
+++ /dev/null
@@ -1,248 +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";
-
-module.metadata = {
- "stability": "experimental",
- "engines": {
- "Firefox": "> 28"
- }
-};
-
-const { Cu } = require("chrome");
-const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
-const { subscribe, send, Reactor, foldp, lift, merges } = require("../../event/utils");
-const { InputPort } = require("../../input/system");
-const { OutputPort } = require("../../output/system");
-const { Interactive } = require("../../input/browser");
-const { CustomizationInput } = require("../../input/customizable-ui");
-const { pairs, map, isEmpty, object,
- each, keys, values } = require("../../util/sequence");
-const { curry, flip } = require("../../lang/functional");
-const { patch, diff } = require("diffpatcher/index");
-const prefs = require("../../preferences/service");
-const { getByOuterId } = require("../../window/utils");
-const { ignoreWindow } = require('../../private-browsing/utils');
-
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const PREF_ROOT = "extensions.sdk-toolbar-collapsed.";
-
-
-// There are two output ports one for publishing changes that occured
-// and the other for change requests. Later is synchronous and is only
-// consumed here. Note: it needs to be synchronous to avoid race conditions
-// when `collapsed` attribute changes are caused by user interaction and
-// toolbar is destroyed between the ticks.
-const output = new OutputPort({ id: "toolbar-changed" });
-const syncoutput = new OutputPort({ id: "toolbar-change", sync: true });
-
-// Merge disptached changes and recevied changes from models to keep state up to
-// date.
-const Toolbars = foldp(patch, {}, merges([new InputPort({ id: "toolbar-changed" }),
- new InputPort({ id: "toolbar-change" })]));
-const State = lift((toolbars, windows, customizable) =>
- ({windows: windows, toolbars: toolbars, customizable: customizable}),
- Toolbars, Interactive, new CustomizationInput());
-
-// Shared event handler that makes `event.target.parent` collapsed.
-// Used as toolbar's close buttons click handler.
-const collapseToolbar = event => {
- const toolbar = event.target.parentNode;
- toolbar.collapsed = true;
-};
-
-const parseAttribute = x =>
- x === "true" ? true :
- x === "false" ? false :
- x === "" ? null :
- x;
-
-// Shared mutation observer that is used to observe `toolbar` node's
-// attribute mutations. Mutations are aggregated in the `delta` hash
-// and send to `ToolbarStateChanged` channel to let model know state
-// has changed.
-const attributesChanged = mutations => {
- const delta = mutations.reduce((changes, {attributeName, target}) => {
- const id = target.id;
- const field = attributeName === "toolbarname" ? "title" : attributeName;
- let change = changes[id] || (changes[id] = {});
- change[field] = parseAttribute(target.getAttribute(attributeName));
- return changes;
- }, {});
-
- // Calculate what are the updates from the current state and if there are
- // any send them.
- const updates = diff(reactor.value, patch(reactor.value, delta));
-
- if (!isEmpty(pairs(updates))) {
- // TODO: Consider sending sync to make sure that there won't be a new
- // update doing a delete in the meantime.
- send(syncoutput, updates);
- }
-};
-
-
-// Utility function creates `toolbar` with a "close" button and returns
-// it back. In addition it set's up a listener and observer to communicate
-// state changes.
-const addView = curry((options, {document, window}) => {
- if (ignoreWindow(window))
- return;
-
- let view = document.createElementNS(XUL_NS, "toolbar");
- view.setAttribute("id", options.id);
- view.setAttribute("collapsed", options.collapsed);
- view.setAttribute("toolbarname", options.title);
- view.setAttribute("pack", "end");
- view.setAttribute("customizable", "false");
- view.setAttribute("style", "padding: 2px 0; max-height: 40px;");
- view.setAttribute("mode", "icons");
- view.setAttribute("iconsize", "small");
- view.setAttribute("context", "toolbar-context-menu");
- view.setAttribute("class", "chromeclass-toolbar");
-
- let label = document.createElementNS(XUL_NS, "label");
- label.setAttribute("value", options.title);
- label.setAttribute("collapsed", "true");
- view.appendChild(label);
-
- let closeButton = document.createElementNS(XUL_NS, "toolbarbutton");
- closeButton.setAttribute("id", "close-" + options.id);
- closeButton.setAttribute("class", "close-icon");
- closeButton.setAttribute("customizable", false);
- closeButton.addEventListener("command", collapseToolbar);
-
- view.appendChild(closeButton);
-
- // In order to have a close button not costumizable, aligned on the right,
- // leaving the customizable capabilities of Australis, we need to create
- // a toolbar inside a toolbar.
- // This is should be a temporary hack, we should have a proper XBL for toolbar
- // instead. See:
- // https://bugzilla.mozilla.org/show_bug.cgi?id=982005
- let toolbar = document.createElementNS(XUL_NS, "toolbar");
- toolbar.setAttribute("id", "inner-" + options.id);
- toolbar.setAttribute("defaultset", options.items.join(","));
- toolbar.setAttribute("customizable", "true");
- toolbar.setAttribute("style", "-moz-appearance: none; overflow: hidden");
- toolbar.setAttribute("mode", "icons");
- toolbar.setAttribute("iconsize", "small");
- toolbar.setAttribute("context", "toolbar-context-menu");
- toolbar.setAttribute("flex", "1");
-
- view.insertBefore(toolbar, closeButton);
-
- const observer = new document.defaultView.MutationObserver(attributesChanged);
- observer.observe(view, { attributes: true,
- attributeFilter: ["collapsed", "toolbarname"] });
-
- const toolbox = document.getElementById("navigator-toolbox");
- toolbox.appendChild(view);
-});
-const viewAdd = curry(flip(addView));
-
-const removeView = curry((id, {document}) => {
- const view = document.getElementById(id);
- if (view) view.remove();
-});
-
-const updateView = curry((id, {title, collapsed, isCustomizing}, {document}) => {
- const view = document.getElementById(id);
-
- if (!view)
- return;
-
- if (title)
- view.setAttribute("toolbarname", title);
-
- if (collapsed !== void(0))
- view.setAttribute("collapsed", Boolean(collapsed));
-
- if (isCustomizing !== void(0)) {
- view.querySelector("label").collapsed = !isCustomizing;
- view.querySelector("toolbar").style.visibility = isCustomizing
- ? "hidden" : "visible";
- }
-});
-
-const viewUpdate = curry(flip(updateView));
-
-// Utility function used to register toolbar into CustomizableUI.
-const registerToolbar = state => {
- // If it's first additon register toolbar as customizableUI component.
- CustomizableUI.registerArea("inner-" + state.id, {
- type: CustomizableUI.TYPE_TOOLBAR,
- legacy: true,
- defaultPlacements: [...state.items]
- });
-};
-// Utility function used to unregister toolbar from the CustomizableUI.
-const unregisterToolbar = CustomizableUI.unregisterArea;
-
-const reactor = new Reactor({
- onStep: (present, past) => {
- const delta = diff(past, present);
-
- each(([id, update]) => {
- // If update is `null` toolbar is removed, in such case
- // we unregister toolbar and remove it from each window
- // it was added to.
- if (update === null) {
- unregisterToolbar("inner-" + id);
- each(removeView(id), values(past.windows));
-
- send(output, object([id, null]));
- }
- else if (past.toolbars[id]) {
- // If `collapsed` state for toolbar was updated, persist
- // it for a future sessions.
- if (update.collapsed !== void(0))
- prefs.set(PREF_ROOT + id, update.collapsed);
-
- // Reflect update in each window it was added to.
- each(updateView(id, update), values(past.windows));
-
- send(output, object([id, update]));
- }
- // Hack: Mutation observers are invoked async, which means that if
- // client does `hide(toolbar)` & then `toolbar.destroy()` by the
- // time we'll get update for `collapsed` toolbar will be removed.
- // For now we check if `update.id` is present which will be undefined
- // in such cases.
- else if (update.id) {
- // If it is a new toolbar we create initial state by overriding
- // `collapsed` filed with value persisted in previous sessions.
- const state = patch(update, {
- collapsed: prefs.get(PREF_ROOT + id, update.collapsed),
- });
-
- // Register toolbar and add it each window known in the past
- // (note that new windows if any will be handled in loop below).
- registerToolbar(state);
- each(addView(state), values(past.windows));
-
- send(output, object([state.id, state]));
- }
- }, pairs(delta.toolbars));
-
- // Add views to every window that was added.
- each(window => {
- if (window)
- each(viewAdd(window), values(past.toolbars));
- }, values(delta.windows));
-
- each(([id, isCustomizing]) => {
- each(viewUpdate(getByOuterId(id), {isCustomizing: !!isCustomizing}),
- keys(present.toolbars));
-
- }, pairs(delta.customizable))
- },
- onEnd: state => {
- each(id => {
- unregisterToolbar("inner-" + id);
- each(removeView(id), values(state.windows));
- }, keys(state.toolbars));
- }
-});
-reactor.run(State);