addon-sdk/source/lib/sdk/ui/state.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4 'use strict';
michael@0 5
michael@0 6 // The Button module currently supports only Firefox.
michael@0 7 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
michael@0 8 module.metadata = {
michael@0 9 'stability': 'experimental',
michael@0 10 'engines': {
michael@0 11 'Firefox': '*'
michael@0 12 }
michael@0 13 };
michael@0 14
michael@0 15 const { Ci } = require('chrome');
michael@0 16
michael@0 17 const events = require('../event/utils');
michael@0 18 const { events: browserEvents } = require('../browser/events');
michael@0 19 const { events: tabEvents } = require('../tab/events');
michael@0 20 const { events: stateEvents } = require('./state/events');
michael@0 21
michael@0 22 const { windows, isInteractive, getFocusedBrowser } = require('../window/utils');
michael@0 23 const { getActiveTab, getOwnerWindow } = require('../tabs/utils');
michael@0 24
michael@0 25 const { ignoreWindow } = require('../private-browsing/utils');
michael@0 26
michael@0 27 const { freeze } = Object;
michael@0 28 const { merge } = require('../util/object');
michael@0 29 const { on, off, emit } = require('../event/core');
michael@0 30
michael@0 31 const { add, remove, has, clear, iterator } = require('../lang/weak-set');
michael@0 32 const { isNil } = require('../lang/type');
michael@0 33
michael@0 34 const { viewFor } = require('../view/core');
michael@0 35
michael@0 36 const components = new WeakMap();
michael@0 37
michael@0 38 const ERR_UNREGISTERED = 'The state cannot be set or get. ' +
michael@0 39 'The object may be not be registered, or may already have been unloaded.';
michael@0 40
michael@0 41 const ERR_INVALID_TARGET = 'The state cannot be set or get for this target.' +
michael@0 42 'Only window, tab and registered component are valid targets.';
michael@0 43
michael@0 44 const isWindow = thing => thing instanceof Ci.nsIDOMWindow;
michael@0 45 const isTab = thing => thing.tagName && thing.tagName.toLowerCase() === 'tab';
michael@0 46 const isActiveTab = thing => isTab(thing) && thing === getActiveTab(getOwnerWindow(thing));
michael@0 47 const isEnumerable = window => !ignoreWindow(window);
michael@0 48 const browsers = _ =>
michael@0 49 windows('navigator:browser', { includePrivate: true }).filter(isInteractive);
michael@0 50 const getMostRecentTab = _ => getActiveTab(getFocusedBrowser());
michael@0 51
michael@0 52 function getStateFor(component, target) {
michael@0 53 if (!isRegistered(component))
michael@0 54 throw new Error(ERR_UNREGISTERED);
michael@0 55
michael@0 56 if (!components.has(component))
michael@0 57 return null;
michael@0 58
michael@0 59 let states = components.get(component);
michael@0 60
michael@0 61 if (target) {
michael@0 62 if (isTab(target) || isWindow(target) || target === component)
michael@0 63 return states.get(target) || null;
michael@0 64 else
michael@0 65 throw new Error(ERR_INVALID_TARGET);
michael@0 66 }
michael@0 67
michael@0 68 return null;
michael@0 69 }
michael@0 70 exports.getStateFor = getStateFor;
michael@0 71
michael@0 72 function getDerivedStateFor(component, target) {
michael@0 73 if (!isRegistered(component))
michael@0 74 throw new Error(ERR_UNREGISTERED);
michael@0 75
michael@0 76 if (!components.has(component))
michael@0 77 return null;
michael@0 78
michael@0 79 let states = components.get(component);
michael@0 80
michael@0 81 let componentState = states.get(component);
michael@0 82 let windowState = null;
michael@0 83 let tabState = null;
michael@0 84
michael@0 85 if (target) {
michael@0 86 // has a target
michael@0 87 if (isTab(target)) {
michael@0 88 windowState = states.get(getOwnerWindow(target), null);
michael@0 89
michael@0 90 if (states.has(target)) {
michael@0 91 // we have a tab state
michael@0 92 tabState = states.get(target);
michael@0 93 }
michael@0 94 }
michael@0 95 else if (isWindow(target) && states.has(target)) {
michael@0 96 // we have a window state
michael@0 97 windowState = states.get(target);
michael@0 98 }
michael@0 99 }
michael@0 100
michael@0 101 return freeze(merge({}, componentState, windowState, tabState));
michael@0 102 }
michael@0 103 exports.getDerivedStateFor = getDerivedStateFor;
michael@0 104
michael@0 105 function setStateFor(component, target, state) {
michael@0 106 if (!isRegistered(component))
michael@0 107 throw new Error(ERR_UNREGISTERED);
michael@0 108
michael@0 109 let isComponentState = target === component;
michael@0 110 let targetWindows = isWindow(target) ? [target] :
michael@0 111 isActiveTab(target) ? [getOwnerWindow(target)] :
michael@0 112 isComponentState ? browsers() :
michael@0 113 isTab(target) ? [] :
michael@0 114 null;
michael@0 115
michael@0 116 if (!targetWindows)
michael@0 117 throw new Error(ERR_INVALID_TARGET);
michael@0 118
michael@0 119 // initialize the state's map
michael@0 120 if (!components.has(component))
michael@0 121 components.set(component, new WeakMap());
michael@0 122
michael@0 123 let states = components.get(component);
michael@0 124
michael@0 125 if (state === null && !isComponentState) // component state can't be deleted
michael@0 126 states.delete(target);
michael@0 127 else {
michael@0 128 let base = isComponentState ? states.get(target) : null;
michael@0 129 states.set(target, freeze(merge({}, base, state)));
michael@0 130 }
michael@0 131
michael@0 132 render(component, targetWindows);
michael@0 133 }
michael@0 134 exports.setStateFor = setStateFor;
michael@0 135
michael@0 136 function render(component, targetWindows) {
michael@0 137 targetWindows = targetWindows ? [].concat(targetWindows) : browsers();
michael@0 138
michael@0 139 for (let window of targetWindows.filter(isEnumerable)) {
michael@0 140 let tabState = getDerivedStateFor(component, getActiveTab(window));
michael@0 141
michael@0 142 emit(stateEvents, 'data', {
michael@0 143 type: 'render',
michael@0 144 target: component,
michael@0 145 window: window,
michael@0 146 state: tabState
michael@0 147 });
michael@0 148
michael@0 149 }
michael@0 150 }
michael@0 151 exports.render = render;
michael@0 152
michael@0 153 function properties(contract) {
michael@0 154 let { rules } = contract;
michael@0 155 let descriptor = Object.keys(rules).reduce(function(descriptor, name) {
michael@0 156 descriptor[name] = {
michael@0 157 get: function() { return getDerivedStateFor(this)[name] },
michael@0 158 set: function(value) {
michael@0 159 let changed = {};
michael@0 160 changed[name] = value;
michael@0 161
michael@0 162 setStateFor(this, this, contract(changed));
michael@0 163 }
michael@0 164 }
michael@0 165 return descriptor;
michael@0 166 }, {});
michael@0 167
michael@0 168 return Object.create(Object.prototype, descriptor);
michael@0 169 }
michael@0 170 exports.properties = properties;
michael@0 171
michael@0 172 function state(contract) {
michael@0 173 return {
michael@0 174 state: function state(target, state) {
michael@0 175 let nativeTarget = target === 'window' ? getFocusedBrowser()
michael@0 176 : target === 'tab' ? getMostRecentTab()
michael@0 177 : target === this ? null
michael@0 178 : viewFor(target);
michael@0 179
michael@0 180 if (!nativeTarget && target !== this && !isNil(target))
michael@0 181 throw new Error(ERR_INVALID_TARGET);
michael@0 182
michael@0 183 target = nativeTarget || target;
michael@0 184
michael@0 185 // jquery style
michael@0 186 return arguments.length < 2
michael@0 187 ? getDerivedStateFor(this, target)
michael@0 188 : setStateFor(this, target, contract(state))
michael@0 189 }
michael@0 190 }
michael@0 191 }
michael@0 192 exports.state = state;
michael@0 193
michael@0 194 const register = (component, state) => {
michael@0 195 add(components, component);
michael@0 196 setStateFor(component, component, state);
michael@0 197 }
michael@0 198 exports.register = register;
michael@0 199
michael@0 200 const unregister = component => {
michael@0 201 remove(components, component);
michael@0 202 }
michael@0 203 exports.unregister = unregister;
michael@0 204
michael@0 205 const isRegistered = component => has(components, component);
michael@0 206 exports.isRegistered = isRegistered;
michael@0 207
michael@0 208 let tabSelect = events.filter(tabEvents, e => e.type === 'TabSelect');
michael@0 209 let tabClose = events.filter(tabEvents, e => e.type === 'TabClose');
michael@0 210 let windowOpen = events.filter(browserEvents, e => e.type === 'load');
michael@0 211 let windowClose = events.filter(browserEvents, e => e.type === 'close');
michael@0 212
michael@0 213 let close = events.merge([tabClose, windowClose]);
michael@0 214 let activate = events.merge([windowOpen, tabSelect]);
michael@0 215
michael@0 216 on(activate, 'data', ({target}) => {
michael@0 217 let [window, tab] = isWindow(target)
michael@0 218 ? [target, getActiveTab(target)]
michael@0 219 : [getOwnerWindow(target), target];
michael@0 220
michael@0 221 if (ignoreWindow(window)) return;
michael@0 222
michael@0 223 for (let component of iterator(components)) {
michael@0 224 emit(stateEvents, 'data', {
michael@0 225 type: 'render',
michael@0 226 target: component,
michael@0 227 window: window,
michael@0 228 state: getDerivedStateFor(component, tab)
michael@0 229 });
michael@0 230 }
michael@0 231 });
michael@0 232
michael@0 233 on(close, 'data', function({target}) {
michael@0 234 for (let component of iterator(components)) {
michael@0 235 components.get(component).delete(target);
michael@0 236 }
michael@0 237 });

mercurial