diff options
author | Matt A. Tobin <email@mattatobin.com> | 2018-02-09 06:46:43 -0500 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2018-02-09 06:46:43 -0500 |
commit | ac46df8daea09899ce30dc8fd70986e258c746bf (patch) | |
tree | 2750d3125fc253fd5b0671e4bd268eff1fd97296 /addon-sdk/source/lib/sdk/ui | |
parent | 8cecf8d5208f3945b35f879bba3015bb1a11bec6 (diff) | |
download | UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.gz UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.lz UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.xz UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.zip |
Move Add-on SDK source to toolkit/jetpack
Diffstat (limited to 'addon-sdk/source/lib/sdk/ui')
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); |