var requirejs, require, define; (function (global, undef) { var topReq, dataMain, src, subPath, bootstrapConfig = requirejs || require, hasOwn = Object.prototype.hasOwnProperty, contexts = {}, queue = [], currDirRegExp = /^\.\//, urlRegExp = /^\/|\:|\?|\.js$/, commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/gm, cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, jsSuffixRegExp = /\.js$/; if (typeof requirejs === 'function') { return; } function hasProp(obj, prop) { return hasOwn.call(obj, prop); } function getOwn(obj, prop) { return obj && hasProp(obj, prop) && obj[prop]; } function eachProp(obj, func) { var prop; for (prop in obj) { if (hasProp(obj, prop)) { if (func(obj[prop], prop)) { break; } } } } function mixin(target, source, force, deepStringMixin) { if (source) { eachProp(source, function (value, prop) { if (force || !hasProp(target, prop)) { if (deepStringMixin && typeof value === 'object' && value && !Array.isArray(value) && typeof value !== 'function' && !(value instanceof RegExp)) { if (!target[prop]) { target[prop] = {}; } mixin(target[prop], value, force, deepStringMixin); } else { target[prop] = value; } } }); } return target; } function getGlobal(value) { if (!value) { return value; } var g = global; value.split('.').forEach(function (part) { g = g[part]; }); return g; } function newContext(contextName) { var req, main, makeMap, callDep, handlers, checkingLater, load, context, defined = {}, waiting = {}, config = { waitSeconds: 7, baseUrl: './', paths: {}, bundles: {}, pkgs: {}, shim: {}, config: {} }, mapCache = {}, requireDeferreds = [], deferreds = {}, calledDefine = {}, calledPlugin = {}, loadCount = 0, startTime = new Date().getTime(), errCount = 0, trackedErrors = {}, urlFetched = {}, bundlesMap = {}; var nextMicroTaskPass; (function () { 'use strict'; var waitingResolving, waiting = []; function callWaiting() { waitingResolving = null; var w = waiting; waiting = []; while (w.length) { w.shift()(); } } nextMicroTaskPass = function (fn) { waiting.push(fn); if (!waitingResolving) { waitingResolving = new Promise(function (resolve, reject) { resolve(); }).then(callWaiting).catch(delayedError); } }; }()); function trimDots(ary) { var i, part, length = ary.length; for (i = 0; i < length; i++) { part = ary[i]; if (part === '.') { ary.splice(i, 1); i -= 1; } else if (part === '..') { if (i === 1 && (ary[2] === '..' || ary[0] === '..')) { break; } else if (i > 0) { ary.splice(i - 1, 2); i -= 2; } } } } function normalize(name, baseName, applyMap) { var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex, foundMap, foundI, foundStarMap, starI, baseParts = baseName && baseName.split('/'), normalizedBaseParts = baseParts, map = config.map, starMap = map && map['*']; if (name && name.charAt(0) === '.') { if (baseName) { normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); name = name.split('/'); lastIndex = name.length - 1; if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); } name = normalizedBaseParts.concat(name); trimDots(name); name = name.join('/'); } else if (name.indexOf('./') === 0) { name = name.substring(2); } } if (applyMap && map && (baseParts || starMap)) { nameParts = name.split('/'); outerLoop: for (i = nameParts.length; i > 0; i -= 1) { nameSegment = nameParts.slice(0, i).join('/'); if (baseParts) { for (j = baseParts.length; j > 0; j -= 1) { mapValue = getOwn(map, baseParts.slice(0, j).join('/')); if (mapValue) { mapValue = getOwn(mapValue, nameSegment); if (mapValue) { foundMap = mapValue; foundI = i; break outerLoop; } } } } if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) { foundStarMap = getOwn(starMap, nameSegment); starI = i; } } if (!foundMap && foundStarMap) { foundMap = foundStarMap; foundI = starI; } if (foundMap) { nameParts.splice(0, foundI, foundMap); name = nameParts.join('/'); } } pkgMain = getOwn(config.pkgs, name); return pkgMain ? pkgMain : name; } function makeShimExports(value) { function fn() { var ret; if (value.init) { ret = value.init.apply(global, arguments); } return ret || value.exports && getGlobal(value.exports); } return fn; } function takeQueue(anonId) { var i, id, args, shim; for (i = 0; i < queue.length; i += 1) { if (typeof queue[i][0] !== 'string') { if (anonId) { queue[i].unshift(anonId); anonId = undef; } else { break; } } args = queue.shift(); id = args[0]; i -= 1; if (!hasProp(defined, id) && !hasProp(waiting, id)) { if (hasProp(deferreds, id)) { main.apply(undef, args); } else { waiting[id] = args; } } } if (anonId) { shim = getOwn(config.shim, anonId) || {}; main(anonId, shim.deps || [], shim.exportsFn); } } function makeRequire(relName, topLevel) { var req = function (deps, callback, errback, alt) { var name, cfg; if (topLevel) { takeQueue(); } if (typeof deps === 'string') { if (handlers[deps]) { return handlers[deps](relName); } name = makeMap(deps, relName, true).id; if (!hasProp(defined, name)) { throw new Error('Not loaded: ' + name); } return defined[name]; } else if (deps && !Array.isArray(deps)) { cfg = deps; deps = undef; if (Array.isArray(callback)) { deps = callback; callback = errback; errback = alt; } if (topLevel) { return req.config(cfg)(deps, callback, errback); } } callback = callback || function () { }; nextMicroTaskPass(function () { takeQueue(); main(undef, deps || [], callback, errback, relName); }); return req; }; req.isBrowser = typeof document !== 'undefined' && typeof navigator !== 'undefined'; req.nameToUrl = function (moduleName, ext, skipExt) { var paths, syms, i, parentModule, url, parentPath, bundleId, pkgMain = getOwn(config.pkgs, moduleName); if (pkgMain) { moduleName = pkgMain; } bundleId = getOwn(bundlesMap, moduleName); if (bundleId) { return req.nameToUrl(bundleId, ext, skipExt); } if (urlRegExp.test(moduleName)) { url = moduleName + (ext || ''); } else { paths = config.paths; syms = moduleName.split('/'); for (i = syms.length; i > 0; i -= 1) { parentModule = syms.slice(0, i).join('/'); parentPath = getOwn(paths, parentModule); if (parentPath) { if (Array.isArray(parentPath)) { parentPath = parentPath[0]; } syms.splice(0, i, parentPath); break; } } url = syms.join('/'); url += ext || (/^data\:|\?/.test(url) || skipExt ? '' : '.js'); url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url; } return config.urlArgs ? url + ((url.indexOf('?') === -1 ? '?' : '&') + config.urlArgs) : url; }; req.toUrl = function (moduleNamePlusExt) { var ext, index = moduleNamePlusExt.lastIndexOf('.'), segment = moduleNamePlusExt.split('/')[0], isRelative = segment === '.' || segment === '..'; if (index !== -1 && (!isRelative || index > 1)) { ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length); moduleNamePlusExt = moduleNamePlusExt.substring(0, index); } return req.nameToUrl(normalize(moduleNamePlusExt, relName), ext, true); }; req.defined = function (id) { return hasProp(defined, makeMap(id, relName, true).id); }; req.specified = function (id) { id = makeMap(id, relName, true).id; return hasProp(defined, id) || hasProp(deferreds, id); }; return req; } function resolve(name, d, value) { if (name) { defined[name] = value; if (requirejs.onResourceLoad) { requirejs.onResourceLoad(context, d.map, d.deps); } } d.finished = true; d.resolve(value); } function reject(d, err) { d.finished = true; d.rejected = true; d.reject(err); } function makeNormalize(relName) { return function (name) { return normalize(name, relName, true); }; } function defineModule(d) { var name = d.map.id, ret = d.factory.apply(defined[name], d.values); if (name) { if (ret === undef) { if (d.cjsModule) { ret = d.cjsModule.exports; } else if (d.usingExports) { ret = defined[name]; } } } else { requireDeferreds.splice(requireDeferreds.indexOf(d), 1); } resolve(name, d, ret); } function depFinished(val, i) { if (!this.rejected && !this.depDefined[i]) { this.depDefined[i] = true; this.depCount += 1; this.values[i] = val; if (!this.depending && this.depCount === this.depMax) { defineModule(this); } } } function makeDefer(name) { var d = {}; d.promise = new Promise(function (resolve, reject) { d.resolve = resolve; d.reject = reject; }); d.map = name ? makeMap(name, null, true) : {}; d.depCount = 0; d.depMax = 0; d.values = []; d.depDefined = []; d.depFinished = depFinished; if (d.map.pr) { d.deps = [makeMap(d.map.pr)]; } return d; } function getDefer(name) { var d; if (name) { d = hasProp(deferreds, name) && deferreds[name]; if (!d) { d = deferreds[name] = makeDefer(name); } } else { d = makeDefer(); requireDeferreds.push(d); } return d; } function makeErrback(d, name) { return function (err) { if (!d.rejected) { if (!err.dynaId) { err.dynaId = 'id' + (errCount += 1); err.requireModules = [name]; } reject(d, err); } }; } function waitForDep(depMap, relName, d, i) { d.depMax += 1; callDep(depMap, relName).then(function (val) { d.depFinished(val, i); }, makeErrback(d, depMap.id)).catch(makeErrback(d, d.map.id)); } function makeLoad(id) { var fromTextCalled; function load(value) { if (!fromTextCalled) { resolve(id, getDefer(id), value); } } load.error = function (err) { getDefer(id).reject(err); }; load.fromText = function (text, textAlt) { var d = getDefer(id), map = makeMap(makeMap(id).n), plainId = map.id; fromTextCalled = true; d.factory = function (p, val) { return val; }; if (textAlt) { text = textAlt; } if (hasProp(config.config, id)) { config.config[plainId] = config.config[id]; } try { req.exec(text); } catch (e) { reject(d, new Error('fromText eval for ' + plainId + ' failed: ' + e)); } takeQueue(plainId); d.deps = [map]; waitForDep(map, null, d, d.deps.length); }; return load; } load = typeof importScripts === 'function' ? function (map) { var url = map.url; if (urlFetched[url]) { return; } urlFetched[url] = true; getDefer(map.id); importScripts(url); takeQueue(map.id); } : function (map) { var script, id = map.id, url = map.url; if (urlFetched[url]) { return; } urlFetched[url] = true; script = document.createElement('script'); script.setAttribute('data-requiremodule', id); script.type = config.scriptType || 'text/javascript'; script.charset = 'utf-8'; script.async = true; loadCount += 1; script.addEventListener('load', function () { loadCount -= 1; takeQueue(id); }, false); script.addEventListener('error', function () { loadCount -= 1; var err, pathConfig = getOwn(config.paths, id), d = getOwn(deferreds, id); if (pathConfig && Array.isArray(pathConfig) && pathConfig.length > 1) { script.parentNode.removeChild(script); pathConfig.shift(); d.map = makeMap(id); load(d.map); } else { err = new Error('Load failed: ' + id + ': ' + script.src); err.requireModules = [id]; getDefer(id).reject(err); } }, false); script.src = url; document.head.appendChild(script); }; function callPlugin(plugin, map, relName) { plugin.load(map.n, makeRequire(relName), makeLoad(map.id), {}); } callDep = function (map, relName) { var args, bundleId, name = map.id, shim = config.shim[name]; if (hasProp(waiting, name)) { args = waiting[name]; delete waiting[name]; main.apply(undef, args); } else if (!hasProp(deferreds, name)) { if (map.pr) { if (bundleId = getOwn(bundlesMap, name)) { map.url = req.nameToUrl(bundleId); load(map); } else { return callDep(makeMap(map.pr)).then(function (plugin) { var newMap = makeMap(name, relName, true), newId = newMap.id, shim = getOwn(config.shim, newId); if (!hasProp(calledPlugin, newId)) { calledPlugin[newId] = true; if (shim && shim.deps) { req(shim.deps, function () { callPlugin(plugin, newMap, relName); }); } else { callPlugin(plugin, newMap, relName); } } return getDefer(newId).promise; }); } } else if (shim && shim.deps) { req(shim.deps, function () { load(map); }); } else { load(map); } } return getDefer(name).promise; }; function splitPrefix(name) { var prefix, index = name ? name.indexOf('!') : -1; if (index > -1) { prefix = name.substring(0, index); name = name.substring(index + 1, name.length); } return [ prefix, name ]; } makeMap = function (name, relName, applyMap) { if (typeof name !== 'string') { return name; } var plugin, url, parts, prefix, result, cacheKey = name + ' & ' + (relName || '') + ' & ' + !!applyMap; parts = splitPrefix(name); prefix = parts[0]; name = parts[1]; if (!prefix && hasProp(mapCache, cacheKey)) { return mapCache[cacheKey]; } if (prefix) { prefix = normalize(prefix, relName, applyMap); plugin = hasProp(defined, prefix) && defined[prefix]; } if (prefix) { if (plugin && plugin.normalize) { name = plugin.normalize(name, makeNormalize(relName)); } else { name = normalize(name, relName, applyMap); } } else { name = normalize(name, relName, applyMap); parts = splitPrefix(name); prefix = parts[0]; name = parts[1]; url = req.nameToUrl(name); } result = { id: prefix ? prefix + '!' + name : name, n: name, pr: prefix, url: url }; if (!prefix) { mapCache[cacheKey] = result; } return result; }; handlers = { require: function (name) { return makeRequire(name); }, exports: function (name) { var e = defined[name]; if (typeof e !== 'undefined') { return e; } else { return defined[name] = {}; } }, module: function (name) { return { id: name, uri: '', exports: handlers.exports(name), config: function () { return getOwn(config.config, name) || {}; } }; } }; function breakCycle(d, traced, processed) { var id = d.map.id; traced[id] = true; if (!d.finished && d.deps) { d.deps.forEach(function (depMap) { var depId = depMap.id, dep = !hasProp(handlers, depId) && getDefer(depId); if (dep && !dep.finished && !processed[depId]) { if (hasProp(traced, depId)) { d.deps.forEach(function (depMap, i) { if (depMap.id === depId) { d.depFinished(defined[depId], i); } }); } else { breakCycle(dep, traced, processed); } } }); } processed[id] = true; } function check(d) { var err, notFinished = [], waitInterval = config.waitSeconds * 1000, expired = waitInterval && startTime + waitInterval < new Date().getTime(); if (loadCount === 0) { if (d) { if (!d.finished) { breakCycle(d, {}, {}); } } else if (requireDeferreds.length) { requireDeferreds.forEach(function (d) { breakCycle(d, {}, {}); }); } } if (expired) { eachProp(deferreds, function (d) { if (!d.finished) { notFinished.push(d.map.id); } }); err = new Error('Timeout for modules: ' + notFinished); err.requireModules = notFinished; req.onError(err); } else if (loadCount || requireDeferreds.length) { if (!checkingLater) { checkingLater = true; setTimeout(function () { checkingLater = false; check(); }, 70); } } } function delayedError(e) { setTimeout(function () { if (!e.dynaId || !trackedErrors[e.dynaId]) { trackedErrors[e.dynaId] = true; req.onError(e); } }); } main = function (name, deps, factory, errback, relName) { if (name && hasProp(calledDefine, name)) { return; } calledDefine[name] = true; var d = getDefer(name); if (deps && !Array.isArray(deps)) { factory = deps; deps = []; } d.promise.catch(errback || delayedError); relName = relName || name; if (typeof factory === 'function') { if (!deps.length && factory.length) { factory.toString().replace(commentRegExp, '').replace(cjsRequireRegExp, function (match, dep) { deps.push(dep); }); deps = (factory.length === 1 ? ['require'] : [ 'require', 'exports', 'module' ]).concat(deps); } d.factory = factory; d.deps = deps; d.depending = true; deps.forEach(function (depName, i) { var depMap; deps[i] = depMap = makeMap(depName, relName, true); depName = depMap.id; if (depName === 'require') { d.values[i] = handlers.require(name); } else if (depName === 'exports') { d.values[i] = handlers.exports(name); d.usingExports = true; } else if (depName === 'module') { d.values[i] = d.cjsModule = handlers.module(name); } else if (depName === undefined) { d.values[i] = undefined; } else { waitForDep(depMap, relName, d, i); } }); d.depending = false; if (d.depCount === d.depMax) { defineModule(d); } } else if (name) { resolve(name, d, factory); } startTime = new Date().getTime(); if (!name) { check(d); } }; req = makeRequire(null, true); req.config = function (cfg) { if (cfg.context && cfg.context !== contextName) { return newContext(cfg.context).config(cfg); } mapCache = {}; if (cfg.baseUrl) { if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') { cfg.baseUrl += '/'; } } var shim = config.shim, objs = { paths: true, bundles: true, config: true, map: true }; eachProp(cfg, function (value, prop) { if (objs[prop]) { if (!config[prop]) { config[prop] = {}; } mixin(config[prop], value, true, true); } else { config[prop] = value; } }); if (cfg.bundles) { eachProp(cfg.bundles, function (value, prop) { value.forEach(function (v) { if (v !== prop) { bundlesMap[v] = prop; } }); }); } if (cfg.shim) { eachProp(cfg.shim, function (value, id) { if (Array.isArray(value)) { value = { deps: value }; } if ((value.exports || value.init) && !value.exportsFn) { value.exportsFn = makeShimExports(value); } shim[id] = value; }); config.shim = shim; } if (cfg.packages) { cfg.packages.forEach(function (pkgObj) { var location, name; pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj; name = pkgObj.name; location = pkgObj.location; if (location) { config.paths[name] = pkgObj.location; } config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main').replace(currDirRegExp, '').replace(jsSuffixRegExp, ''); }); } if (cfg.deps || cfg.callback) { req(cfg.deps, cfg.callback); } return req; }; req.onError = function (err) { throw err; }; context = { id: contextName, defined: defined, waiting: waiting, config: config, deferreds: deferreds }; contexts[contextName] = context; return req; } requirejs = topReq = newContext('_'); if (typeof require !== 'function') { require = topReq; } topReq.exec = function (text) { return eval(text); }; topReq.contexts = contexts; define = function () { queue.push([].slice.call(arguments, 0)); }; define.amd = { jQuery: true }; if (bootstrapConfig) { topReq.config(bootstrapConfig); } if (topReq.isBrowser && !contexts._.config.skipDataMain) { dataMain = document.querySelectorAll('script[data-main]')[0]; dataMain = dataMain && dataMain.getAttribute('data-main'); if (dataMain) { dataMain = dataMain.replace(jsSuffixRegExp, ''); if (!bootstrapConfig || !bootstrapConfig.baseUrl) { src = dataMain.split('/'); dataMain = src.pop(); subPath = src.length ? src.join('/') + '/' : './'; topReq.config({ baseUrl: subPath }); } topReq([dataMain]); } } }(this)); define('alameda', function () { }); (function () { function consoleHelper() { var msg = arguments[0] + ':'; for (var i = 1; i < arguments.length; i++) { msg += ' ' + arguments[i]; } msg += '\x1B[0m\n'; dump(msg); } if ('mozTCPSocket' in window.navigator) { window.console = { log: consoleHelper.bind(null, '\x1B[32mLOG'), error: consoleHelper.bind(null, '\x1B[31mERR'), info: consoleHelper.bind(null, '\x1B[36mINF'), warn: consoleHelper.bind(null, '\x1B[33mWAR') }; } window.onerror = function errHandler(msg, url, line) { console.error('onerror reporting:', msg, '@', url, ':', line); return false; }; }()); define('console_hook', function () { }); define('evt', [], function () { var evt, slice = Array.prototype.slice, props = [ '_events', '_pendingEvents', 'on', 'once', 'latest', 'latestOnce', 'removeListener', 'emitWhenListener', 'emit' ]; function Emitter() { this._events = {}; this._pendingEvents = {}; } Emitter.prototype = { on: function (id, fn) { var listeners = this._events[id], pending = this._pendingEvents[id]; if (!listeners) { listeners = this._events[id] = []; } listeners.push(fn); if (pending) { pending.forEach(function (args) { fn.apply(null, args); }); delete this._pendingEvents[id]; } return this; }, once: function (id, fn) { var self = this, fired = false; function one() { if (fired) { return; } fired = true; fn.apply(null, arguments); setTimeout(function () { self.removeListener(id, one); }); } return this.on(id, one); }, latest: function (id, fn) { if (this[id] && !this._pendingEvents[id]) { fn(this[id]); } this.on(id, fn); }, latestOnce: function (id, fn) { if (this[id] && !this._pendingEvents[id]) { fn(this[id]); } else { this.once(id, fn); } }, removeListener: function (id, fn) { var i, listeners = this._events[id]; if (listeners) { i = listeners.indexOf(fn); if (i !== -1) { listeners.splice(i, 1); } if (listeners.length === 0) { delete this._events[id]; } } }, emitWhenListener: function (id) { var listeners = this._events[id]; if (listeners) { this.emit.apply(this, arguments); } else { if (!this._pendingEvents[id]) { this._pendingEvents[id] = []; } this._pendingEvents[id].push(slice.call(arguments, 1)); } }, emit: function (id) { var args = slice.call(arguments, 1), listeners = this._events[id]; if (listeners) { listeners.forEach(function (fn) { try { fn.apply(null, args); } catch (e) { setTimeout(function () { throw e; }); } }); } } }; evt = new Emitter(); evt.Emitter = Emitter; evt.mix = function (obj) { var e = new Emitter(); props.forEach(function (prop) { if (obj.hasOwnProperty(prop)) { throw new Error('Object already has a property "' + prop + '"'); } obj[prop] = e[prop]; }); return obj; }; return evt; }); define('app_self', [ 'require', 'exports', 'module', 'evt' ], function (require, exports, module) { var evt = require('evt'); var appSelf = evt.mix({}), mozApps = navigator.mozApps, oldLatest = appSelf.latest, loaded = false; if (!mozApps) { appSelf.self = {}; loaded = true; } function loadSelf() { mozApps.getSelf().onsuccess = function (event) { loaded = true; var app = event.target.result; appSelf.self = app; appSelf.emit('self', appSelf.self); }; } appSelf.latest = function (id) { if (!loaded) { loadSelf(); } if (id !== 'self') { throw new Error(module.id + ' only supports "self" property'); } return oldLatest.apply(this, arguments); }; return appSelf; }); define('l10n', { load: function (id, require, onload, config) { if (config.isBuild) { return onload(); } require(['l10nbase'], function () { navigator.mozL10n.once(function () { var dir = navigator.mozL10n.language.direction, htmlNode = document.querySelector('html'); if (htmlNode.getAttribute('dir') !== dir) { console.log('email l10n updating html dir to ' + dir); htmlNode.setAttribute('dir', dir); } onload(navigator.mozL10n); }); }); } }); (function (window) { 'use strict'; window.NotificationHelper = { getIconURI: function nh_getIconURI(app, entryPoint) { var icons = app.manifest.icons; if (entryPoint) { icons = app.manifest.entry_points[entryPoint].icons; } if (!icons) { return null; } var sizes = Object.keys(icons).map(function parse(str) { return parseInt(str, 10); }); sizes.sort(function (x, y) { return y - x; }); var HVGA = document.documentElement.clientWidth < 480; var index = sizes[HVGA ? sizes.length - 1 : 0]; return app.installOrigin + icons[index]; }, send: function nh_send(titleL10n, options) { return Promise.all([ titleL10n, options.bodyL10n ].map(getL10n)).then(([title, body]) => { if (body) { options.body = body; } options.lang = document.documentElement.getAttribute('lang'); var notification = new window.Notification(title, options); if (options.closeOnClick !== false) { notification.addEventListener('click', function nh_click() { notification.removeEventListener('click', nh_click); notification.close(); }); } return notification; }); } }; function getL10n(l10nAttrs) { if (!l10nAttrs) { return; } if (typeof l10nAttrs === 'string') { return document.l10n.formatValue(l10nAttrs); } if (typeof l10nAttrs.raw === "string") { return l10nAttrs.raw; } return document.l10n.formatValue(l10nAttrs.id, l10nAttrs.args); } }(this)); define('shared/js/notification_helper', function (global) { return function () { var ret, fn; return ret || global.NotificationHelper; }; }(this)); define('sync', [ 'require', 'app_self', 'evt', 'l10n!', 'shared/js/notification_helper' ], function (require) { var cronSyncStartTime, appSelf = require('app_self'), evt = require('evt'), mozL10n = require('l10n!'), notificationHelper = require('shared/js/notification_helper'); var notificationDataVersion = '1'; return function syncInit(model, api) { var hasBeenVisible = !document.hidden, waitingOnCron = {}; if (hasBeenVisible) { api.setInteractive(); } document.addEventListener('visibilitychange', function onVisibilityChange() { if (!document.hidden) { hasBeenVisible = true; api.setInteractive(); } }, false); function makeAccountKey(accountIds) { return 'id' + accountIds.join(' '); } var sendNotification; if (typeof Notification !== 'function') { console.log('email: notifications not available'); sendNotification = function () { }; } else { sendNotification = function (notificationId, titleL10n, bodyL10n, iconUrl, data, behavior) { console.log('Notification sent for ' + notificationId); if (Notification.permission !== 'granted') { console.log('email: notification skipped, permission: ' + Notification.permission); return; } data = data || {}; var notificationOptions = { bodyL10n: bodyL10n, icon: iconUrl, tag: notificationId, data: data, mozbehavior: { noscreen: true }, closeOnClick: false }; if (behavior) { Object.keys(behavior).forEach(function (key) { notificationOptions.mozbehavior[key] = behavior[key]; }); } notificationHelper.send(titleL10n, notificationOptions).then(function (notification) { notification.onclick = function () { evt.emit('notification', { clicked: true, imageURL: iconUrl, tag: notificationId, data: data }); }; }); }; } api.oncronsyncstart = function (accountIds) { console.log('email oncronsyncstart: ' + accountIds); cronSyncStartTime = Date.now(); var accountKey = makeAccountKey(accountIds); waitingOnCron[accountKey] = true; }; function fetchNotificationsData(ntype) { if (typeof Notification !== 'function' || !Notification.get) { return Promise.resolve({}); } return Notification.get().then(function (notifications) { var result = {}; notifications.forEach(function (notification) { var data = notification.data; if (!data.v || data.v !== notificationDataVersion) { notification.close(); } else if (data.ntype === ntype) { data.notification = notification; result[data.accountId] = data; } }); return result; }, function (err) { console.error('email notification.get call failed: ' + err); return {}; }); } function getSyncEnv(fn) { appSelf.latest('self', function (app) { model.latestOnce('account', function (currentAccount) { fetchNotificationsData('sync').then(function (existingNotificationsData) { mozL10n.formatValue('senders-separation-sign').then(function (separator) { var localized = { separator }; mozL10n.formatValue('notification-no-subject').then(function (noSubject) { localized.noSubject = noSubject; fn(app, currentAccount, existingNotificationsData, localized); }); }); }); }); }); } function topUniqueFromNames(latestInfos, oldFromNames) { var names = [], maxCount = 3; latestInfos.sort(function (a, b) { return b.date - a.date; }); latestInfos.some(function (info) { if (names.length > maxCount) { return true; } if (names.indexOf(info.from) === -1) { names.push(info.from); } }); oldFromNames.some(function (name) { if (names.length > maxCount) { return true; } if (names.indexOf(name) === -1) { names.push(name); } }); return names; } api.oncronsyncstop = function (accountsResults) { console.log('email oncronsyncstop: ' + accountsResults.accountIds); function finishSync() { evt.emit('cronSyncStop', accountsResults.accountIds); var accountKey = makeAccountKey(accountsResults.accountIds); waitingOnCron[accountKey] = false; var stillWaiting = Object.keys(waitingOnCron).some(function (key) { return !!waitingOnCron[key]; }); if (!hasBeenVisible && !stillWaiting) { console.log('sync completed in ' + (Date.now() - cronSyncStartTime) / 1000 + ' seconds, closing mail app'); window.close(); } } if (!accountsResults.updates) { finishSync(); return; } getSyncEnv(function (app, currentAccount, existingNotificationsData, localized) { var iconUrl = notificationHelper.getIconURI(app); accountsResults.updates.forEach(function (result) { if (currentAccount.id === result.id && !document.hidden) { model.notifyInboxMessages(result); return; } if (!model.getAccount(result.id).notifyOnNew || typeof Notification !== 'function') { return; } var dataObject, subjectL10n, bodyL10n, behavior, count = result.count, oldFromNames = []; var existingData = existingNotificationsData[result.id]; if (existingData) { if (existingData.count) { count += parseInt(existingData.count, 10); } if (existingData.fromNames) { oldFromNames = existingData.fromNames; } } if (count > 1) { var newFromNames = topUniqueFromNames(result.latestMessageInfos, oldFromNames); dataObject = { v: notificationDataVersion, ntype: 'sync', type: 'message_list', accountId: result.id, count: count, fromNames: newFromNames }; if (existingData && existingData.count) { behavior = { soundFile: 'does-not-exist-to-simulate-silent', vibrationPattern: [1] }; } if (model.getAccountCount() === 1) { subjectL10n = { id: 'new-emails-notify-one-account', args: { n: count } }; } else { subjectL10n = { id: 'new-emails-notify-multiple-accounts', args: { n: count, accountName: result.address } }; } bodyL10n = { raw: newFromNames.join(localized.separator) }; } else { var info = result.latestMessageInfos[0]; dataObject = { v: notificationDataVersion, ntype: 'sync', type: 'message_reader', accountId: info.accountId, messageSuid: info.messageSuid, count: 1, fromNames: [info.from] }; var rawSubject = info.subject || localized.noSubject; if (model.getAccountCount() === 1) { subjectL10n = { raw: rawSubject }; bodyL10n = { raw: info.from }; } else { subjectL10n = { id: 'new-emails-notify-multiple-accounts', args: { n: count, accountName: result.address } }; bodyL10n = { id: 'new-emails-notify-multiple-accounts-body', args: { from: info.from, subject: rawSubject } }; } } sendNotification(result.id, subjectL10n, bodyL10n, iconUrl, dataObject, behavior); }); finishSync(); }); }; var BACKGROUND_SEND_NOTIFICATION_ID = 'backgroundSendFailed'; var sentAudio = null; api.onbackgroundsendstatus = function (data) { console.log('outbox: Message', data.suid, 'status =', JSON.stringify({ state: data.state, err: data.err, sendFailures: data.sendFailures, emitNotifications: data.emitNotifications })); var descId; switch (data.state) { case 'pending': descId = 'background-send-pending'; break; case 'sending': descId = 'background-send-sending'; break; case 'success': descId = 'background-send-success'; break; case 'error': if (data.badAddresses && data.badAddresses.length || data.err === 'bad-recipient') { descId = 'background-send-error-recipients'; } else { descId = 'background-send-error'; } break; case 'syncDone': break; default: console.error('No state description for background send state "' + data.state + '"'); return; } if (data.state === 'success') { model.latestOnce('acctsSlice', function () { var account = model.getAccount(data.accountId); if (!account) { console.error('Invalid account ID', data.accountId, 'for a background send notification.'); return; } if (account.playSoundOnSend) { if (!sentAudio) { sentAudio = new Audio('/sounds/firefox_sent.opus'); sentAudio.mozAudioChannelType = 'notification'; } sentAudio.play(); } }.bind(this)); } if (!document.hidden) { mozL10n.formatValue(descId).then(function (localizedDescription) { data.localizedDescription = localizedDescription; model.notifyBackgroundSendStatus(data); }); } else if (data.state === 'error' && data.emitNotifications) { appSelf.latest('self', function (app) { var iconUrl = notificationHelper.getIconURI(app); var dataObject = { v: notificationDataVersion, ntype: 'outbox', type: 'message_reader', folderType: 'outbox', accountId: data.accountId, messageSuid: data.suid }; sendNotification(BACKGROUND_SEND_NOTIFICATION_ID, 'background-send-error-title', descId, iconUrl, dataObject); }); } }; evt.on('inboxShown', function (accountId) { fetchNotificationsData('sync').then(function (notificationsData) { if (notificationsData.hasOwnProperty(accountId)) { notificationsData[accountId].notification.close(); } }); }); }; }); define('model_init', [ 'require', 'sync', 'evt', 'l10n!' ], function (require) { return function modelInit(model, api) { require('sync')(model, api); var evt = require('evt'), mozL10n = require('l10n!'); api.onbadlogin = function (account, problem, whichSide) { evt.emitWhenListener('apiBadLogin', account, problem, whichSide); }; api.useLocalizedStrings({ wrote: mozL10n.get('reply-quoting-wrote'), originalMessage: mozL10n.get('forward-original-message'), forwardHeaderLabels: { subject: mozL10n.get('forward-header-subject'), date: mozL10n.get('forward-header-date'), from: mozL10n.get('forward-header-from'), replyTo: mozL10n.get('forward-header-reply-to'), to: mozL10n.get('forward-header-to'), cc: mozL10n.get('forward-header-cc') }, folderNames: { inbox: mozL10n.get('folder-inbox'), outbox: mozL10n.get('folder-outbox'), sent: mozL10n.get('folder-sent'), drafts: mozL10n.get('folder-drafts'), trash: mozL10n.get('folder-trash'), queue: mozL10n.get('folder-queue'), junk: mozL10n.get('folder-junk'), archives: mozL10n.get('folder-archives'), localdrafts: mozL10n.get('folder-localdrafts') } }); }; }); define('model_create', [ 'require', 'evt', 'model_init' ], function (require) { var evt = require('evt'), modelInit = require('model_init'); function dieOnFatalError(msg) { console.error('FATAL:', msg); throw new Error(msg); } function saveHasAccount(acctsSlice) { localStorage.setItem('data_has_account', acctsSlice.items.length ? 'yes' : 'no'); console.log('WRITING LOCAL STORAGE ITEM: ' + 'data_has_account', acctsSlice.items.length ? 'yes' : 'no'); } function Model() { evt.Emitter.call(this); } Model.prototype = { acctsSlice: null, account: null, foldersSlice: null, folder: null, _callEmit: function (id) { this.emit(id, this[id]); }, inited: false, hasAccount: function () { return this.getAccountCount() > 0; }, getAccount: function (id) { if (!this.acctsSlice || !this.acctsSlice.items) { throw new Error('No acctsSlice available'); } var targetAccount; this.acctsSlice.items.some(function (account) { if (account.id === id) { return !!(targetAccount = account); } }); return targetAccount; }, getAccountCount: function () { var count = 0; if (this.acctsSlice && this.acctsSlice.items && this.acctsSlice.items.length) { count = this.acctsSlice.items.length; } return count; }, init: function (showLatest, callback) { require(['api'], function (api) { if (this === modelCreate.defaultModel) { modelInit(this, api); } this.api = api; this.die(); var acctsSlice = api.viewAccounts(false); acctsSlice.oncomplete = function () { this.acctsSlice = acctsSlice; saveHasAccount(acctsSlice); if (acctsSlice.items.length) { var account = showLatest ? acctsSlice.items.slice(-1)[0] : acctsSlice.defaultAccount; this.changeAccount(account, callback); } else if (callback) { callback(); } this.inited = true; this._callEmit('acctsSlice'); if (this === modelCreate.defaultModel) { evt.emitWhenListener('metrics:apiDone'); } }.bind(this); acctsSlice.onchange = function () { saveHasAccount(acctsSlice); }.bind(this); }.bind(this)); }, changeAccount: function (account, callback) { if (this.account && this.account.id === account.id) { if (callback) { callback(); } return; } this._dieFolders(); this.account = account; this._callEmit('account'); var foldersSlice = this.api.viewFolders('account', account); foldersSlice.oncomplete = function () { this.foldersSlice = foldersSlice; this.foldersSlice.onchange = this.notifyFoldersSliceOnChange.bind(this); this.selectInbox(callback); this._callEmit('foldersSlice'); }.bind(this); }, changeAccountFromId: function (accountId, callback) { if (!this.acctsSlice || !this.acctsSlice.items.length) { throw new Error('No accounts available'); } this.acctsSlice.items.some(function (account) { if (account.id === accountId) { this.changeAccount(account, callback); return true; } }.bind(this)); }, changeFolder: function (folder) { if (folder && (!this.folder || folder.id !== this.folder.id)) { this.folder = folder; this._callEmit('folder'); } }, selectInbox: function (callback) { this.selectFirstFolderWithType('inbox', callback); }, selectFirstFolderWithType: function (folderType, callback) { if (!this.foldersSlice) { throw new Error('No foldersSlice available'); } var folder = this.foldersSlice.getFirstFolderWithType(folderType); if (!folder) { dieOnFatalError('We have an account without a folderType ' + folderType + '!', this.foldersSlice.items); } if (this.folder && this.folder.id === folder.id) { if (callback) { callback(); } } else { if (callback) { this.once('folder', callback); } this.changeFolder(folder); } }, notifyInboxMessages: function (accountUpdate) { if (accountUpdate.id === this.account.id) { this.emit('newInboxMessages', accountUpdate.count); } }, notifyFoldersSliceOnChange: function (folder) { this.emit('foldersSliceOnChange', folder); }, notifyBackgroundSendStatus: function (data) { this.emit('backgroundSendStatus', data); }, _dieFolders: function () { if (this.foldersSlice) { this.foldersSlice.die(); } this.foldersSlice = null; this.folder = null; }, die: function () { if (this.acctsSlice) { this.acctsSlice.die(); } this.acctsSlice = null; this.account = null; this._dieFolders(); } }; evt.mix(Model.prototype); function modelCreate() { return new Model(); } modelCreate.defaultModel = new Model(); return modelCreate; }); var MimeMapper = { _typeToExtensionMap: { 'image/jpeg': 'jpg', 'image/png': 'png', 'image/gif': 'gif', 'image/bmp': 'bmp', 'audio/mpeg': 'mp3', 'audio/mp4': 'm4a', 'audio/ogg': 'ogg', 'audio/webm': 'webm', 'audio/3gpp': '3gp', 'audio/amr': 'amr', 'video/mp4': 'mp4', 'video/mpeg': 'mpg', 'video/ogg': 'ogg', 'video/webm': 'webm', 'video/3gpp': '3gp', 'video/3gpp2': '3g2', 'application/pdf': 'pdf', 'application/vcard': 'vcf', 'text/vcard': 'vcf', 'text/x-vcard': 'vcf' }, _extensionToTypeMap: { 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'jpe': 'image/jpeg', 'png': 'image/png', 'gif': 'image/gif', 'bmp': 'image/bmp', 'mp3': 'audio/mpeg', 'm4a': 'audio/mp4', 'm4b': 'audio/mp4', 'm4p': 'audio/mp4', 'm4r': 'audio/mp4', 'aac': 'audio/aac', 'opus': 'audio/ogg', 'amr': 'audio/amr', 'mp4': 'video/mp4', 'mpeg': 'video/mpeg', 'mpg': 'video/mpeg', 'ogv': 'video/ogg', 'ogx': 'video/ogg', 'webm': 'video/webm', '3gp': 'video/3gpp', '3g2': 'video/3gpp2', 'ogg': 'video/ogg', 'pdf': 'application/pdf', 'vcf': 'text/vcard' }, _parseExtension: function (filename) { var array = filename.split('.'); return array.length > 1 ? array.pop() : ''; }, isSupportedType: function (mimetype) { return mimetype in this._typeToExtensionMap; }, isSupportedExtension: function (extension) { return extension in this._extensionToTypeMap; }, isFilenameMatchesType: function (filename, mimetype) { var extension = this._parseExtension(filename); var guessedType = this.guessTypeFromExtension(extension); return guessedType == mimetype; }, guessExtensionFromType: function (mimetype) { return this._typeToExtensionMap[mimetype]; }, guessTypeFromExtension: function (extension) { return this._extensionToTypeMap[extension]; }, guessTypeFromFileProperties: function (filename, mimetype) { var extension = this._parseExtension(filename); var type = this.isSupportedType(mimetype) ? mimetype : this.guessTypeFromExtension(extension); return type || ''; }, ensureFilenameMatchesType: function (filename, mimetype) { if (!this.isFilenameMatchesType(filename, mimetype)) { var guessedExt = this.guessExtensionFromType(mimetype); if (guessedExt) { filename += '.' + guessedExt; } } return filename; } }; define('shared/js/mime_mapper', function (global) { return function () { var ret, fn; return ret || global.MimeMapper; }; }(this)); define('attachment_name', [ 'require', 'l10n!', 'shared/js/mime_mapper' ], function (require) { var mozL10n = require('l10n!'), mapper = require('shared/js/mime_mapper'); var attachmentName = { ensureName: function (blob, name, count) { if (!name) { count = count || 1; var suffix = mapper.guessExtensionFromType(blob.type); name = mozL10n.get('default-attachment-filename', { n: count }) + (suffix ? '.' + suffix : ''); } return name; }, ensureNameList: function (blobs, names) { for (var i = 0; i < blobs.length; i++) { names[i] = attachmentName.ensureName(blobs[i], names[i], i + 1); } } }; return attachmentName; }); define('query_uri', [], function () { 'use strict'; function decode(value) { try { return decodeURIComponent(value); } catch (err) { console.error('Skipping "' + value + '", decodeURIComponent error: ' + err); return ''; } } function queryURI(uri) { function addressesToArray(addresses) { if (!addresses) { return []; } addresses = addresses.split(/[,;]/); var addressesArray = addresses.filter(function notEmpty(addr) { return addr.trim() !== ''; }); return addressesArray; } var mailtoReg = /^mailto:(.*)/i; var obj = {}; if (uri && uri.match(mailtoReg)) { uri = uri.match(mailtoReg)[1]; var parts = uri.split('?'); var subjectReg = /(?:^|&)subject=([^\&]*)/i, bodyReg = /(?:^|&)body=([^\&]*)/i, ccReg = /(?:^|&)cc=([^\&]*)/i, bccReg = /(?:^|&)bcc=([^\&]*)/i; obj.to = parts[0] ? addressesToArray(decode(parts[0])) : []; if (parts.length == 2) { var data = parts[1]; if (data.match(subjectReg)) { obj.subject = decode(data.match(subjectReg)[1]); } if (data.match(bodyReg)) { obj.body = decode(data.match(bodyReg)[1]); } if (data.match(ccReg)) { obj.cc = addressesToArray(decode(data.match(ccReg)[1])); } if (parts[1].match(bccReg)) { obj.bcc = addressesToArray(decode(data.match(bccReg)[1])); } } } return obj; } return queryURI; }); define('activity_composer_data', [ 'require', 'exports', 'module', 'attachment_name', 'query_uri' ], function (require, exports, module) { var attachmentName = require('attachment_name'), queryUri = require('query_uri'); return function activityComposeR(rawActivity) { var source = rawActivity.source; var data = source.data; var activityName = source.name; var dataType = data.type; var url = data.url || data.URI; var attachData; if (dataType === 'url' && activityName === 'share') { attachData = { body: url }; } else { attachData = queryUri(url); attachData.attachmentBlobs = data.blobs || []; attachData.attachmentNames = data.filenames || []; attachmentName.ensureNameList(attachData.attachmentBlobs, attachData.attachmentNames); } return { onComposer: function (composer, composeCard) { var attachmentBlobs = attachData.attachmentBlobs; if (attachData.to) { composer.to = attachData.to; } if (attachData.subject) { composer.subject = attachData.subject; } if (attachData.body) { composer.body = { text: attachData.body + composer.body.text }; } if (attachData.cc) { composer.cc = attachData.cc; } if (attachData.bcc) { composer.bcc = attachData.bcc; } if (attachmentBlobs) { var attachmentsToAdd = []; for (var iBlob = 0; iBlob < attachmentBlobs.length; iBlob++) { attachmentsToAdd.push({ name: attachData.attachmentNames[iBlob], blob: attachmentBlobs[iBlob] }); } composeCard.addAttachmentsSubjectToSizeLimits(attachmentsToAdd); } } }; }; }); define('cards_init', ['require'], function (require) { return function cardsInit(cards) { cards.pushDefaultCard = function (onPushed) { require(['model_create'], function (modelCreate) { cards.pushCard('message_list', 'none', { model: modelCreate.defaultModel, onPushed: onPushed }, 'left'); }); }; }; }); define('html_cache', [ 'require', 'exports', 'module', 'l10n!' ], function (require, exports) { var mozL10n = require('l10n!'); exports.cloneAsInertNodeAvoidingCustomElementHorrors = function (node) { var templateNode = document.createElement('template'); var cacheDoc = templateNode.content.ownerDocument; return cacheDoc.importNode(node, true); }; exports.save = function htmlCacheSave(moduleId, html) { var id = exports.moduleIdToKey(moduleId); var langDir = document.querySelector('html').getAttribute('dir'); html = window.HTML_CACHE_VERSION + (langDir ? ',' + langDir : '') + ':' + html; localStorage.setItem('html_cache_' + id, html); console.log('htmlCache.save ' + id + ': ' + html.length + ', lang dir: ' + langDir); }; exports.reset = function () { localStorage.clear(); var expiry = Date.now() + 20 * 365 * 24 * 60 * 60 * 1000; expiry = new Date(expiry).toUTCString(); for (var i = 0; i < 40; i++) { document.cookie = 'htmlc' + i + '=; expires=' + expiry; } console.log('htmlCache reset'); }; window.addEventListener('languagechange', exports.reset); exports.moduleIdToKey = function moduleIdToKey(moduleId) { return moduleId.replace(/^cards\//, '').replace(/-/g, '_'); }; exports.nodeToKey = function nodeToKey(node) { return node.nodeName.toLowerCase().replace(/^cards-/, '').replace(/-/g, '_'); }; exports.cloneAndSave = function cloneAndSave(moduleId, node) { var cachedNode = exports.cloneAsInertNodeAvoidingCustomElementHorrors(node); mozL10n.translateFragment(cachedNode); cachedNode.dataset.cached = 'cached'; exports.delayedSaveFromNode(moduleId, cachedNode); }; exports.saveFromNode = function saveFromNode(moduleId, node) { var cl = node.classList; cl.remove('before'); cl.remove('after'); cl.add('center'); var nodes = node.querySelectorAll('.email-ce'); for (var i = 0; i < nodes.length; i++) { nodes[i].dataset.cached = 'cached'; } if (node.classList.contains('email-ce')) { node.dataset.cached = 'cached'; } var html = node.outerHTML; exports.save(moduleId, html); }; var delayedSaveId = 0; var delayedNode = ''; exports.delayedSaveFromNode = function delayedSaveFromNode(moduleId, node) { delayedNode = node; if (!delayedSaveId) { delayedSaveId = setTimeout(function () { delayedSaveId = 0; exports.saveFromNode(moduleId, delayedNode); delayedNode = null; }, 500); } }; }); define('tmpl', ['l10n!'], function (mozL10n) { var tmpl = { pluginBuilder: './tmpl_builder', toDom: function (text) { var temp = document.createElement('div'); temp.innerHTML = text; var node = temp.children[0]; mozL10n.translateFragment(node); return node; }, load: function (id, require, onload, config) { require(['text!' + id], function (text) { var node = tmpl.toDom(text); onload(node); }); } }; return tmpl; }); define('tmpl!cards/toaster.html', ['tmpl'], function (tmpl) { return tmpl.toDom('\n'); }); define('transition_end', [ 'require', 'exports', 'module' ], function (require, exports, module) { return function transitionEnd(node, fn, capturing) { function asyncFn(event) { Promise.resolve().then(function () { fn(event); }).catch(function (error) { console.error(error); }); } node.addEventListener('transitionend', asyncFn, capturing); return asyncFn; }; }); define('toaster', [ 'require', 'l10n!', 'tmpl!./cards/toaster.html', 'transition_end' ], function (require) { var mozL10n = require('l10n!'); var toasterNode = require('tmpl!./cards/toaster.html'); var transitionEnd = require('transition_end'); var toaster = { defaultTimeout: 5000, _previousActionClass: undefined, init: function (parentEl) { this.el = toasterNode; parentEl.appendChild(this.el); this.text = this.el.querySelector('.toaster-text'); this.actionButton = this.el.querySelector('.toaster-action'); this.el.addEventListener('click', this.hide.bind(this)); transitionEnd(this.el, this.hide.bind(this)); this.el.querySelector('.toaster-action-target').addEventListener('click', this.onAction.bind(this)); this.currentToast = null; }, toastOperation: function (op) { if (!op || !op.affectedCount) { return; } var type = op.operation; var canUndo = op.undo && type !== 'move' && type !== 'delete'; this.toast({ text: mozL10n.get('toaster-message-' + type, { n: op.affectedCount }), actionLabel: mozL10n.get('toaster-undo'), actionClass: 'undo', action: canUndo && op.undo.bind(op) }); }, onAction: function () { var actionFunction = this.currentToast && this.currentToast.action; this.hide(); if (actionFunction) { actionFunction(); } }, toast: function (opts) { opts = opts || {}; console.log('Showing toast:', JSON.stringify(opts)); this.hide(); this.currentToast = opts; this.text.textContent = opts.text; this.actionButton.textContent = opts.actionLabel; if (this._previousActionClass) { this.actionButton.classList.remove(this._previousActionClass); this._previousActionClass = undefined; } if (opts.actionClass) { this._previousActionClass = opts.actionClass; this.actionButton.classList.add(this._previousActionClass); } this.el.classList.toggle('actionable', !opts.action); this.actionButton.disabled = !opts.action; this.el.classList.remove('collapsed'); this._fadeTimeout = setTimeout(function () { this.el.classList.add('fadeout'); }.bind(this), opts.timeout || this.defaultTimeout); }, isShowing: function () { return !this.el.classList.contains('collapsed'); }, hide: function () { this.currentToast = null; this.el.classList.add('collapsed'); this.el.classList.remove('fadeout'); window.clearTimeout(this._fadeTimeout); this._fadeTimeout = null; } }; return toaster; }); define('input_areas', [ 'require', 'exports', 'module' ], function (require, exports) { var slice = Array.prototype.slice; return function hookupInputAreaResetButtons(e) { var selector = 'form p input + button[type="reset"],' + 'form p textarea + button[type="reset"]'; var resetButtons = slice.call(e.querySelectorAll(selector)); resetButtons.forEach(function (resetButton) { resetButton.addEventListener('mousedown', function (e) { e.preventDefault(); }); resetButton.addEventListener('click', function (e) { e.target.previousElementSibling.value = ''; e.preventDefault(); }); }); }; }); define('cards', [ 'require', 'exports', 'module', 'cards_init', 'html_cache', 'l10n!', 'evt', 'toaster', 'transition_end', 'input_areas' ], function (require, exports, module) { var cardsInit = require('cards_init'), htmlCache = require('html_cache'), mozL10n = require('l10n!'), evt = require('evt'), toaster = require('toaster'), transitionEnd = require('transition_end'), hookupInputAreaResetButtons = require('input_areas'); function addClass(domNode, name) { if (domNode) { domNode.classList.add(name); } } function removeClass(domNode, name) { if (domNode) { domNode.classList.remove(name); } } var cards = { _cardDefs: {}, _cardStack: [], activeCardIndex: -1, _pendingPush: null, _zIndex: 0, _rootNode: null, _containerNode: null, _cardsNode: null, _animatingDeadDomNodes: [], _transitionCount: 0, _startupEventsEmitted: false, _popupActive: null, _eatingEventsUntilNextCard: false, init: function () { this._rootNode = document.body; this._containerNode = document.getElementById('cardContainer'); this._cardsNode = document.getElementById('cards'); this._statusColorMeta = document.querySelector('meta[name="theme-color"]'); toaster.init(this._containerNode); this._containerNode.addEventListener('click', this._onMaybeIntercept.bind(this), true); transitionEnd(this._cardsNode, this._onTransitionEnd.bind(this), false); document.addEventListener('visibilitychange', function (evt) { var card = this._cardStack[this.activeCardIndex]; if (card && card.onCurrentCardDocumentVisibilityChange) { card.onCurrentCardDocumentVisibilityChange(document.hidden); } }.bind(this)); cardsInit(this); }, _onMaybeIntercept: function (event) { if (this._eatingEventsUntilNextCard) { event.stopPropagation(); event.preventDefault(); return; } if (this._popupActive) { event.stopPropagation(); event.preventDefault(); this._popupActive.close(); return; } var cardNode = event.target; for (cardNode = event.target; cardNode; cardNode = cardNode.parentElement) { if (cardNode.classList.contains('card')) { break; } } }, pushCard: function (type, showMethod, args, placement) { var cardDef = this._cardDefs[type]; args = args || {}; if (!cardDef) { var cbArgs = Array.slice(arguments); this._pendingPush = [type]; if (showMethod !== 'none') { this.eatEventsUntilNextCard(); } require(['element!cards/' + type], function (Ctor) { this._cardDefs[type] = Ctor; this.pushCard.apply(this, cbArgs); }.bind(this)); return; } this._pendingPush = null; console.log('pushCard for type: ' + type); var domNode = args.cachedNode || new cardDef(); if (args && domNode.onArgs) { domNode.onArgs(args); } var cardIndex, insertBuddy; if (!placement) { cardIndex = this._cardStack.length; insertBuddy = null; domNode.classList.add(cardIndex === 0 ? 'before' : 'after'); } else if (placement === 'left') { cardIndex = this.activeCardIndex++; insertBuddy = this._cardsNode.children[cardIndex]; domNode.classList.add('before'); } else if (placement === 'right') { cardIndex = this.activeCardIndex + 1; if (cardIndex >= this._cardStack.length) { insertBuddy = null; } else { insertBuddy = this._cardsNode.children[cardIndex]; } domNode.classList.add('after'); } this._cardStack.splice(cardIndex, 0, domNode); if (!args.cachedNode) { this._cardsNode.insertBefore(domNode, insertBuddy); } hookupInputAreaResetButtons(domNode); if (!domNode.callHeaderFontSize) { window.dispatchEvent(new CustomEvent('lazyload', { detail: domNode })); } if ('postInsert' in domNode) { domNode.postInsert(); } if (showMethod !== 'none') { if (!args.cachedNode) { domNode.clientWidth; } this._showCard(cardIndex, showMethod, 'forward'); } if (args.onPushed) { args.onPushed(domNode); } }, pushOrTellCard: function (type, showMethod, args, placement) { var query = type; if (this.hasCard(query)) { this.tellCard(query, args); return false; } else { this.pushCard.apply(this, Array.slice(arguments)); return true; } }, setStatusColor: function (element) { var color; if (!element) { element = this._cardStack[this.activeCardIndex]; } var statusElement = element.dataset.statuscolor ? element : element.querySelector('[data-statuscolor]'); if (statusElement) { color = statusElement.dataset.statuscolor; if (color === 'default') { color = null; } else if (color === 'background') { color = getComputedStyle(statusElement).backgroundColor; } } else { color = getComputedStyle(element).backgroundColor; } if (color && color.indexOf('rgb') !== 0 && color.indexOf('#') !== 0) { color = null; } color = color || this._statusColorMeta.dataset.statuscolor; var existingColor = this._statusColorMeta.getAttribute('content'); if (color !== existingColor) { this._statusColorMeta.setAttribute('content', color); } }, _findCardUsingType: function (type) { for (var i = 0; i < this._cardStack.length; i++) { var domNode = this._cardStack[i]; if (htmlCache.nodeToKey(domNode) === type) { return i; } } }, _findCard: function (query, skipFail) { var result; if (typeof query === 'string') { result = this._findCardUsingType(query, skipFail); } else if (typeof query === 'number') { result = query; } else { result = this._cardStack.indexOf(query); } if (result > -1) { return result; } else if (!skipFail) { throw new Error('Unable to find card with query:', query); } else { return undefined; } }, hasCard: function (query) { if (this._pendingPush && this._pendingPush === query) { return true; } return this._findCard(query, true) > -1; }, isVisible: function (domNode) { return !!(domNode && domNode.classList.contains('center')); }, findCardObject: function (query) { return this._cardStack[this._findCard(query)]; }, getCurrentCardType: function () { var result = null, card = this._cardStack[this.activeCardIndex]; if (this._pendingPush) { result = this._pendingPush; } else if (card) { result = htmlCache.nodeToKey(card); } return result; }, folderSelector: function (model, callback, filter) { var self = this; require(['value_selector'], function (ValueSelector) { if (!self.folderPrompt) { var selectorTitle = mozL10n.get('messages-folder-select'); self.folderPrompt = new ValueSelector(selectorTitle); } model.latestOnce('foldersSlice', function (foldersSlice) { var folders = foldersSlice.items; folders.forEach(function (folder) { var isMatch = !filter || filter(folder); if (folder.neededForHierarchy || isMatch) { self.folderPrompt.addToList(folder.name, folder.depth, isMatch, function (folder) { return function () { self.folderPrompt.hide(); callback(folder); }; }(folder)); } }); self.folderPrompt.show(); }); }); }, moveToCard: function (query, showMethod) { this._showCard(this._findCard(query), showMethod || 'animate'); }, tellCard: function (query, what) { var cardIndex = this._findCard(query), domNode = this._cardStack[cardIndex]; if (!('told' in domNode)) { console.warn('Tried to tell a card that\'s not listening!', query, what); } else { domNode.told(what); } }, removeCardAndSuccessors: function (cardDomNode, showMethod, numCards, nextCardSpec, skipDefault) { if (!this._cardStack.length) { return; } if (cardDomNode && this._cardStack.length === 1 && !skipDefault) { return cards.pushDefaultCard(function () { this.removeCardAndSuccessors(cardDomNode, showMethod, numCards, nextCardSpec); }.bind(this)); } var firstIndex, iCard, domNode; if (cardDomNode === undefined) { throw new Error('undefined is not a valid card spec!'); } else if (cardDomNode === null) { firstIndex = 0; this._zIndex = 0; } else { for (iCard = this._cardStack.length - 1; iCard >= 0; iCard--) { domNode = this._cardStack[iCard]; if (domNode === cardDomNode) { firstIndex = iCard; break; } } if (firstIndex === undefined) { throw new Error('No card represented by that DOM node'); } } if (!numCards) { numCards = this._cardStack.length - firstIndex; } if (showMethod === 'none') { if (cardDomNode && cardDomNode.classList.contains('anim-overlay')) { this._zIndex -= 10; } } else { var nextCardIndex = -1; if (nextCardSpec) { nextCardIndex = this._findCard(nextCardSpec); } else if (this._cardStack.length) { nextCardIndex = Math.min(firstIndex - 1, this._cardStack.length - 1); } if (nextCardIndex > -1) { this._showCard(nextCardIndex, showMethod, 'back'); } } if (firstIndex <= this.activeCardIndex) { this.activeCardIndex -= numCards; if (this.activeCardIndex < -1) { this.activeCardIndex = -1; } } var deadDomNodes = this._cardStack.splice(firstIndex, numCards); for (iCard = 0; iCard < deadDomNodes.length; iCard++) { domNode = deadDomNodes[iCard]; try { domNode.die(); } catch (ex) { console.warn('Problem cleaning up card:', ex, '\n', ex.stack); } switch (showMethod) { case 'animate': case 'immediate': this._animatingDeadDomNodes.push(domNode); break; case 'none': domNode.parentNode.removeChild(domNode); break; } } this._setScreenReaderVisibility(); }, removeAllCards: function () { return this.removeCardAndSuccessors(null, 'none'); }, _showCard: function (cardIndex, showMethod, navDirection) { if (cardIndex === this.activeCardIndex) { return; } var activeElement = document.activeElement; if (activeElement && activeElement.blur) { activeElement.blur(); } if (cardIndex > this._cardStack.length - 1) { cardIndex = this._cardStack.length - 1; } if (this.activeCardIndex > this._cardStack.length - 1) { this.activeCardIndex = -1; } if (this.activeCardIndex === -1) { this.activeCardIndex = cardIndex === 0 ? cardIndex : cardIndex - 1; } var domNode = cardIndex !== null ? this._cardStack[cardIndex] : null; var beginNode = this._cardStack[this.activeCardIndex]; var endNode = this._cardStack[cardIndex]; var isForward = navDirection === 'forward'; if (this._cardStack.length === 1) { this._zIndex = 0; } if (isForward && endNode.classList.contains('anim-overlay')) { beginNode = null; this._zIndex += 10; } if (beginNode && beginNode.classList.contains('anim-overlay')) { if (isForward) { if (showMethod !== 'immediate') { if (beginNode.classList.contains('anim-vertical')) { removeClass(beginNode, 'anim-vertical'); addClass(beginNode, 'disabled-anim-vertical'); } else if (beginNode.classList.contains('anim-fade')) { removeClass(beginNode, 'anim-fade'); addClass(beginNode, 'disabled-anim-fade'); } } } else { this.setStatusColor(endNode); endNode = null; this._zIndex -= 10; } } if (endNode && isForward && this._zIndex) { endNode.style.zIndex = this._zIndex; } var cardsNode = this._cardsNode; if (endNode) { this.setStatusColor(endNode); } if (showMethod === 'immediate') { addClass(beginNode, 'no-anim'); addClass(endNode, 'no-anim'); cardsNode.clientWidth; this._eatingEventsUntilNextCard = false; } else if (showMethod === 'none') { } else { this._transitionCount = beginNode && endNode ? 2 : 1; this._eatingEventsUntilNextCard = true; } if (this.activeCardIndex === cardIndex) { removeClass(beginNode, 'before'); removeClass(beginNode, 'after'); addClass(beginNode, 'center'); } else if (this.activeCardIndex > cardIndex) { removeClass(beginNode, 'center'); addClass(beginNode, 'after'); removeClass(endNode, 'before'); addClass(endNode, 'center'); } else { removeClass(beginNode, 'center'); addClass(beginNode, 'before'); removeClass(endNode, 'after'); addClass(endNode, 'center'); } if (showMethod === 'immediate') { cardsNode.clientWidth; removeClass(beginNode, 'no-anim'); removeClass(endNode, 'no-anim'); this._onCardVisible(domNode); } toaster.hide(); this.activeCardIndex = cardIndex; this._setScreenReaderVisibility(); }, _setScreenReaderVisibility: function () { this._cardStack.forEach(function (card, index) { card.setAttribute('aria-hidden', index !== this.activeCardIndex); }, this); }, _onTransitionEnd: function (event) { if (!event.target.classList.contains('card')) { return; } var activeCard = this._cardStack[this.activeCardIndex]; if (!activeCard) { return; } if (this._transitionCount > 0) { this._transitionCount -= 1; } if (this._transitionCount === 0) { if (this._eatingEventsUntilNextCard) { this._eatingEventsUntilNextCard = false; } if (this._animatingDeadDomNodes.length) { setTimeout(function () { this._animatingDeadDomNodes.forEach(function (domNode) { if (domNode.parentNode) { domNode.parentNode.removeChild(domNode); } }); this._animatingDeadDomNodes = []; }.bind(this), 100); } var endNode = activeCard; if (endNode.classList.contains('disabled-anim-vertical')) { removeClass(endNode, 'disabled-anim-vertical'); addClass(endNode, 'anim-vertical'); } else if (endNode.classList.contains('disabled-anim-fade')) { removeClass(endNode, 'disabled-anim-fade'); addClass(endNode, 'anim-fade'); } if (this._afterTransitionAction) { var afterTransitionAction = this._afterTransitionAction; this._afterTransitionAction = null; afterTransitionAction(); } this._onCardVisible(activeCard); var nextCards = activeCard.nextCards; if (nextCards) { console.log('Preloading cards: ' + nextCards); require(nextCards.map(function (id) { return 'cards/' + id; })); } } }, _onCardVisible: function (domNode) { if (domNode.onCardVisible) { domNode.onCardVisible(); } this._emitStartupEvents(domNode.skipEmitContentEvents); }, _emitStartupEvents: function (skipEmitContentEvents) { if (!this._startupEventsEmitted) { if (window.startupCacheEventsSent) { window.performance.mark('contentInteractive'); } else { window.performance.mark('navigationLoaded'); } window.performance.mark('navigationInteractive'); if (!skipEmitContentEvents) { evt.emit('metrics:contentDone'); } this._startupEventsEmitted = true; } }, eatEventsUntilNextCard: function () { this._eatingEventsUntilNextCard = true; }, stopEatingEvents: function () { this._eatingEventsUntilNextCard = false; }, assertNoCards: function () { if (this._cardStack.length) { throw new Error('There are ' + this._cardStack.length + ' cards but' + ' there should be ZERO'); } } }; return cards; }); define('array', ['require'], function (require) { var array = { indexOfGeneric: function (array, callback, thisObject) { var result = -1; array.some(function (value, index) { if (callback.call(thisObject, value)) { result = index; return true; } }); return result; } }; return array; }); define('header_cursor', [ 'require', 'array', 'evt' ], function (require) { var array = require('array'), evt = require('evt'); function makeListener(type, obj) { return function () { var args = Array.slice(arguments); this.emit.apply(this, ['messages_' + type].concat(args)); }.bind(obj); } function HeaderCursor(model) { evt.Emitter.call(this); this.model = model; this.onLatestFolder = this.onLatestFolder.bind(this); this.searchMode = 'nonsearch'; } HeaderCursor.prototype = evt.mix({ currentMessage: null, messagesSlice: null, expectingMessageSuid: null, sliceEvents: [ 'splice', 'change', 'status', 'remove', 'complete' ], _inited: false, init: function () { this._inited = true; this.on('messages_splice', this.onMessagesSplice.bind(this)); this.on('messages_remove', this.onMessagesSpliceRemove.bind(this)); this.on('messages_complete', function () { if (this.messagesSlice) { this.messagesSlice.oncomplete = makeListener('complete', this); } }.bind(this)); this.model.latest('folder', this.onLatestFolder); }, advance: function (direction) { var index = this.indexOfMessageById(this.currentMessage.header.id); switch (direction) { case 'previous': index -= 1; break; case 'next': index += 1; break; } var messages = this.messagesSlice.items; if (index < 0 || index >= messages.length) { return; } this.setCurrentMessageByIndex(index); }, setCurrentMessageBySuid: function (messageSuid) { this.expectingMessageSuid = messageSuid; this.checkExpectingMessageSuid(); }, checkExpectingMessageSuid: function (eventIfNotFound) { var messageSuid = this.expectingMessageSuid; var model = this.model; if (!messageSuid || !model.folder || model.folder.type !== 'inbox') { return; } var index = this.indexOfMessageById(messageSuid); if (index > -1) { this.expectingMessageSuid = null; return this.setCurrentMessageByIndex(index); } if (eventIfNotFound) { console.error('header_cursor could not find messageSuid ' + messageSuid + ', emitting messageSuidNotFound'); this.emit('messageSuidNotFound', messageSuid); } }, setCurrentMessage: function (header) { if (!header) { return; } this.setCurrentMessageByIndex(this.indexOfMessageById(header.id)); }, setCurrentMessageByIndex: function (index) { var messages = this.messagesSlice.items; if (index === -1 || index > messages.length - 1) { return; } var header = messages[index]; if ('header' in header) { header = header.header; } var currentMessage = new CurrentMessage(header, { hasPrevious: index !== 0, hasNext: index !== messages.length - 1 }); this.emit('currentMessage', currentMessage, index); this.currentMessage = currentMessage; }, indexOfMessageById: function (id) { var messages = this.messagesSlice && this.messagesSlice.items || []; return array.indexOfGeneric(messages, function (message) { var other = 'header' in message ? message.header.id : message.id; return other === id; }); }, onLatestFolder: function (folder) { if (!this.model.foldersSlice) { return; } this.freshMessagesSlice(); }, startSearch: function (phrase, whatToSearch) { this.searchMode = 'search'; this.bindToSlice(this.model.api.searchFolderMessages(this.model.folder, phrase, whatToSearch)); }, endSearch: function () { this.resetMessageSlice(); this.searchMode = 'nonsearch'; this.freshMessagesSlice(); }, freshMessagesSlice: function () { this.bindToSlice(this.model.api.viewFolderMessages(this.model.folder)); }, bindToSlice: function (messagesSlice) { this.resetMessageSlice(); this.messagesSlice = messagesSlice; this.sliceEvents.forEach(function (type) { messagesSlice['on' + type] = makeListener(type, this); }.bind(this)); }, onMessagesSplice: function (index, howMany, addedItems, requested, moreExpected) { if (!this.messagesSlice) { return; } if (this.messagesSlice.atTop && this.expectingMessageSuid && this.messagesSlice.items && this.messagesSlice.items.length) { this.checkExpectingMessageSuid(true); } }, onMessagesSpliceRemove: function (removedHeader, removedFromIndex) { if (this.currentMessage !== removedHeader) { return this.setCurrentMessage(this.currentMessage); } var messages = this.messagesSlice.items; if (messages.length === 0) { return this.currentMessage = null; } var index = Math.min(removedFromIndex, messages.length - 1); var message = this.messagesSlice.items[index]; this.setCurrentMessage(message); }, resetMessageSlice: function () { if (this.messagesSlice) { this.messagesSlice.die(); this.messagesSlice = null; } this.currentMessage = null; }, die: function () { this.model.removeListener('folder', this.onLatestFolder); this.resetMessageSlice(); } }); var oldOn = HeaderCursor.prototype.on; HeaderCursor.prototype.on = function () { if (!this._inited) { this.init(); } return oldOn.apply(this, arguments); }; function CurrentMessage(header, siblings) { this.header = header; this.siblings = siblings; } CurrentMessage.prototype = { header: null, siblings: null }; HeaderCursor.CurrentMessage = CurrentMessage; return HeaderCursor; }); (function (exports) { 'use strict'; const HEADER_SIZES = [ 16, 17, 18, 19, 20, 21, 22, 23 ]; var FontSizeUtils = { _cachedContexts: {}, _getCachedContext: function (fontSize, fontFamily, fontStyle) { fontStyle = fontStyle || 'italic'; var cache = this._cachedContexts; var ctx = cache[fontSize] && cache[fontSize][fontFamily] ? cache[fontSize][fontFamily][fontStyle] : null; if (!ctx) { var canvas = document.createElement('canvas'); canvas.setAttribute('moz-opaque', 'true'); canvas.setAttribute('width', '1'); canvas.setAttribute('height', '1'); ctx = canvas.getContext('2d', { willReadFrequently: true }); ctx.font = fontStyle + ' ' + fontSize + 'px ' + fontFamily; if (!cache[fontSize]) { cache[fontSize] = {}; } if (!cache[fontSize][fontFamily]) { cache[fontSize][fontFamily] = {}; } cache[fontSize][fontFamily][fontStyle] = ctx; } return ctx; }, resetCache: function () { this._cachedContexts = {}; }, _textChangeObserver: null, _handleTextChanges: function (mutations) { for (var i = 0; i < mutations.length; i++) { this._reformatHeaderText(mutations[i].target); } }, _getTextChangeObserver: function () { if (!this._textChangeObserver) { this._textChangeObserver = new MutationObserver(this._handleTextChanges.bind(this)); } return this._textChangeObserver; }, _observeHeaderChanges: function (element) { var observer = this._getTextChangeObserver(); observer.observe(element, { childList: true }); }, _reformatHeaderText: function (header) { if (header.textContent.trim() === '') { return; } this.resetCentering(header); var style = this.getStyleProperties(header); style.textWidth = this.autoResizeElement(header, style); this.centerTextToScreen(header, style); }, _registerHeadersInSubtree: function (domNode) { if (!domNode) { return; } var headers = domNode.querySelectorAll('header > h1'); for (var i = 0; i < headers.length; i++) { window.requestAnimationFrame(function (header) { this._reformatHeaderText(header); this._observeHeaderChanges(header); }.bind(this, headers[i])); } }, getFontWidth: function (string, fontSize, fontFamily, fontStyle) { var ctx = this._getCachedContext(fontSize, fontFamily, fontStyle); return ctx.measureText(string).width; }, getMaxFontSizeInfo: function (string, allowedSizes, fontFamily, maxWidth) { var fontSize; var resultWidth; var i = allowedSizes.length - 1; do { fontSize = allowedSizes[i]; resultWidth = this.getFontWidth(string, fontSize, fontFamily); i--; } while (resultWidth > maxWidth && i >= 0); return { fontSize: fontSize, overflow: resultWidth > maxWidth, textWidth: resultWidth }; }, _overflowCountCache: -1, getOverflowCount: function (string, fontSize, fontFamily, maxWidth) { var substring; var resultWidth; var overflowCount = -1; if (string.length > this._overflowCountCache) { substring = string.substr(0, string.length - this._overflowCountCache); resultWidth = this.getFontWidth(substring, fontSize, fontFamily); if (resultWidth > maxWidth) { overflowCount = this._overflowCountCache; } } do { overflowCount++; substring = string.substr(0, string.length - overflowCount); resultWidth = this.getFontWidth(substring, fontSize, fontFamily); } while (substring.length > 0 && resultWidth > maxWidth); this._overflowCountCache = overflowCount; return overflowCount; }, getAllowedSizes: function (element) { if (element.tagName === 'H1' && element.parentNode.tagName === 'HEADER') { return HEADER_SIZES; } return []; }, getContentWidth: function (style) { var width = parseInt(style.width, 10); if (style.boxSizing === 'border-box') { width -= parseInt(style.paddingRight, 10) + parseInt(style.paddingLeft, 10); } return width; }, getStyleProperties: function (element) { var style = window.getComputedStyle(element); var contentWidth = this.getContentWidth(style); if (isNaN(contentWidth)) { contentWidth = 0; } return { fontFamily: style.fontFamily, contentWidth: contentWidth, paddingRight: parseInt(style.paddingRight, 10), paddingLeft: parseInt(style.paddingLeft, 10), offsetLeft: element.offsetLeft }; }, autoResizeElement: function (element, styleOptions) { var allowedSizes = this.getAllowedSizes(element); if (allowedSizes.length === 0) { return 0; } var contentWidth = styleOptions.contentWidth || this.getContentWidth(element); var fontFamily = styleOptions.fontFamily || getComputedStyle(element).fontFamily; var info = this.getMaxFontSizeInfo(element.textContent.trim(), allowedSizes, fontFamily, contentWidth); element.style.fontSize = info.fontSize + 'px'; return info.textWidth; }, resetCentering: function (element) { element.style.marginLeft = element.style.marginRight = '0'; }, centerTextToScreen: function (element, styleOptions) { var minHeaderWidth = styleOptions.textWidth + styleOptions.paddingRight + styleOptions.paddingLeft; var sideSpaceLeft = styleOptions.offsetLeft; var sideSpaceRight = this.getWindowWidth() - sideSpaceLeft - styleOptions.contentWidth - styleOptions.paddingRight - styleOptions.paddingLeft; if (sideSpaceLeft === sideSpaceRight) { return; } var margin = Math.max(sideSpaceLeft, sideSpaceRight); if (minHeaderWidth + margin * 2 < this.getWindowWidth() - 1) { element.style.marginLeft = element.style.marginRight = margin + 'px'; } }, _initHeaderFormatting: function () { if (navigator.mozL10n) { navigator.mozL10n.once(function () { this._registerHeadersInSubtree(document.body); }.bind(this)); } else { this._registerHeadersInSubtree(document.body); } }, init: function () { window.addEventListener('lazyload', function (evt) { this._registerHeadersInSubtree(evt.detail); }.bind(this)); if (document.readyState === 'loading') { window.addEventListener('DOMContentLoaded', function () { this._initHeaderFormatting(); }.bind(this)); } else { this._initHeaderFormatting(); } }, getWindowWidth: function () { return window.innerWidth; } }; FontSizeUtils.init(); exports.FontSizeUtils = FontSizeUtils; }(this)); define('font_size_utils', function () { }); define('metrics', [ 'require', 'evt' ], function (require) { var evt = require('evt'), apiDone = false, contentDone = false; function checkAppLoaded() { if (apiDone && contentDone) { window.performance.mark('fullyLoaded'); } } evt.once('metrics:apiDone', function onApiDone() { apiDone = true; checkAppLoaded(); }); evt.once('metrics:contentDone', function () { contentDone = true; if (!window.startupCacheEventsSent) { window.performance.mark('visuallyLoaded'); window.performance.mark('contentInteractive'); } checkAppLoaded(); }); }); define('wake_locks', [ 'require', 'evt' ], function (require) { 'use strict'; var lockTimeouts = {}, evt = require('evt'), allLocks = {}, dataOps = {}, dataOpsTimeoutId = 0, maxLockInterval = 45000, dataOpsTimeout = 5000; function close() { dataOps = {}; dataOpsTimeoutId = 0; if (document.hidden) { console.log('email: cronsync wake locks expired, force closing app'); window.close(); } else { console.log('email: cronsync wake locks expired, but app visible, ' + 'not force closing'); Object.keys(allLocks).forEach(function (accountKey) { clearLocks(accountKey); }); } } function closeIfNoDataOps() { var dataOpsKeys = Object.keys(dataOps); if (!dataOpsKeys.length) { return close(); } console.log('email: cronsync wake lock force shutdown waiting on email ' + 'data operations: ' + dataOpsKeys.join(', ')); dataOpsTimeoutId = setTimeout(close, dataOpsTimeout); } evt.on('uiDataOperationStart', function (dataId) { dataOps[dataId] = true; }); evt.on('uiDataOperationStop', function (dataId) { delete dataOps[dataId]; if (dataOpsTimeoutId && !Object.keys(dataOps).length) { clearTimeout(dataOpsTimeoutId); close(); } }); function clearLocks(accountKey) { console.log('email: clearing wake locks for "' + accountKey + '"'); var lockTimeoutId = lockTimeouts[accountKey]; if (lockTimeoutId) { clearTimeout(lockTimeoutId); } lockTimeouts[accountKey] = 0; var locks = allLocks[accountKey]; allLocks[accountKey] = null; if (locks) { locks.forEach(function (lock) { lock.unlock(); }); } } function makeAccountKey(accountIds) { return 'id' + accountIds.join(' '); } function onCronStop(accountIds) { clearLocks(makeAccountKey(accountIds)); } evt.on('cronSyncWakeLocks', function (accountKey, locks) { if (lockTimeouts[accountKey]) { clearLocks(accountKey); } allLocks[accountKey] = locks; lockTimeouts[accountKey] = setTimeout(closeIfNoDataOps, maxLockInterval); }); evt.on('cronSyncStop', onCronStop); }); define('mail_app', [ 'require', 'exports', 'module', 'l10n!', 'activity_composer_data', 'cards', 'evt', 'model_create', 'header_cursor', 'html_cache', 'font_size_utils', 'metrics', 'wake_locks' ], function (require, exports, module) { var mozL10n = require('l10n!'), activityComposerData = require('activity_composer_data'), cards = require('cards'), evt = require('evt'), model = require('model_create').defaultModel, HeaderCursor = require('header_cursor'), htmlCache = require('html_cache'), waitingRawActivity, activityCallback; require('font_size_utils'); require('metrics'); require('wake_locks'); var started = false; function pushStartCard(id, addedArgs) { var args = { model: model }; if (addedArgs) { Object.keys(addedArgs).forEach(function (key) { args[key] = addedArgs[key]; }); } if (!started) { var cachedNode = cards._cardsNode.children[0]; if (cachedNode && id === htmlCache.nodeToKey(cachedNode)) { mozL10n.translateFragment(cachedNode); args.cachedNode = cachedNode; } document.body.classList.add('content-visible'); } cards.pushCard(id, 'immediate', args); started = true; } document.addEventListener('visibilitychange', function onVisibilityChange() { if (!document.hidden && !started && startupData && startupData.entry === 'alarm') { pushStartCard('message_list'); } }, false); function isCurrentCardMessageList() { var cardType = cards.getCurrentCardType(); return cardType && cardType === 'message_list'; } evt.on('addAccount', function () { cards.removeAllCards(); pushStartCard('setup_account_info', { allowBack: true }); }); function resetApp() { activityCallback = waitingRawActivity = undefined; cards.removeAllCards(); model.init(false, function () { var cardId = model.hasAccount() ? 'message_list' : 'setup_account_info'; pushStartCard(cardId); }); } evt.on('accountDeleted', resetApp); evt.on('resetApp', resetApp); evt.on('setupAccountCanceled', function (fromCard) { if (waitingRawActivity) { waitingRawActivity.postError('cancelled'); } if (!model.foldersSlice) { evt.emit('resetApp'); } else { cards.removeCardAndSuccessors(fromCard, 'animate', 1); } }); evt.on('showLatestAccount', function () { cards.removeAllCards(); model.latestOnce('acctsSlice', function (acctsSlice) { var account = acctsSlice.items[acctsSlice.items.length - 1]; model.changeAccount(account, function () { pushStartCard('message_list', { onPushed: function () { if (activityCallback) { var activityCb = activityCallback; activityCallback = null; activityCb(); return true; } return false; } }); }); }); }); evt.on('apiBadLogin', function (account, problem, whichSide) { switch (problem) { case 'bad-user-or-pass': cards.pushCard('setup_fix_password', 'animate', { account: account, whichSide: whichSide, restoreCard: cards.activeCardIndex }, 'right'); break; case 'imap-disabled': case 'pop3-disabled': cards.pushCard('setup_fix_gmail', 'animate', { account: account, restoreCard: cards.activeCardIndex }, 'right'); break; case 'needs-app-pass': cards.pushCard('setup_fix_gmail_twofactor', 'animate', { account: account, restoreCard: cards.activeCardIndex }, 'right'); break; case 'needs-oauth-reauth': cards.pushCard('setup_fix_oauth2', 'animate', { account: account, restoreCard: cards.activeCardIndex }, 'right'); break; } }); cards.init(); if (!model.inited) { model.init(); } var startupData = globalOnAppMessage({ activity: function (rawActivity) { if (!isCurrentCardMessageList()) { cards.removeAllCards(); } function activityCompose() { var cardArgs = { activity: rawActivity, composerData: activityComposerData(rawActivity) }; pushStartCard('compose', cardArgs); } if (globalOnAppMessage.hasAccount()) { activityCompose(); } else { activityCallback = activityCompose; waitingRawActivity = rawActivity; pushStartCard('setup_account_info', { allowBack: true, launchedFromActivity: true }); } }, notification: function (data) { data = data || {}; var type = data.type || ''; var folderType = data.folderType || 'inbox'; model.latestOnce('foldersSlice', function latestFolderSlice() { function onCorrectFolder() { if (!isCurrentCardMessageList()) { cards.removeAllCards(); } if (type === 'message_list') { pushStartCard('message_list', {}); } else if (type === 'message_reader') { var headerCursor = new HeaderCursor(model); headerCursor.setCurrentMessageBySuid(data.messageSuid); pushStartCard(type, { messageSuid: data.messageSuid, headerCursor: headerCursor }); } else { console.error('unhandled notification type: ' + type); } } var acctsSlice = model.acctsSlice, accountId = data.accountId; if (model.account.id === accountId) { return model.selectFirstFolderWithType(folderType, onCorrectFolder); } else { var newAccount; acctsSlice.items.some(function (account) { if (account.id === accountId) { newAccount = account; return true; } }); if (newAccount) { model.changeAccount(newAccount, function () { model.selectFirstFolderWithType(folderType, onCorrectFolder); }); } } }); } }); console.log('startupData: ' + JSON.stringify(startupData, null, ' ')); if (startupData.entry === 'default' || startupData.entry === 'alarm' && !document.hidden) { pushStartCard(startupData.view); } }); if (typeof TestUrlResolver === 'undefined') { requirejs.config({ waitSeconds: 0, baseUrl: 'js', paths: { l10nbase: '../shared/js/l10n', moz_intl: '../shared/js/moz_intl', style: '../style', shared: '../shared' }, map: { '*': { 'api': 'ext/main-frame-setup' } }, shim: { moz_intl: { deps: ['l10nbase'], exports: 'mozIntl' }, 'shared/js/mime_mapper': { exports: 'MimeMapper' }, 'shared/js/notification_helper': { exports: 'NotificationHelper' }, 'shared/js/accessibility_helper': { exports: 'AccessibilityHelper' }, 'shared/js/gesture_detector': { exports: 'GestureDetector' } }, config: { template: { tagToId: function (tag) { return tag.replace(/^cards-/, 'cards/').replace(/^lst-/, 'cards/lst/').replace(/^msg-/, 'cards/msg/').replace(/^cmp-/, 'cards/cmp/').replace(/-/g, '_'); } }, element: { idToTag: function (id) { return id.toLowerCase().replace(/^cards\/lst\//, 'lst-').replace(/^cards\/msg\//, 'msg-').replace(/^cards\/cmp\//, 'cmp-').replace(/[^a-z]/g, '-'); } } }, definePrim: 'prim' }); } if (navigator.mozAudioChannelManager) { navigator.mozAudioChannelManager.volumeControlChannel = 'notification'; } if (window.startupOnModelLoaded) { requirejs([ 'console_hook', 'model_create' ], function (hook, modelCreate) { var model = modelCreate.defaultModel; model.init(); window.startupOnModelLoaded(model, function () { require(['mail_app']); }); }); } else { requirejs([ 'console_hook', 'mail_app' ]); } ; define('config', function () { }); (function (window, undefined) { 'use strict'; function L10nError(message, id, loc) { this.name = 'L10nError'; this.message = message; this.id = id; this.loc = loc; } L10nError.prototype = Object.create(Error.prototype); L10nError.prototype.constructor = L10nError; var io = { _load: function (type, url, callback, sync) { var xhr = new XMLHttpRequest(); var needParse; if (xhr.overrideMimeType) { xhr.overrideMimeType(type); } xhr.open('GET', url, !sync); if (type === 'application/json') { if (sync) { needParse = true; } else { xhr.responseType = 'json'; } } xhr.addEventListener('load', function io_onload(e) { if (e.target.status === 200 || e.target.status === 0) { var res = e.target.response || e.target.responseText; callback(null, needParse ? JSON.parse(res) : res); } else { callback(new L10nError('Not found: ' + url)); } }); xhr.addEventListener('error', callback); xhr.addEventListener('timeout', callback); try { xhr.send(null); } catch (e) { if (e.name === 'NS_ERROR_FILE_NOT_FOUND') { callback(new L10nError('Not found: ' + url)); } else { throw e; } } }, load: function (url, callback, sync) { return io._load('text/plain', url, callback, sync); }, loadJSON: function (url, callback, sync) { return io._load('application/json', url, callback, sync); } }; function EventEmitter() { } EventEmitter.prototype.emit = function ee_emit() { if (!this._listeners) { return; } var args = Array.prototype.slice.call(arguments); var type = args.shift(); if (!this._listeners[type]) { return; } var typeListeners = this._listeners[type].slice(); for (var i = 0; i < typeListeners.length; i++) { typeListeners[i].apply(this, args); } }; EventEmitter.prototype.addEventListener = function ee_add(type, listener) { if (!this._listeners) { this._listeners = {}; } if (!(type in this._listeners)) { this._listeners[type] = []; } this._listeners[type].push(listener); }; EventEmitter.prototype.removeEventListener = function ee_rm(type, listener) { if (!this._listeners) { return; } var typeListeners = this._listeners[type]; var pos = typeListeners.indexOf(listener); if (pos === -1) { return; } typeListeners.splice(pos, 1); }; function getPluralRule(lang) { var locales2rules = { 'af': 3, 'ak': 4, 'am': 4, 'ar': 1, 'asa': 3, 'az': 0, 'be': 11, 'bem': 3, 'bez': 3, 'bg': 3, 'bh': 4, 'bm': 0, 'bn': 3, 'bo': 0, 'br': 20, 'brx': 3, 'bs': 11, 'ca': 3, 'cgg': 3, 'chr': 3, 'cs': 12, 'cy': 17, 'da': 3, 'de': 3, 'dv': 3, 'dz': 0, 'ee': 3, 'el': 3, 'en': 3, 'eo': 3, 'es': 3, 'et': 3, 'eu': 3, 'fa': 0, 'ff': 5, 'fi': 3, 'fil': 4, 'fo': 3, 'fr': 5, 'fur': 3, 'fy': 3, 'ga': 8, 'gd': 24, 'gl': 3, 'gsw': 3, 'gu': 3, 'guw': 4, 'gv': 23, 'ha': 3, 'haw': 3, 'he': 2, 'hi': 4, 'hr': 11, 'hu': 0, 'id': 0, 'ig': 0, 'ii': 0, 'is': 3, 'it': 3, 'iu': 7, 'ja': 0, 'jmc': 3, 'jv': 0, 'ka': 0, 'kab': 5, 'kaj': 3, 'kcg': 3, 'kde': 0, 'kea': 0, 'kk': 3, 'kl': 3, 'km': 0, 'kn': 0, 'ko': 0, 'ksb': 3, 'ksh': 21, 'ku': 3, 'kw': 7, 'lag': 18, 'lb': 3, 'lg': 3, 'ln': 4, 'lo': 0, 'lt': 10, 'lv': 6, 'mas': 3, 'mg': 4, 'mk': 16, 'ml': 3, 'mn': 3, 'mo': 9, 'mr': 3, 'ms': 0, 'mt': 15, 'my': 0, 'nah': 3, 'naq': 7, 'nb': 3, 'nd': 3, 'ne': 3, 'nl': 3, 'nn': 3, 'no': 3, 'nr': 3, 'nso': 4, 'ny': 3, 'nyn': 3, 'om': 3, 'or': 3, 'pa': 3, 'pap': 3, 'pl': 13, 'ps': 3, 'pt': 3, 'rm': 3, 'ro': 9, 'rof': 3, 'ru': 11, 'rwk': 3, 'sah': 0, 'saq': 3, 'se': 7, 'seh': 3, 'ses': 0, 'sg': 0, 'sh': 11, 'shi': 19, 'sk': 12, 'sl': 14, 'sma': 7, 'smi': 7, 'smj': 7, 'smn': 7, 'sms': 7, 'sn': 3, 'so': 3, 'sq': 3, 'sr': 11, 'ss': 3, 'ssy': 3, 'st': 3, 'sv': 3, 'sw': 3, 'syr': 3, 'ta': 3, 'te': 3, 'teo': 3, 'th': 0, 'ti': 4, 'tig': 3, 'tk': 3, 'tl': 4, 'tn': 3, 'to': 0, 'tr': 0, 'ts': 3, 'tzm': 22, 'uk': 11, 'ur': 3, 've': 3, 'vi': 0, 'vun': 3, 'wa': 4, 'wae': 3, 'wo': 0, 'xh': 3, 'xog': 3, 'yo': 0, 'zh': 0, 'zu': 3 }; function isIn(n, list) { return list.indexOf(n) !== -1; } function isBetween(n, start, end) { return typeof n === typeof start && start <= n && n <= end; } var pluralRules = { '0': function () { return 'other'; }, '1': function (n) { if (isBetween(n % 100, 3, 10)) { return 'few'; } if (n === 0) { return 'zero'; } if (isBetween(n % 100, 11, 99)) { return 'many'; } if (n === 2) { return 'two'; } if (n === 1) { return 'one'; } return 'other'; }, '2': function (n) { if (n !== 0 && n % 10 === 0) { return 'many'; } if (n === 2) { return 'two'; } if (n === 1) { return 'one'; } return 'other'; }, '3': function (n) { if (n === 1) { return 'one'; } return 'other'; }, '4': function (n) { if (isBetween(n, 0, 1)) { return 'one'; } return 'other'; }, '5': function (n) { if (isBetween(n, 0, 2) && n !== 2) { return 'one'; } return 'other'; }, '6': function (n) { if (n === 0) { return 'zero'; } if (n % 10 === 1 && n % 100 !== 11) { return 'one'; } return 'other'; }, '7': function (n) { if (n === 2) { return 'two'; } if (n === 1) { return 'one'; } return 'other'; }, '8': function (n) { if (isBetween(n, 3, 6)) { return 'few'; } if (isBetween(n, 7, 10)) { return 'many'; } if (n === 2) { return 'two'; } if (n === 1) { return 'one'; } return 'other'; }, '9': function (n) { if (n === 0 || n !== 1 && isBetween(n % 100, 1, 19)) { return 'few'; } if (n === 1) { return 'one'; } return 'other'; }, '10': function (n) { if (isBetween(n % 10, 2, 9) && !isBetween(n % 100, 11, 19)) { return 'few'; } if (n % 10 === 1 && !isBetween(n % 100, 11, 19)) { return 'one'; } return 'other'; }, '11': function (n) { if (isBetween(n % 10, 2, 4) && !isBetween(n % 100, 12, 14)) { return 'few'; } if (n % 10 === 0 || isBetween(n % 10, 5, 9) || isBetween(n % 100, 11, 14)) { return 'many'; } if (n % 10 === 1 && n % 100 !== 11) { return 'one'; } return 'other'; }, '12': function (n) { if (isBetween(n, 2, 4)) { return 'few'; } if (n === 1) { return 'one'; } return 'other'; }, '13': function (n) { if (isBetween(n % 10, 2, 4) && !isBetween(n % 100, 12, 14)) { return 'few'; } if (n !== 1 && isBetween(n % 10, 0, 1) || isBetween(n % 10, 5, 9) || isBetween(n % 100, 12, 14)) { return 'many'; } if (n === 1) { return 'one'; } return 'other'; }, '14': function (n) { if (isBetween(n % 100, 3, 4)) { return 'few'; } if (n % 100 === 2) { return 'two'; } if (n % 100 === 1) { return 'one'; } return 'other'; }, '15': function (n) { if (n === 0 || isBetween(n % 100, 2, 10)) { return 'few'; } if (isBetween(n % 100, 11, 19)) { return 'many'; } if (n === 1) { return 'one'; } return 'other'; }, '16': function (n) { if (n % 10 === 1 && n !== 11) { return 'one'; } return 'other'; }, '17': function (n) { if (n === 3) { return 'few'; } if (n === 0) { return 'zero'; } if (n === 6) { return 'many'; } if (n === 2) { return 'two'; } if (n === 1) { return 'one'; } return 'other'; }, '18': function (n) { if (n === 0) { return 'zero'; } if (isBetween(n, 0, 2) && n !== 0 && n !== 2) { return 'one'; } return 'other'; }, '19': function (n) { if (isBetween(n, 2, 10)) { return 'few'; } if (isBetween(n, 0, 1)) { return 'one'; } return 'other'; }, '20': function (n) { if ((isBetween(n % 10, 3, 4) || n % 10 === 9) && !(isBetween(n % 100, 10, 19) || isBetween(n % 100, 70, 79) || isBetween(n % 100, 90, 99))) { return 'few'; } if (n % 1000000 === 0 && n !== 0) { return 'many'; } if (n % 10 === 2 && !isIn(n % 100, [ 12, 72, 92 ])) { return 'two'; } if (n % 10 === 1 && !isIn(n % 100, [ 11, 71, 91 ])) { return 'one'; } return 'other'; }, '21': function (n) { if (n === 0) { return 'zero'; } if (n === 1) { return 'one'; } return 'other'; }, '22': function (n) { if (isBetween(n, 0, 1) || isBetween(n, 11, 99)) { return 'one'; } return 'other'; }, '23': function (n) { if (isBetween(n % 10, 1, 2) || n % 20 === 0) { return 'one'; } return 'other'; }, '24': function (n) { if (isBetween(n, 3, 10) || isBetween(n, 13, 19)) { return 'few'; } if (isIn(n, [ 2, 12 ])) { return 'two'; } if (isIn(n, [ 1, 11 ])) { return 'one'; } return 'other'; } }; var index = locales2rules[lang.replace(/-.*$/, '')]; if (!(index in pluralRules)) { return function () { return 'other'; }; } return pluralRules[index]; } var MAX_PLACEABLES = 100; var PropertiesParser = { patterns: null, entryIds: null, init: function () { this.patterns = { comment: /^\s*#|^\s*$/, entity: /^([^=\s]+)\s*=\s*(.*)$/, multiline: /[^\\]\\$/, index: /\{\[\s*(\w+)(?:\(([^\)]*)\))?\s*\]\}/i, unicode: /\\u([0-9a-fA-F]{1,4})/g, entries: /[^\r\n]+/g, controlChars: /\\([\\\n\r\t\b\f\{\}\"\'])/g, placeables: /\{\{\s*([^\s]*?)\s*\}\}/ }; }, parse: function (ctx, source) { if (!this.patterns) { this.init(); } var ast = []; this.entryIds = Object.create(null); var entries = source.match(this.patterns.entries); if (!entries) { return ast; } for (var i = 0; i < entries.length; i++) { var line = entries[i]; if (this.patterns.comment.test(line)) { continue; } while (this.patterns.multiline.test(line) && i < entries.length) { line = line.slice(0, -1) + entries[++i].trim(); } var entityMatch = line.match(this.patterns.entity); if (entityMatch) { try { this.parseEntity(entityMatch[1], entityMatch[2], ast); } catch (e) { if (ctx) { ctx._emitter.emit('parseerror', e); } else { throw e; } } } } return ast; }, parseEntity: function (id, value, ast) { var name, key; var pos = id.indexOf('['); if (pos !== -1) { name = id.substr(0, pos); key = id.substring(pos + 1, id.length - 1); } else { name = id; key = null; } var nameElements = name.split('.'); if (nameElements.length > 2) { throw new L10nError('Error in ID: "' + name + '".' + ' Nested attributes are not supported.'); } var attr; if (nameElements.length > 1) { name = nameElements[0]; attr = nameElements[1]; if (attr[0] === '$') { throw new L10nError('Attribute can\'t start with "$"', id); } } else { attr = null; } this.setEntityValue(name, attr, key, this.unescapeString(value), ast); }, setEntityValue: function (id, attr, key, rawValue, ast) { var pos, v; var value = rawValue.indexOf('{{') > -1 ? this.parseString(rawValue) : rawValue; if (attr) { pos = this.entryIds[id]; if (pos === undefined) { v = { $i: id }; if (key) { v[attr] = {}; v[attr][key] = value; } else { v[attr] = value; } ast.push(v); this.entryIds[id] = ast.length - 1; return; } if (key) { if (typeof ast[pos][attr] === 'string') { ast[pos][attr] = { $x: this.parseIndex(ast[pos][attr]), $v: {} }; } ast[pos][attr].$v[key] = value; return; } ast[pos][attr] = value; return; } if (key) { pos = this.entryIds[id]; if (pos === undefined) { v = {}; v[key] = value; ast.push({ $i: id, $v: v }); this.entryIds[id] = ast.length - 1; return; } if (typeof ast[pos].$v === 'string') { ast[pos].$x = this.parseIndex(ast[pos].$v); ast[pos].$v = {}; } ast[pos].$v[key] = value; return; } ast.push({ $i: id, $v: value }); this.entryIds[id] = ast.length - 1; }, parseString: function (str) { var chunks = str.split(this.patterns.placeables); var complexStr = []; var len = chunks.length; var placeablesCount = (len - 1) / 2; if (placeablesCount >= MAX_PLACEABLES) { throw new L10nError('Too many placeables (' + placeablesCount + ', max allowed is ' + MAX_PLACEABLES + ')'); } for (var i = 0; i < chunks.length; i++) { if (chunks[i].length === 0) { continue; } if (i % 2 === 1) { complexStr.push({ t: 'idOrVar', v: chunks[i] }); } else { complexStr.push(chunks[i]); } } return complexStr; }, unescapeString: function (str) { if (str.lastIndexOf('\\') !== -1) { str = str.replace(this.patterns.controlChars, '$1'); } return str.replace(this.patterns.unicode, function (match, token) { return unescape('%u' + '0000'.slice(token.length) + token); }); }, parseIndex: function (str) { var match = str.match(this.patterns.index); if (!match) { throw new L10nError('Malformed index'); } if (match[2]) { return [ { t: 'idOrVar', v: match[1] }, match[2] ]; } else { return [{ t: 'idOrVar', v: match[1] }]; } } }; var KNOWN_MACROS = ['plural']; var MAX_PLACEABLE_LENGTH = 2500; var rePlaceables = /\{\{\s*(.+?)\s*\}\}/g; var nonLatin1 = /[^\x01-\xFF]/; var FSI = '\u2068'; var PDI = '\u2069'; function createEntry(node, env) { var keys = Object.keys(node); if (typeof node.$v === 'string' && keys.length === 2) { return node.$v; } var attrs; for (var i = 0, key; key = keys[i]; i++) { if (key[0] === '$') { continue; } if (!attrs) { attrs = Object.create(null); } attrs[key] = createAttribute(node[key], env, node.$i + '.' + key); } return { id: node.$i, value: node.$v !== undefined ? node.$v : null, index: node.$x || null, attrs: attrs || null, env: env, dirty: false }; } function createAttribute(node, env, id) { if (typeof node === 'string') { return node; } return { id: id, value: node.$v || (node !== undefined ? node : null), index: node.$x || null, env: env, dirty: false }; } function format(args, entity) { if (typeof entity === 'string') { return [ {}, entity ]; } if (entity.dirty) { throw new L10nError('Cyclic reference detected: ' + entity.id); } entity.dirty = true; var rv; try { rv = resolveValue({}, args, entity.env, entity.value, entity.index); } finally { entity.dirty = false; } return rv; } function resolveIdentifier(args, env, id) { if (KNOWN_MACROS.indexOf(id) > -1) { return [ {}, env['__' + id] ]; } if (args && args.hasOwnProperty(id)) { if (typeof args[id] === 'string' || typeof args[id] === 'number' && !isNaN(args[id])) { return [ {}, args[id] ]; } else { throw new L10nError('Arg must be a string or a number: ' + id); } } if (id in env && id !== '__proto__') { return format(args, env[id]); } throw new L10nError('Unknown reference: ' + id); } function subPlaceable(locals, args, env, id) { var res; try { res = resolveIdentifier(args, env, id); } catch (err) { return [ { error: err }, '{{ ' + id + ' }}' ]; } var value = res[1]; if (typeof value === 'number') { return res; } if (typeof value === 'string') { if (value.length >= MAX_PLACEABLE_LENGTH) { throw new L10nError('Too many characters in placeable (' + value.length + ', max allowed is ' + MAX_PLACEABLE_LENGTH + ')'); } if (locals.contextIsNonLatin1 || value.match(nonLatin1)) { res[1] = FSI + value + PDI; } return res; } return [ {}, '{{ ' + id + ' }}' ]; } function interpolate(locals, args, env, arr) { return arr.reduce(function (prev, cur) { if (typeof cur === 'string') { return [ prev[0], prev[1] + cur ]; } else if (cur.t === 'idOrVar') { var placeable = subPlaceable(locals, args, env, cur.v); return [ prev[0], prev[1] + placeable[1] ]; } }, [ locals, '' ]); } function resolveSelector(args, env, expr, index) { var selectorName = index[0].v; var selector = resolveIdentifier(args, env, selectorName)[1]; if (typeof selector !== 'function') { return selector; } var argValue = index[1] ? resolveIdentifier(args, env, index[1])[1] : undefined; if (selector === env.__plural) { if (argValue === 0 && 'zero' in expr) { return 'zero'; } if (argValue === 1 && 'one' in expr) { return 'one'; } if (argValue === 2 && 'two' in expr) { return 'two'; } } return selector(argValue); } function resolveValue(locals, args, env, expr, index) { if (!expr) { return [ locals, expr ]; } if (typeof expr === 'string' || typeof expr === 'boolean' || typeof expr === 'number') { return [ locals, expr ]; } if (Array.isArray(expr)) { locals.contextIsNonLatin1 = expr.some(function ($_) { return typeof $_ === 'string' && $_.match(nonLatin1); }); return interpolate(locals, args, env, expr); } if (index) { var selector = resolveSelector(args, env, expr, index); if (expr.hasOwnProperty(selector)) { return resolveValue(locals, args, env, expr[selector]); } } if ('other' in expr) { return resolveValue(locals, args, env, expr.other); } throw new L10nError('Unresolvable value'); } var Resolver = { createEntry: createEntry, format: format, rePlaceables: rePlaceables }; function walkContent(node, fn) { if (typeof node === 'string') { return fn(node); } if (node.t === 'idOrVar') { return node; } var rv = Array.isArray(node) ? [] : {}; var keys = Object.keys(node); for (var i = 0, key; key = keys[i]; i++) { if (key === '$i' || key === '$x') { rv[key] = node[key]; } else { rv[key] = walkContent(node[key], fn); } } return rv; } var reAlphas = /[a-zA-Z]/g; var reVowels = /[aeiouAEIOU]/g; var ACCENTED_MAP = 'ȦƁƇḒḖƑƓĦĪ' + 'ĴĶĿḾȠǾƤɊŘ' + 'ŞŦŬṼẆẊẎẐ' + '[\\]^_`' + 'ȧƀƈḓḗƒɠħī' + 'ĵķŀḿƞǿƥɋř' + 'şŧŭṽẇẋẏẑ'; var FLIPPED_MAP = '\u2200ԐↃpƎɟפHIſ' + 'Ӽ\u02E5WNOԀÒᴚS\u22A5\u2229Ʌ' + 'MXʎZ' + '[\\]ᵥ_,' + 'ɐqɔpǝɟƃɥıɾ' + 'ʞʅɯuodbɹsʇnʌʍxʎz'; function makeLonger(val) { return val.replace(reVowels, function (match) { return match + match.toLowerCase(); }); } function replaceChars(map, val) { return val.replace(reAlphas, function (match) { return map.charAt(match.charCodeAt(0) - 65); }); } var reWords = /[^\W0-9_]+/g; function makeRTL(val) { return val.replace(reWords, function (match) { return '\u202E' + match + '\u202C'; }); } var reExcluded = /(%[EO]?\w|\{\s*.+?\s*\}|&[#\w]+;|<\s*.+?\s*>)/; function mapContent(fn, val) { if (!val) { return val; } var parts = val.split(reExcluded); var modified = parts.map(function (part) { if (reExcluded.test(part)) { return part; } return fn(part); }); return modified.join(''); } function Pseudo(id, name, charMap, modFn) { this.id = id; this.translate = mapContent.bind(null, function (val) { return replaceChars(charMap, modFn(val)); }); this.name = this.translate(name); } var PSEUDO = { 'fr-x-psaccent': new Pseudo('fr-x-psaccent', 'Runtime Accented', ACCENTED_MAP, makeLonger), 'ar-x-psbidi': new Pseudo('ar-x-psbidi', 'Runtime Bidi', FLIPPED_MAP, makeRTL) }; function Locale(id, ctx) { this.id = id; this.ctx = ctx; this.isReady = false; this.entries = Object.create(null); this.entries.__plural = getPluralRule(this.isPseudo() ? this.ctx.defaultLocale : id); } Locale.prototype.isPseudo = function () { return this.ctx.qps.indexOf(this.id) !== -1; }; var bindingsIO = { extra: function (id, ver, path, type, callback, errback) { if (type === 'properties') { type = 'text'; } navigator.mozApps.getLocalizationResource(id, ver, path, type).then(callback.bind(null, null), errback); }, app: function (id, ver, path, type, callback, errback, sync) { switch (type) { case 'properties': io.load(path, callback, sync); break; case 'json': io.loadJSON(path, callback, sync); break; } } }; Locale.prototype.build = function L_build(callback) { var sync = !callback; var ctx = this.ctx; var self = this; var l10nLoads = ctx.resLinks.length; function onL10nLoaded(err) { if (err) { ctx._emitter.emit('fetcherror', err); } if (--l10nLoads <= 0) { self.isReady = true; if (callback) { callback(); } } } if (l10nLoads === 0) { onL10nLoaded(); return; } function onJSONLoaded(err, json) { if (!err && json) { self.addAST(json); } onL10nLoaded(err); } function onPropLoaded(err, source) { if (!err && source) { var ast = PropertiesParser.parse(ctx, source); self.addAST(ast); } onL10nLoaded(err); } var idToFetch = this.isPseudo() ? ctx.defaultLocale : this.id; var appVersion = null; var source = 'app'; if (typeof navigator !== 'undefined') { source = navigator.mozL10n._config.localeSources[this.id] || 'app'; appVersion = navigator.mozL10n._config.appVersion; } for (var i = 0; i < ctx.resLinks.length; i++) { var resLink = decodeURI(ctx.resLinks[i]); var path = resLink.replace('{locale}', idToFetch); var type = path.substr(path.lastIndexOf('.') + 1); var cb; switch (type) { case 'json': cb = onJSONLoaded; break; case 'properties': cb = onPropLoaded; break; } bindingsIO[source](this.id, appVersion, path, type, cb, onL10nLoaded, sync); } }; function createPseudoEntry(node, entries) { return Resolver.createEntry(walkContent(node, PSEUDO[this.id].translate), entries); } Locale.prototype.addAST = function (ast) { var createEntry = this.isPseudo() ? createPseudoEntry.bind(this) : Resolver.createEntry; for (var i = 0; i < ast.length; i++) { this.entries[ast[i].$i] = createEntry(ast[i], this.entries); } }; function Context(id) { this.id = id; this.isReady = false; this.isLoading = false; this.defaultLocale = 'en-US'; this.availableLocales = []; this.supportedLocales = []; this.qps = []; this.resLinks = []; this.locales = {}; this._emitter = new EventEmitter(); this._ready = new Promise(this.once.bind(this)); } function reportMissing(id, err) { this._emitter.emit('notfounderror', err); return id; } function getWithFallback(id) { var cur = 0; var loc; var locale; while (loc = this.supportedLocales[cur]) { locale = this.getLocale(loc); if (!locale.isReady) { locale.build(null); } var entry = locale.entries[id]; if (entry === undefined) { cur++; reportMissing.call(this, id, new L10nError('"' + id + '"' + ' not found in ' + loc + ' in ' + this.id, id, loc)); continue; } return entry; } throw new L10nError('"' + id + '"' + ' missing from all supported locales in ' + this.id, id); } function formatTuple(args, entity) { try { return Resolver.format(args, entity); } catch (err) { this._emitter.emit('resolveerror', err); var locals = { error: err }; return [ locals, entity.id ]; } } function formatValue(args, entity) { if (typeof entity === 'string') { return entity; } return formatTuple.call(this, args, entity)[1]; } function formatEntity(args, entity) { var entityTuple = formatTuple.call(this, args, entity); var value = entityTuple[1]; var formatted = { value: value, attrs: null }; if (entity.attrs) { formatted.attrs = Object.create(null); } for (var key in entity.attrs) { var attrTuple = formatTuple.call(this, args, entity.attrs[key]); formatted.attrs[key] = attrTuple[1]; } return formatted; } function formatAsync(fn, id, args) { return this._ready.then(getWithFallback.bind(this, id)).then(fn.bind(this, args), reportMissing.bind(this, id)); } Context.prototype.formatValue = function (id, args) { return formatAsync.call(this, formatValue, id, args); }; Context.prototype.formatEntity = function (id, args) { return formatAsync.call(this, formatEntity, id, args); }; function legacyGet(fn, id, args) { if (!this.isReady) { throw new L10nError('Context not ready'); } var entry; try { entry = getWithFallback.call(this, id); } catch (err) { if (err.loc) { throw err; } reportMissing.call(this, id, err); return ''; } return fn.call(this, args, entry); } Context.prototype.get = function (id, args) { return legacyGet.call(this, formatValue, id, args); }; Context.prototype.getEntity = function (id, args) { return legacyGet.call(this, formatEntity, id, args); }; Context.prototype.getLocale = function getLocale(code) { var locales = this.locales; if (locales[code]) { return locales[code]; } return locales[code] = new Locale(code, this); }; function negotiate(available, requested, defaultLocale) { var supportedLocale; for (var i = 0; i < requested.length; i++) { var locale = requested[i]; if (available.indexOf(locale) !== -1) { supportedLocale = locale; break; } } if (!supportedLocale || supportedLocale === defaultLocale) { return [defaultLocale]; } return [ supportedLocale, defaultLocale ]; } function freeze(supported) { var locale = this.getLocale(supported[0]); if (locale.isReady) { setReady.call(this, supported); } else { locale.build(setReady.bind(this, supported)); } } function setReady(supported) { this.supportedLocales = supported; this.isReady = true; this._emitter.emit('ready'); } Context.prototype.registerLocales = function (defLocale, available) { if (defLocale) { this.defaultLocale = defLocale; } this.availableLocales = [this.defaultLocale]; this.qps = Object.keys(PSEUDO); if (available) { for (var i = 0, loc; loc = available[i]; i++) { if (this.availableLocales.indexOf(loc) === -1) { this.availableLocales.push(loc); var pos = this.qps.indexOf(loc); if (pos !== -1) { this.qps.splice(pos, 1); } } } } }; Context.prototype.requestLocales = function requestLocales() { if (this.isLoading && !this.isReady) { throw new L10nError('Context not ready'); } this.isLoading = true; var requested = Array.prototype.slice.call(arguments); if (requested.length === 0) { throw new L10nError('No locales requested'); } var supported = negotiate(this.availableLocales.concat(this.qps), requested, this.defaultLocale); if (this.supportedLocales[0] !== supported[0]) { freeze.call(this, supported); } }; Context.prototype.addEventListener = function (type, listener) { this._emitter.addEventListener(type, listener); }; Context.prototype.removeEventListener = function (type, listener) { this._emitter.removeEventListener(type, listener); }; Context.prototype.ready = function (callback) { if (this.isReady) { setTimeout(callback); } this.addEventListener('ready', callback); }; Context.prototype.once = function (callback) { if (this.isReady) { setTimeout(callback); return; } var callAndRemove = function () { this.removeEventListener('ready', callAndRemove); callback(); }.bind(this); this.addEventListener('ready', callAndRemove); }; var allowed = { elements: [ 'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'data', 'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b', 'u', 'mark', 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 'wbr' ], attributes: { global: [ 'title', 'aria-label', 'aria-valuetext', 'aria-moz-hint' ], a: ['download'], area: [ 'download', 'alt' ], input: [ 'alt', 'placeholder' ], menuitem: ['label'], menu: ['label'], optgroup: ['label'], option: ['label'], track: ['label'], img: ['alt'], textarea: ['placeholder'], th: ['abbr'] } }; var rtlList = [ 'ar', 'he', 'fa', 'ps', 'ar-x-psbidi', 'ur' ]; var nodeObserver = null; var pendingElements = null; var moConfig = { attributes: true, characterData: false, childList: true, subtree: true, attributeFilter: [ 'data-l10n-id', 'data-l10n-args' ] }; navigator.mozL10n = { ctx: null, get: function get(id, ctxdata) { return navigator.mozL10n.ctx.get(id, ctxdata); }, formatValue: function (id, ctxdata) { return navigator.mozL10n.ctx.formatValue(id, ctxdata); }, formatEntity: function (id, ctxdata) { return navigator.mozL10n.ctx.formatEntity(id, ctxdata); }, translateFragment: function (fragment) { return translateFragment.call(navigator.mozL10n, fragment); }, setAttributes: setL10nAttributes, getAttributes: getL10nAttributes, ready: function ready(callback) { return navigator.mozL10n.ctx.ready(callback); }, once: function once(callback) { return navigator.mozL10n.ctx.once(callback); }, get readyState() { return navigator.mozL10n.ctx.isReady ? 'complete' : 'loading'; }, language: { set code(lang) { navigator.mozL10n.ctx.requestLocales(lang); }, get code() { return navigator.mozL10n.ctx.supportedLocales[0]; }, get direction() { return getDirection(navigator.mozL10n.ctx.supportedLocales[0]); } }, qps: PSEUDO, _config: { appVersion: null, localeSources: Object.create(null), isPretranslated: false }, _getInternalAPI: function () { return { Error: L10nError, Context: Context, Locale: Locale, Resolver: Resolver, getPluralRule: getPluralRule, rePlaceables: rePlaceables, translateDocument: translateDocument, onMetaInjected: onMetaInjected, PropertiesParser: PropertiesParser, walkContent: walkContent, buildLocaleList: buildLocaleList }; } }; function getDirection(lang) { return rtlList.indexOf(lang) >= 0 ? 'rtl' : 'ltr'; } var readyStates = { loading: 0, interactive: 1, complete: 2 }; function whenInteractive(callback) { if (readyStates[document.readyState] >= readyStates.interactive) { callback(); return; } document.addEventListener('readystatechange', function l10n_onrsc() { if (readyStates[document.readyState] >= readyStates.interactive) { document.removeEventListener('readystatechange', l10n_onrsc); callback(); } }); } function initObserver() { nodeObserver = new MutationObserver(onMutations.bind(navigator.mozL10n)); nodeObserver.observe(document, moConfig); } function init(pretranslate) { if (!pretranslate) { initObserver(); } initResources.call(navigator.mozL10n); } function initResources() { var meta = {}; var nodes = document.head.querySelectorAll('link[rel="localization"],' + 'meta[name="availableLanguages"],' + 'meta[name="defaultLanguage"],' + 'meta[name="appVersion"],' + 'script[type="application/l10n"]'); for (var i = 0, node; node = nodes[i]; i++) { var type = node.getAttribute('rel') || node.nodeName.toLowerCase(); switch (type) { case 'localization': this.ctx.resLinks.push(node.getAttribute('href')); break; case 'meta': onMetaInjected.call(this, node, meta); break; case 'script': onScriptInjected.call(this, node); break; } } var additionalLanguagesPromise; if (navigator.mozApps && navigator.mozApps.getAdditionalLanguages) { additionalLanguagesPromise = navigator.mozApps.getAdditionalLanguages().catch(function (e) { console.error('Error while loading getAdditionalLanguages', e); }); document.addEventListener('additionallanguageschange', function (evt) { registerLocales.call(this, meta, evt.detail); this.ctx.requestLocales.apply(this.ctx, navigator.languages || [navigator.language]); }.bind(this)); } else { additionalLanguagesPromise = Promise.resolve(); } additionalLanguagesPromise.then(function (extraLangs) { registerLocales.call(this, meta, extraLangs); initLocale.call(this); }.bind(this)); } function registerLocales(meta, extraLangs) { var locales = buildLocaleList.call(this, meta, extraLangs); navigator.mozL10n._config.localeSources = locales[1]; this.ctx.registerLocales(locales[0], Object.keys(locales[1])); } function getMatchingLangpack(appVersion, langpacks) { for (var i = 0, langpack; langpack = langpacks[i]; i++) { if (langpack.target === appVersion) { return langpack; } } return null; } function buildLocaleList(meta, extraLangs) { var loc, lp; var localeSources = Object.create(null); var defaultLocale = meta.defaultLanguage || this.ctx.defaultLocale; if (meta.availableLanguages) { for (loc in meta.availableLanguages) { localeSources[loc] = 'app'; } } if (extraLangs) { for (loc in extraLangs) { lp = getMatchingLangpack(this._config.appVersion, extraLangs[loc]); if (!lp) { continue; } if (!(loc in localeSources) || !meta.availableLanguages[loc] || parseInt(lp.revision) > meta.availableLanguages[loc]) { localeSources[loc] = 'extra'; } } } if (!(defaultLocale in localeSources)) { localeSources[defaultLocale] = 'app'; } return [ defaultLocale, localeSources ]; } function splitAvailableLanguagesString(str) { var langs = {}; str.split(',').forEach(function (lang) { lang = lang.trim().split(':'); langs[lang[0]] = parseInt(lang[1]); }); return langs; } function onMetaInjected(node, meta) { switch (node.getAttribute('name')) { case 'availableLanguages': meta.availableLanguages = splitAvailableLanguagesString(node.getAttribute('content')); break; case 'defaultLanguage': meta.defaultLanguage = node.getAttribute('content'); break; case 'appVersion': navigator.mozL10n._config.appVersion = node.getAttribute('content'); break; } } function onScriptInjected(node) { var lang = node.getAttribute('lang'); var locale = this.ctx.getLocale(lang); locale.addAST(JSON.parse(node.textContent)); } function initLocale() { this.ctx.requestLocales.apply(this.ctx, navigator.languages || [navigator.language]); window.addEventListener('languagechange', function l10n_langchange() { this.ctx.requestLocales.apply(this.ctx, navigator.languages || [navigator.language]); }.bind(this)); } function localizeMutations(mutations) { var mutation; var targets = new Set(); for (var i = 0; i < mutations.length; i++) { mutation = mutations[i]; if (mutation.type === 'childList') { var addedNode; for (var j = 0; j < mutation.addedNodes.length; j++) { addedNode = mutation.addedNodes[j]; if (addedNode.nodeType !== Node.ELEMENT_NODE) { continue; } targets.add(addedNode); } } if (mutation.type === 'attributes') { targets.add(mutation.target); } } targets.forEach(function (target) { if (target.childElementCount) { translateFragment.call(this, target); } else if (target.hasAttribute('data-l10n-id')) { translateElement.call(this, target); } }, this); } function onMutations(mutations, self) { self.disconnect(); localizeMutations.call(this, mutations); self.observe(document, moConfig); } function onReady() { if (!navigator.mozL10n._config.isPretranslated) { translateDocument.call(this); } navigator.mozL10n._config.isPretranslated = false; if (pendingElements) { for (var i = 0, element; element = pendingElements[i]; i++) { translateElement.call(this, element); } pendingElements = null; } if (!nodeObserver) { initObserver(); } fireLocalizedEvent.call(this); } function fireLocalizedEvent() { var event = new CustomEvent('localized', { 'bubbles': false, 'cancelable': false, 'detail': { 'language': this.ctx.supportedLocales[0] } }); window.dispatchEvent(event); } var reOverlay = /<|&#?\w+;/; var reHtml = /[&<>]/g; var htmlEntities = { '&': '&', '<': '<', '>': '>' }; function translateDocument() { document.documentElement.lang = this.language.code; document.documentElement.dir = this.language.direction; translateFragment.call(this, document.documentElement); } function translateFragment(element) { if (typeof element.hasAttribute === 'function' && element.hasAttribute('data-l10n-id')) { translateElement.call(this, element); } var nodes = getTranslatableChildren(element); for (var i = 0; i < nodes.length; i++) { translateElement.call(this, nodes[i]); } } function setL10nAttributes(element, id, args) { element.setAttribute('data-l10n-id', id); if (args) { element.setAttribute('data-l10n-args', JSON.stringify(args)); } } function getL10nAttributes(element) { return { id: element.getAttribute('data-l10n-id'), args: JSON.parse(element.getAttribute('data-l10n-args')) }; } function getTranslatableChildren(element) { return element ? element.querySelectorAll('*[data-l10n-id]') : []; } function camelCaseToDashed(string) { if (string === 'ariaValueText') { return 'aria-valuetext'; } return string.replace(/[A-Z]/g, function (match) { return '-' + match.toLowerCase(); }).replace(/^-/, ''); } function escapeL10nArgs(match) { return htmlEntities[match]; } function translateElement(element) { if (!this.ctx.isReady) { if (!pendingElements) { pendingElements = []; } pendingElements.push(element); return; } var l10nId = element.getAttribute('data-l10n-id'); if (!l10nId) { return false; } var l10nArgs = element.getAttribute('data-l10n-args'); var entity = this.ctx.getEntity(l10nId, l10nArgs ? JSON.parse(l10nArgs.replace(reHtml, escapeL10nArgs)) : undefined); var value = entity.value; if (typeof value === 'string') { if (!reOverlay.test(value)) { element.textContent = value; } else { var translation = element.ownerDocument.createElement('template'); translation.innerHTML = value; overlayElement(element, translation.content); } } for (var key in entity.attrs) { var attrName = camelCaseToDashed(key); if (isAttrAllowed({ name: attrName }, element)) { element.setAttribute(attrName, entity.attrs[key]); } } } function overlayElement(sourceElement, translationElement) { var result = translationElement.ownerDocument.createDocumentFragment(); var k, attr; var childElement; while (childElement = translationElement.childNodes[0]) { translationElement.removeChild(childElement); if (childElement.nodeType === Node.TEXT_NODE) { result.appendChild(childElement); continue; } var index = getIndexOfType(childElement); var sourceChild = getNthElementOfType(sourceElement, childElement, index); if (sourceChild) { overlayElement(sourceChild, childElement); result.appendChild(sourceChild); continue; } if (isElementAllowed(childElement)) { var sanitizedChild = childElement.ownerDocument.createElement(childElement.nodeName); overlayElement(sanitizedChild, childElement); result.appendChild(sanitizedChild); continue; } result.appendChild(document.createTextNode(childElement.textContent)); } sourceElement.textContent = ''; sourceElement.appendChild(result); if (translationElement.attributes) { for (k = 0, attr; attr = translationElement.attributes[k]; k++) { if (isAttrAllowed(attr, sourceElement)) { sourceElement.setAttribute(attr.name, attr.value); } } } } function isElementAllowed(element) { return allowed.elements.indexOf(element.tagName.toLowerCase()) !== -1; } function isAttrAllowed(attr, element) { var attrName = attr.name.toLowerCase(); var tagName = element.tagName.toLowerCase(); if (allowed.attributes.global.indexOf(attrName) !== -1) { return true; } if (!allowed.attributes[tagName]) { return false; } if (allowed.attributes[tagName].indexOf(attrName) !== -1) { return true; } if (tagName === 'input' && attrName === 'value') { var type = element.type.toLowerCase(); if (type === 'submit' || type === 'button' || type === 'reset') { return true; } } return false; } function getNthElementOfType(context, element, index) { var nthOfType = 0; for (var i = 0, child; child = context.children[i]; i++) { if (child.nodeType === Node.ELEMENT_NODE && child.tagName === element.tagName) { if (nthOfType === index) { return child; } nthOfType++; } } return null; } function getIndexOfType(element) { var index = 0; var child; while (child = element.previousElementSibling) { if (child.tagName === element.tagName) { index++; } } return index; } var DEBUG = false; navigator.mozL10n.ctx = new Context(window.document ? document.URL : null); navigator.mozL10n.ctx.ready(onReady.bind(navigator.mozL10n)); navigator.mozL10n.ctx.addEventListener('notfounderror', function reportMissingEntity(e) { if (DEBUG || e.loc === 'en-US') { console.warn(e.toString()); } }); if (DEBUG) { navigator.mozL10n.ctx.addEventListener('fetcherror', console.error.bind(console)); navigator.mozL10n.ctx.addEventListener('parseerror', console.error.bind(console)); navigator.mozL10n.ctx.addEventListener('resolveerror', console.error.bind(console)); } if (window.document) { navigator.mozL10n._config.isPretranslated = document.documentElement.lang === navigator.language; var forcePretranslate = !navigator.mozL10n._config.isPretranslated; whenInteractive(init.bind(navigator.mozL10n, forcePretranslate)); } document.l10n = { setAttributes: navigator.mozL10n.setAttributes, getAttributes: navigator.mozL10n.getAttributes, formatValue: function (id, args) { return navigator.mozL10n.formatValue(id, args); }, translateFragment: function (frag) { return Promise.resolve(navigator.mozL10n.translateFragment(frag)); }, ready: new Promise(function (resolve) { navigator.mozL10n.once(resolve); }), formatValues: function () { var keys = arguments; var resp = keys.map(function (key) { if (Array.isArray(key)) { return navigator.mozL10n.formatValue(key[0], key[1]); } return navigator.mozL10n.formatValue(key); }); return Promise.all(resp); }, requestLanguages: function (langs) { navigator.mozL10n.ctx.requestLocales.apply(navigator.mozL10n.ctx, langs); }, pseudo: { 'fr-x-psaccent': { getName: function () { return Promise.resolve(navigator.mozL10n.qps['fr-x-psaccent'].name); }, processString: function (s) { return Promise.resolve(navigator.mozL10n.qps['fr-x-psaccent'].translate(s)); } }, 'ar-x-psbidi': { getName: function () { return Promise.resolve(navigator.mozL10n.qps['ar-x-psbidi'].name); }, processString: function (s) { return Promise.resolve(navigator.mozL10n.qps['ar-x-psbidi'].translate(s)); } } } }; navigator.mozL10n.ready(function () { document.documentElement.setAttribute('langs', navigator.mozL10n.ctx.supportedLocales.join(' ')); }); navigator.mozL10n.once(function () { window.addEventListener('localized', function () { document.dispatchEvent(new CustomEvent('DOMRetranslated', { bubbles: false, cancelable: false })); }); }); }(this)); define('l10nbase', function () { }); (function (global) { 'use strict'; global.mozIntl = { formatList: function (list) { return navigator.mozL10n.formatValue('listSeparator_middle').then(sep => list.join(sep)); }, DateTimeFormat: function (locales, options) { const resolvedOptions = Object.assign({}, options); if (resolvedOptions.dayperiod) { if (resolvedOptions.hour === undefined) { resolvedOptions.hour = 'numeric'; } } if (resolvedOptions.hour !== undefined && resolvedOptions.hour12 === undefined) { resolvedOptions.hour12 = navigator.mozHour12; } if (resolvedOptions.dayperiod === undefined && resolvedOptions.hour12 === true) { resolvedOptions.dayperiod = true; } var intlFormat = new Intl.DateTimeFormat(locales, resolvedOptions); resolvedOptions.locale = intlFormat.resolvedOptions().locale; resolvedOptions.hour12 = intlFormat.resolvedOptions().hour12; var hourFormatter; if (resolvedOptions.dayperiod !== undefined && resolvedOptions.hour12 === true) { hourFormatter = Intl.DateTimeFormat(locales, { hour: 'numeric', hour12: false }); } return { resolvedOptions() { return resolvedOptions; }, format: function (date, tokenFormats) { var dayPeriod; var string = intlFormat.format(date); if (resolvedOptions.dayperiod === false && resolvedOptions.hour12 === true) { dayPeriod = getDayPeriodTokenForDate(date, hourFormatter); string = string.replace(dayPeriod, '').trim(); } else if (resolvedOptions.dayperiod === true && options.hour === undefined) { dayPeriod = getDayPeriodTokenForDate(date, hourFormatter); const hour = date.toLocaleString(navigator.languages, { hour12: true, hour: 'numeric' }).replace(dayPeriod, '').trim(); string = string.replace(hour, '').trim(); } for (var token in tokenFormats) { if (token === 'dayperiod' && resolvedOptions.hour12 === false) { continue; } const localOptions = { [token]: resolvedOptions[token] }; var formatter = global.mozIntl.DateTimeFormat(navigator.languages, localOptions); var tokenString = formatter.format(date); string = string.replace(tokenString, tokenFormats[token]); } return string; } }; }, calendarInfo: function (token) { switch (token) { case 'firstDayOfTheWeek': return navigator.mozL10n.formatValue('firstDayOfTheWeek').then(firstDayOfTheWeek => parseInt(firstDayOfTheWeek) % 7); default: throw new Error('Unknown token: ' + token); } }, DurationFormat: function (locales = navigator.languages, options = {}) { const resolvedOptions = Object.assign({ locale: locales[0], maxUnit: 'hour', minUnit: 'second' }, options); const numFormatter = Intl.NumberFormat(locales, { style: 'decimal', useGrouping: false, minimumIntegerDigits: 2 }); const maxUnitIdx = getDurationUnitIdx(resolvedOptions.maxUnit, 0); const minUnitIdx = getDurationUnitIdx(resolvedOptions.minUnit, durationFormatOrder.length - 1); return navigator.mozL10n.formatValue('durationPattern').then(fmt => ({ resolvedOptions: function () { return resolvedOptions; }, format: function (input) { const minValue = durationFormatElements[resolvedOptions.minUnit].value; input = Math.round(input / minValue) * minValue; const duration = splitIntoTimeUnits(input, maxUnitIdx, minUnitIdx); var string = trimDurationPattern(fmt, resolvedOptions.maxUnit, resolvedOptions.minUnit); for (var unit in duration) { const token = durationFormatElements[unit].token; string = string.replace(token, numFormatter.format(duration[unit])); } if (input < 0) { return '-' + string; } return string; } })); }, RelativeTimeFormat: function (locales, options) { return { resolvedOptions: function () { return options; }, format: function (x) { const {unit, value} = relativeTimeFormatId(x, options); return navigator.mozL10n.formatValue(unit, { value }); } }; }, UnitFormat: function (locales, options) { const unitGroup = getUnitFormatGroupName(options.unit); if (unitGroup === undefined) { throw new RangeError(`invalid value ${ options.unit } for option unit`); } if (!unitFormatGroups[unitGroup].styles.includes(options.style)) { throw new RangeError(`invalid value ${ options.style } for option style`); } const unit = `${ unitGroup }-${ options.unit }-${ options.style }`; return { format: function (x) { return document.l10n.formatValue(unit, { value: x }); } }; }, _gaia: { relativePart: function (milliseconds) { const units = computeTimeUnits(milliseconds); const unit = getBestMatchUnit(units); return { unit: unit + 's', value: Math.abs(units[unit]) }; }, RelativeDate: function (locales, options) { const style = options && options.style || 'long'; const maxFormatter = Intl.DateTimeFormat(locales, { year: 'numeric', month: 'numeric', day: 'numeric' }); const relativeFmtOptions = { unit: 'bestFit', style: style, minUnit: 'minute' }; return { format: function (time, maxDiff) { maxDiff = maxDiff || 86400 * 10; const secDiff = (Date.now() - time) / 1000; if (isNaN(secDiff)) { return navigator.mozL10n.formatValue('incorrectDate'); } if (secDiff > maxDiff) { return Promise.resolve(maxFormatter.format(time)); } const {unit, value} = relativeTimeFormatId(time, relativeFmtOptions); return navigator.mozL10n.formatValue(unit, { value }); }, formatElement: function (element, time, maxDiff) { maxDiff = maxDiff || 86400 * 10; const secDiff = (Date.now() - time) / 1000; if (isNaN(secDiff)) { element.setAttribute('data-l10n-id', 'incorrectDate'); } element.removeAttribute('data-l10n-id'); if (secDiff > maxDiff) { element.textContent = maxFormatter.format(time); } const {unit, value} = relativeTimeFormatId(time, relativeFmtOptions); navigator.mozL10n.setAttributes(element, unit, { value }); } }; }, getFormattedUnit: function (type, style, v) { if (!unitFormatData.hasOwnProperty(type)) { throw new RangeError(`invalid type ${ type }`); } if (!unitFormatGroups[type].styles.includes(style)) { throw new RangeError(`invalid style ${ style } for type ${ type }`); } var units = unitFormatData[type]; var scale = 0; for (let i = 1; i < units.length; i++) { if (v < units[i].value * unitFormatGroups[type].rounding) { scale = i - 1; break; } else if (i === units.length - 1) { scale = i; } } var value = Math.round(v / units[scale].value * 100) / 100; return global.mozIntl.UnitFormat(navigator.languages, { unit: units[scale].name, style: style }).format(value); } } }; const durationFormatOrder = [ 'hour', 'minute', 'second', 'millisecond' ]; const durationFormatElements = { 'hour': { value: 3600000, token: 'hh' }, 'minute': { value: 60000, token: 'mm' }, 'second': { value: 1000, token: 'ss' }, 'millisecond': { value: 10, token: 'SS' } }; const unitFormatData = { 'duration': [ { 'name': 'second', 'value': 1 }, { 'name': 'minute', 'value': 60 }, { 'name': 'hour', 'value': 60 * 60 }, { 'name': 'day', 'value': 24 * 60 * 60 }, { 'name': 'month', 'value': 30 * 24 * 60 * 60 } ], 'digital': [ { 'name': 'byte', 'value': 1 }, { 'name': 'kilobyte', 'value': 1024 }, { 'name': 'megabyte', 'value': 1024 * 1024 }, { 'name': 'gigabyte', 'value': 1024 * 1024 * 1024 }, { 'name': 'terabyte', 'value': 1024 * 1024 * 1024 * 1024 } ] }; const unitFormatGroups = { 'duration': { 'units': [ 'second', 'minute', 'hour', 'day', 'month' ], 'styles': ['narrow'], 'rounding': 1 }, 'digital': { 'units': [ 'byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte' ], 'styles': ['short'], 'rounding': 0.8 } }; function getDurationUnitIdx(name, defaultValue) { if (!name) { return defaultValue; } const pos = durationFormatOrder.indexOf(name); if (pos === -1) { throw new Error('Unknown unit type: ' + name); } return pos; } function splitIntoTimeUnits(v, maxUnitIdx, minUnitIdx) { const units = {}; var input = Math.abs(v); for (var i = maxUnitIdx; i <= minUnitIdx; i++) { const key = durationFormatOrder[i]; const {value} = durationFormatElements[key]; units[key] = i == minUnitIdx ? Math.round(input / value) : Math.floor(input / value); input -= units[key] * value; } return units; } function trimDurationPattern(string, maxUnit, minUnit) { const maxToken = durationFormatElements[maxUnit].token; const minToken = durationFormatElements[minUnit].token; string = string.substring(string.indexOf(maxToken), string.indexOf(minToken) + minToken.length); return string; } function getDayPeriodTokenForDate(date, hourFormatter) { const hourToken = hourFormatter.format(date); const newDate = new Date(date); newDate.setHours(parseInt(hourToken)); return new Intl.DateTimeFormat(navigator.language, {hour:'numeric', hour12:true}).formatToParts(newDate).find(part => part.type === 'dayPeriod').value; } function computeTimeUnits(v) { const units = {}; const millisecond = Math.round(v); const second = Math.round(millisecond / 1000); const minute = Math.round(second / 60); const hour = Math.round(minute / 60); const day = Math.round(hour / 24); const rawYear = day * 400 / 146097; units.millisecond = millisecond; units.second = second; units.minute = minute; units.hour = hour; units.day = day; units.week = Math.round(day / 7); units.month = Math.round(rawYear * 12); units.quarter = Math.round(rawYear * 4); units.year = Math.round(rawYear); return units; } function getBestMatchUnit(units) { if (Math.abs(units.minute) < 45) { return 'minute'; } if (Math.abs(units.hour) < 22) { return 'hour'; } if (Math.abs(units.day) < 7) { return 'day'; } if (Math.abs(units.week) < 4) { return 'week'; } if (Math.abs(units.month) < 11) { return 'month'; } return 'year'; } function relativeTimeFormatId(x, options) { const ms = x - Date.now(); const units = computeTimeUnits(ms); const unit = options.unit === 'bestFit' ? getBestMatchUnit(units) : options.unit; const v = units[unit]; const tl = v < 0 ? '-ago' : '-until'; const style = options.style || 'long'; const entry = unit + 's' + tl + '-' + style; return { unit: entry, value: Math.abs(v) }; } function getUnitFormatGroupName(unitName) { for (let groupName in unitFormatGroups) { if (unitFormatGroups[groupName].units.includes(unitName)) { return groupName; } } return undefined; } }(this)); define('moz_intl', ['l10nbase'], function (global) { return function () { var ret, fn; return ret || global.mozIntl; }; }(this)); define('text', { load: function (name, req, onload, config) { var url = req.toUrl(name), xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onreadystatechange = function (evt) { var status, err; if (xhr.readyState === 4) { status = xhr.status; if (status > 399 && status < 600) { err = new Error(url + ' HTTP status: ' + status); err.xhr = xhr; onload.error(err); } else { onload(xhr.responseText); } } }; xhr.responseType = 'text'; xhr.send(null); } }); define('folder_depth_classes', [], function () { return [ 'fld-folder-depth0', 'fld-folder-depth1', 'fld-folder-depth2', 'fld-folder-depth3', 'fld-folder-depth4', 'fld-folder-depth5', 'fld-folder-depthmax' ]; }); define('tmpl!cards/value_selector.html', ['tmpl'], function (tmpl) { return tmpl.toDom(''); }); define('tmpl!cards/vsl/item.html', ['tmpl'], function (tmpl) { return tmpl.toDom('
  • '); }); define('value_selector', [ 'require', 'cards', 'folder_depth_classes', 'tmpl!cards/value_selector.html', 'tmpl!cards/vsl/item.html' ], function (require) { 'use strict'; var cards = require('cards'), FOLDER_DEPTH_CLASSES = require('folder_depth_classes'), formNode = require('tmpl!cards/value_selector.html'), itemTemplateNode = require('tmpl!cards/vsl/item.html'); function noop() { } function ValueSelector(title, list) { var init, show, hide, render, setTitle, emptyList, addToList, data; init = function () { data = { title: 'No Title', list: [{ label: 'Dummy element', callback: function () { alert('Define an action here!'); } }] }; document.body.appendChild(formNode); var btnCancel = formNode.querySelector('button'); btnCancel.addEventListener('click', function (event) { event.stopPropagation(); event.preventDefault(); hide(); }); emptyList(); if (typeof title === 'string') { setTitle(title); } if (Array.isArray(list)) { data.list = list; } }; show = function () { render(); cards.setStatusColor(formNode); formNode.classList.remove('collapsed'); }; hide = function () { cards.setStatusColor(); formNode.classList.add('collapsed'); emptyList(); }; render = function () { var title = formNode.querySelector('h1'), list = formNode.querySelector('ol'); title.textContent = data.title; list.innerHTML = ''; data.list.forEach(function (listItem) { var node = itemTemplateNode.cloneNode(true); node.querySelector('span').textContent = listItem.label; var depthIdx = listItem.depth; depthIdx = Math.min(FOLDER_DEPTH_CLASSES.length - 1, depthIdx); node.classList.add(FOLDER_DEPTH_CLASSES[depthIdx]); var callback = listItem.selectable ? listItem.callback : noop; node.addEventListener('click', callback, false); list.appendChild(node); }); }; setTitle = function (str) { data.title = str; }; emptyList = function () { data.list = []; }; addToList = function (label, depth, selectable, callback) { data.list.push({ label: label, depth: depth, selectable: selectable, callback: callback }); }; init(); return { init: init, show: show, hide: hide, setTitle: setTitle, addToList: addToList, List: list }; } return ValueSelector; }); var GestureDetector = function () { function GD(e, options) { this.element = e; this.options = options || {}; this.options.panThreshold = this.options.panThreshold || GD.PAN_THRESHOLD; this.state = initialState; this.timers = {}; } GD.prototype.startDetecting = function () { var self = this; eventtypes.forEach(function (t) { self.element.addEventListener(t, self); }); }; GD.prototype.stopDetecting = function () { var self = this; eventtypes.forEach(function (t) { self.element.removeEventListener(t, self); }); }; GD.prototype.handleEvent = function (e) { var handler = this.state[e.type]; if (!handler) { return; } if (e.changedTouches) { for (var i = 0; i < e.changedTouches.length; i++) { handler(this, e, e.changedTouches[i]); handler = this.state[e.type]; if (!handler) { return; } } } else { handler(this, e); } }; GD.prototype.startTimer = function (type, time) { this.clearTimer(type); var self = this; this.timers[type] = setTimeout(function () { self.timers[type] = null; var handler = self.state[type]; if (handler) { handler(self, type); } }, time); }; GD.prototype.clearTimer = function (type) { if (this.timers[type]) { clearTimeout(this.timers[type]); this.timers[type] = null; } }; GD.prototype.switchTo = function (state, event, touch) { this.state = state; if (state.init) { state.init(this, event, touch); } }; GD.prototype.emitEvent = function (type, detail) { if (!this.target) { console.error('Attempt to emit event with no target'); return; } var event = this.element.ownerDocument.createEvent('CustomEvent'); event.initCustomEvent(type, true, true, detail); this.target.dispatchEvent(event); }; GD.HOLD_INTERVAL = 1000; GD.PAN_THRESHOLD = 20; GD.DOUBLE_TAP_DISTANCE = 50; GD.DOUBLE_TAP_TIME = 500; GD.VELOCITY_SMOOTHING = 0.5; GD.SCALE_THRESHOLD = 20; GD.ROTATE_THRESHOLD = 22.5; GD.THRESHOLD_SMOOTHING = 0.9; var abs = Math.abs, floor = Math.floor, sqrt = Math.sqrt, atan2 = Math.atan2; var PI = Math.PI; var eventtypes = [ 'touchstart', 'touchmove', 'touchend' ]; function eventTime(e) { var ts = e.timeStamp; if (ts > 2 * Date.now()) { return Math.floor(ts / 1000); } else { return ts; } } function coordinates(e, t) { return Object.freeze({ screenX: t.screenX, screenY: t.screenY, clientX: t.clientX, clientY: t.clientY, timeStamp: eventTime(e) }); } function midpoints(e, t1, t2) { return Object.freeze({ screenX: floor((t1.screenX + t2.screenX) / 2), screenY: floor((t1.screenY + t2.screenY) / 2), clientX: floor((t1.clientX + t2.clientX) / 2), clientY: floor((t1.clientY + t2.clientY) / 2), timeStamp: eventTime(e) }); } function between(c1, c2) { var r = GD.THRESHOLD_SMOOTHING; return Object.freeze({ screenX: floor(c1.screenX + r * (c2.screenX - c1.screenX)), screenY: floor(c1.screenY + r * (c2.screenY - c1.screenY)), clientX: floor(c1.clientX + r * (c2.clientX - c1.clientX)), clientY: floor(c1.clientY + r * (c2.clientY - c1.clientY)), timeStamp: floor(c1.timeStamp + r * (c2.timeStamp - c1.timeStamp)) }); } function touchDistance(t1, t2) { var dx = t2.screenX - t1.screenX; var dy = t2.screenY - t1.screenY; return sqrt(dx * dx + dy * dy); } function touchDirection(t1, t2) { return atan2(t2.screenY - t1.screenY, t2.screenX - t1.screenX) * 180 / PI; } function touchRotation(d1, d2) { var angle = d2 - d1; if (angle > 180) { angle -= 360; } else if (angle <= -180) { angle += 360; } return angle; } function isDoubleTap(lastTap, thisTap) { var dx = abs(thisTap.screenX - lastTap.screenX); var dy = abs(thisTap.screenY - lastTap.screenY); var dt = thisTap.timeStamp - lastTap.timeStamp; return dx < GD.DOUBLE_TAP_DISTANCE && dy < GD.DOUBLE_TAP_DISTANCE && dt < GD.DOUBLE_TAP_TIME; } var initialState = { name: 'initialState', init: function (d, e, t) { d.target = null; d.start = d.last = null; d.touch1 = d.touch2 = null; d.vx = d.vy = null; d.startDistance = d.lastDistance = null; d.startDirection = d.lastDirection = null; d.lastMidpoint = null; d.scaled = d.rotated = null; if (e && t && e.type === 'touchstart') { initialState.touchstart(d, e, t); } }, touchstart: function (d, e, t) { d.switchTo(touchStartedState, e, t); } }; var touchStartedState = { name: 'touchStartedState', init: function (d, e, t) { d.target = e.target; d.touch1 = t.identifier; d.start = d.last = coordinates(e, t); if (d.options.holdEvents) { d.startTimer('holdtimeout', GD.HOLD_INTERVAL); } }, touchstart: function (d, e, t) { d.clearTimer('holdtimeout'); if (e.touches.length > 1) { d.switchTo(transformState, e, t); } else { console.warn('Ignoring missing touchend event. See bug 1162771.'); d.switchTo(initialState, e, t); } }, touchmove: function (d, e, t) { if (t.identifier !== d.touch1) { return; } if (abs(t.screenX - d.start.screenX) > d.options.panThreshold || abs(t.screenY - d.start.screenY) > d.options.panThreshold) { d.clearTimer('holdtimeout'); d.switchTo(panStartedState, e, t); } }, touchend: function (d, e, t) { if (t.identifier !== d.touch1) { return; } if (d.lastTap && isDoubleTap(d.lastTap, d.start)) { d.emitEvent('tap', d.start); d.emitEvent('dbltap', d.start); d.lastTap = null; } else { d.emitEvent('tap', d.start); d.lastTap = coordinates(e, t); } d.clearTimer('holdtimeout'); d.switchTo(initialState); }, holdtimeout: function (d) { d.switchTo(holdState); } }; var panStartedState = { name: 'panStartedState', init: function (d, e, t) { d.start = d.last = between(d.start, coordinates(e, t)); if (e.type === 'touchmove') { panStartedState.touchmove(d, e, t); } }, touchmove: function (d, e, t) { if (t.identifier !== d.touch1) { return; } var current = coordinates(e, t); d.emitEvent('pan', { absolute: { dx: current.screenX - d.start.screenX, dy: current.screenY - d.start.screenY }, relative: { dx: current.screenX - d.last.screenX, dy: current.screenY - d.last.screenY }, position: current }); var dt = current.timeStamp - d.last.timeStamp; var vx = (current.screenX - d.last.screenX) / dt; var vy = (current.screenY - d.last.screenY) / dt; if (d.vx == null) { d.vx = vx; d.vy = vy; } else { d.vx = d.vx * GD.VELOCITY_SMOOTHING + vx * (1 - GD.VELOCITY_SMOOTHING); d.vy = d.vy * GD.VELOCITY_SMOOTHING + vy * (1 - GD.VELOCITY_SMOOTHING); } d.last = current; }, touchend: function (d, e, t) { if (t.identifier !== d.touch1) { return; } var current = coordinates(e, t); var dx = current.screenX - d.start.screenX; var dy = current.screenY - d.start.screenY; var angle = atan2(dy, dx) * 180 / PI; if (angle < 0) { angle += 360; } var direction; if (angle >= 315 || angle < 45) { direction = 'right'; } else if (angle >= 45 && angle < 135) { direction = 'down'; } else if (angle >= 135 && angle < 225) { direction = 'left'; } else if (angle >= 225 && angle < 315) { direction = 'up'; } d.emitEvent('swipe', { start: d.start, end: current, dx: dx, dy: dy, dt: e.timeStamp - d.start.timeStamp, vx: d.vx, vy: d.vy, direction: direction, angle: angle }); d.switchTo(initialState); } }; var holdState = { name: 'holdState', init: function (d) { d.emitEvent('holdstart', d.start); }, touchmove: function (d, e, t) { var current = coordinates(e, t); d.emitEvent('holdmove', { absolute: { dx: current.screenX - d.start.screenX, dy: current.screenY - d.start.screenY }, relative: { dx: current.screenX - d.last.screenX, dy: current.screenY - d.last.screenY }, position: current }); d.last = current; }, touchend: function (d, e, t) { var current = coordinates(e, t); d.emitEvent('holdend', { start: d.start, end: current, dx: current.screenX - d.start.screenX, dy: current.screenY - d.start.screenY }); d.switchTo(initialState); } }; var transformState = { name: 'transformState', init: function (d, e, t) { d.touch2 = t.identifier; var t1 = e.touches.identifiedTouch(d.touch1); var t2 = e.touches.identifiedTouch(d.touch2); d.startDistance = d.lastDistance = touchDistance(t1, t2); d.startDirection = d.lastDirection = touchDirection(t1, t2); d.scaled = d.rotated = false; }, touchmove: function (d, e, t) { if (t.identifier !== d.touch1 && t.identifier !== d.touch2) { return; } var t1 = e.touches.identifiedTouch(d.touch1); var t2 = e.touches.identifiedTouch(d.touch2); var midpoint = midpoints(e, t1, t2); var distance = touchDistance(t1, t2); var direction = touchDirection(t1, t2); var rotation = touchRotation(d.startDirection, direction); if (!d.scaled) { if (abs(distance - d.startDistance) > GD.SCALE_THRESHOLD) { d.scaled = true; d.startDistance = d.lastDistance = floor(d.startDistance + GD.THRESHOLD_SMOOTHING * (distance - d.startDistance)); } else { distance = d.startDistance; } } if (!d.rotated) { if (abs(rotation) > GD.ROTATE_THRESHOLD) { d.rotated = true; } else { direction = d.startDirection; } } if (d.scaled || d.rotated) { d.emitEvent('transform', { absolute: { scale: distance / d.startDistance, rotate: touchRotation(d.startDirection, direction) }, relative: { scale: distance / d.lastDistance, rotate: touchRotation(d.lastDirection, direction) }, midpoint: midpoint }); d.lastDistance = distance; d.lastDirection = direction; d.lastMidpoint = midpoint; } }, touchend: function (d, e, t) { if (t.identifier === d.touch2) { d.touch2 = null; } else if (t.identifier === d.touch1) { d.touch1 = d.touch2; d.touch2 = null; } else { return; } if (d.scaled || d.rotated) { d.emitEvent('transformend', { absolute: { scale: d.lastDistance / d.startDistance, rotate: touchRotation(d.startDirection, d.lastDirection) }, relative: { scale: 1, rotate: 0 }, midpoint: d.lastMidpoint }); } d.switchTo(afterTransformState); } }; var afterTransformState = { name: 'afterTransformState', touchstart: function (d, e, t) { d.switchTo(transformState, e, t); }, touchend: function (d, e, t) { if (t.identifier === d.touch1) { d.switchTo(initialState); } } }; return GD; }(); define('shared/js/gesture_detector', function (global) { return function () { var ret, fn; return ret || global.GestureDetector; }; }(this)); define('iframe_shims', ['shared/js/gesture_detector'], function (GestureDetector) { var DEFAULT_STYLE_TAG = ''; var iframeShimsOpts = { zoomDelayMS: 200, initialScale: null, resizeLimit: 4, initialResizePollIntervalMS: 200, noResizePollIntervalMS: 250, didResizePollIntervalMS: 300, pictureDelayPollIntervalMS: 200 }; function createAndInsertIframeForContent(htmlStr, scrollContainer, parentNode, beforeNode, interactiveMode, clickHandler) { var scrollPad = 0; var viewportWidth = parentNode.offsetWidth - scrollPad; var viewport = document.createElement('div'); viewport.setAttribute('style', 'padding: 0; border-width: 0; margin: 0; ' + 'overflow: hidden;'); viewport.style.width = viewportWidth + 'px'; var iframe = document.createElement('iframe'); iframe.setAttribute('sandbox', 'allow-same-origin'); iframe.setAttribute('style', 'padding: 0; border-width: 0; margin: 0; ' + 'overflow: hidden; ' + 'transform-origin: top ' + (document.documentElement.dir === 'rtl' ? 'right' : 'left') + '; ' + 'pointer-events: none;'); if (iframeShimsOpts.tapTransform) { iframe.style.transform = 'scale(1)'; } iframe.style.width = viewportWidth + 'px'; viewport.appendChild(iframe); parentNode.insertBefore(viewport, beforeNode); iframe.contentDocument.open(); iframe.contentDocument.write(''); iframe.contentDocument.write(DEFAULT_STYLE_TAG); iframe.contentDocument.write(''); iframe.contentDocument.write(htmlStr); iframe.contentDocument.write(''); iframe.contentDocument.close(); var iframeBody = iframe.contentDocument.body; var scrollWidth = iframeBody.scrollWidth; var scrollHeight = iframeBody.scrollHeight; var baseScale = Math.min(1, viewportWidth / scrollWidth), lastRequestedScale = iframeShimsOpts.initialScale || baseScale, scale = lastRequestedScale, lastDoubleTapScale = scale, scaleMode = 0; viewport.style.width = Math.ceil(scrollWidth * scale) + 'px'; viewport.style.height = Math.ceil(scrollHeight * scale) + 'px'; iframe.style.width = scrollWidth + 'px'; var resizeFrame = function (why) { if (why === 'initial' || why === 'poll') { scrollWidth = iframeBody.scrollWidth; scrollHeight = iframeBody.scrollHeight; var oldBaseScale = baseScale; baseScale = Math.min(1, viewportWidth / scrollWidth); if (scale === oldBaseScale) { scale = baseScale; } iframe.style.width = scrollWidth + 'px'; console.log('iframe_shims: recalculating height / width because', why, 'sw', scrollWidth, 'sh', scrollHeight, 'bs', baseScale); } console.log('iframe_shims: scale:', scale); iframe.style.transform = 'scale(' + scale + ')'; iframe.style.height = scrollHeight * Math.max(1, scale) + scrollPad + 'px'; viewport.style.width = Math.ceil(scrollWidth * scale) + 'px'; viewport.style.height = Math.ceil(scrollHeight * scale) + scrollPad + 'px'; }; resizeFrame('initial'); var activeZoom = false, lastCenterX, lastCenterY; var zoomFrame = function (newScale, centerX, centerY) { if (newScale === scale) { return; } lastRequestedScale = newScale; lastCenterX = centerX; lastCenterY = centerY; if (activeZoom) { return; } activeZoom = true; var iframeScrolledTop = scrollContainer.scrollTop - extraHeight, iframeScrolledLeft = scrollContainer.scrollLeft; var ix = centerX + iframeScrolledLeft, iy = centerY + iframeScrolledTop; var scaleDelta = newScale / scale; var vertScrollDelta = Math.ceil(iy * scaleDelta), horizScrollDelta = Math.ceil(ix * scaleDelta); scale = newScale; resizeFrame('zoom'); scrollContainer.scrollTop = vertScrollDelta + extraHeight - centerY; scrollContainer.scrollLeft = horizScrollDelta - centerX; window.setTimeout(clearActiveZoom, iframeShimsOpts.zoomDelayMS); }; var clearActiveZoom = function () { activeZoom = false; if (scale !== lastRequestedScale) { window.requestAnimationFrame(function () { console.log('delayed zoomFrame timeout, probably causing a mem-spike'); zoomFrame(lastRequestedScale, lastCenterX, lastCenterY); }); } }; var resizePollerTimeout = null; var resizePollCount = 0; var pollResize = function () { var opts = iframeShimsOpts; var desiredScrollWidth = iframeBody.scrollWidth; var desiredScrollHeight = iframeBody.scrollHeight; var resized = false; if (desiredScrollWidth > scrollWidth || desiredScrollHeight > scrollHeight) { resizeFrame('poll'); resized = true; } if (++resizePollCount < opts.resizeLimit) { resizePollerTimeout = window.setTimeout(pollResize, resized ? opts.didResizePollIntervalMS : opts.noResizePollIntervalMS); } else { resizePollerTimeout = null; } }; resizePollerTimeout = window.setTimeout(pollResize, iframeShimsOpts.initialResizePollIntervalMS); var iframeShims = { iframe: iframe, resizeHandler: function () { resizePollCount = 0; if (resizePollerTimeout) { window.clearTimeout(resizePollerTimeout); } resizePollerTimeout = window.setTimeout(pollResize, iframeShimsOpts.pictureDelayPollIntervalMS); } }; if (interactiveMode !== 'interactive') { return iframeShims; } var detectorTarget = viewport; var detector = new GestureDetector(detectorTarget); detector.startDetecting(); if (clickHandler) { viewport.removeEventListener('click', clickHandler); bindSanitizedClickHandler(viewport, clickHandler, null, iframe); } var title = document.getElementsByClassName('msg-reader-header')[0]; var header = document.getElementsByClassName('msg-envelope-bar')[0]; var extraHeight = title.clientHeight + header.clientHeight; detectorTarget.addEventListener('dbltap', function (e) { var newScale = scale; if (lastDoubleTapScale === lastRequestedScale) { scaleMode = (scaleMode + 1) % 3; switch (scaleMode) { case 0: newScale = baseScale; break; case 1: newScale = 1; break; case 2: newScale = 2; break; } console.log('already in double-tap, deciding on new scale', newScale); } else { if (lastRequestedScale > 1) { newScale = lastDoubleTapScale; scaleMode = 0; } else { newScale = 2; scaleMode = 2; } console.log('user was not in double-tap switching to double-tap with', newScale); } lastDoubleTapScale = newScale; try { zoomFrame(newScale, e.detail.clientX, e.detail.clientY); } catch (ex) { console.error('zoom bug!', ex, '\n', ex.stack); } }); var transformDone = false; detectorTarget.addEventListener('transformend', function (e) { transformDone = false; }); detectorTarget.addEventListener('transform', function (e) { if (transformDone) { return; } var scaleFactor = e.detail.absolute.scale; var newScale = lastRequestedScale; if (scaleFactor > 1.15) { transformDone = true; if (lastRequestedScale < 1) { newScale = 1; } else if (lastRequestedScale < 1.5) { newScale = 1.5; } else if (lastRequestedScale < 2) { newScale = 2; } else { return; } } else if (scaleFactor < 0.9) { transformDone = true; if (lastRequestedScale > 1.5) { newScale = 1.5; } else if (lastRequestedScale > 1) { newScale = 1; } else if (lastRequestedScale > baseScale) { newScale = baseScale; } else { return; } } else { return; } zoomFrame(newScale, e.detail.midpoint.clientX, e.detail.midpoint.clientY); }); return iframeShims; } function bindSanitizedClickHandler(target, clickHandler, topNode, iframe) { var eventType, node; var root, title, header, attachmentsContainer, msgBodyContainer, titleHeight, headerHeight, attachmentsHeight, msgBodyMarginTop, msgBodyMarginLeft, attachmentsMarginTop, iframeDoc, inputStyle, loadBar, loadBarHeight; if (iframe) { root = document.getElementsByClassName('scrollregion-horizontal-too')[0]; title = document.getElementsByClassName('msg-reader-header')[0]; header = document.getElementsByClassName('msg-envelope-bar')[0]; attachmentsContainer = document.getElementsByClassName('msg-attachments-container')[0]; loadBar = document.getElementsByClassName('msg-reader-load-infobar')[0]; msgBodyContainer = document.getElementsByClassName('msg-body-container')[0]; inputStyle = window.getComputedStyle(msgBodyContainer); msgBodyMarginTop = parseInt(inputStyle.marginTop); msgBodyMarginLeft = parseInt(inputStyle.marginLeft); titleHeight = title.clientHeight; headerHeight = header.clientHeight; eventType = 'tap'; iframeDoc = iframe.contentDocument; } else { eventType = 'click'; } target.addEventListener(eventType, function clicked(event) { if (iframe) { loadBarHeight = loadBar.clientHeight; attachmentsHeight = attachmentsContainer.clientHeight; inputStyle = window.getComputedStyle(attachmentsContainer); attachmentsMarginTop = attachmentsHeight ? parseInt(inputStyle.marginTop) : 0; var dx, dy; var transform = iframe.style.transform || 'scale(1)'; var scale = transform.match(/(\d|\.)+/g)[0]; if (document.dir === 'rtl') { dx = event.detail.clientX - msgBodyMarginLeft + root.scrollWidth + root.scrollLeft - root.clientWidth; } else { dx = event.detail.clientX + root.scrollLeft - msgBodyMarginLeft; } dy = event.detail.clientY + root.scrollTop - titleHeight - headerHeight - loadBarHeight - attachmentsHeight - attachmentsMarginTop - msgBodyMarginTop; node = iframeDoc.elementFromPoint(dx / scale, dy / scale); } else { node = event.originalTarget; } while (node !== topNode) { if (node.nodeName === 'A') { if (node.hasAttribute('ext-href')) { clickHandler(event, node, node.getAttribute('ext-href'), node.textContent); event.preventDefault(); event.stopPropagation(); return; } } node = node.parentNode; } }); } return { createAndInsertIframeForContent: createAndInsertIframeForContent, bindSanitizedClickHandler: bindSanitizedClickHandler, iframeShimsOpts: iframeShimsOpts }; }); define('cards/editor_mixins', ['require'], function (require) { return { _bindEditor: function (textNode) { this._editorNode = textNode; textNode.addEventListener('paste', function (event) { event.preventDefault(); var text = event.clipboardData.getData('text/plain'); if (text) { document.execCommand('insertText', false, text); } }); }, populateEditor: function (value) { var lines = value.split('\n'); var frag = document.createDocumentFragment(); for (var i = 0, len = lines.length; i < len; i++) { if (i) { frag.appendChild(document.createElement('br')); } if (lines[i]) { frag.appendChild(document.createTextNode(lines[i])); } } if (!frag.childNodes.length) { frag.appendChild(document.createTextNode('')); } this._editorNode.appendChild(frag); }, fromEditor: function (value) { var content = ''; var len = this._editorNode.childNodes.length; for (var i = 0; i < len; i++) { var node = this._editorNode.childNodes[i]; if (node.nodeName === 'BR' && node.getAttribute('type') !== '_moz') { content += '\n'; } else { content += node.textContent; } } return content; } }; }); define('date', [ 'require', 'moz_intl' ], function (require) { const mozIntl = require('moz_intl'); var shortRelativeDateFmt, longRelativeDateFmt; function setRelativeDateFormatters() { shortRelativeDateFmt = mozIntl._gaia.RelativeDate(navigator.languages, { style: 'short' }); longRelativeDateFmt = mozIntl._gaia.RelativeDate(navigator.languages, { style: 'long' }); } setRelativeDateFormatters(); window.addEventListener('languagechange', setRelativeDateFormatters); var date = { relativeDateElement: function (element, time, useCompactFormat) { if (time) { var f = useCompactFormat ? shortRelativeDateFmt : longRelativeDateFmt; f.formatElement(element, time); } else { element.textContent = ''; } }, setPrettyNodeDate: function (node, timestamp) { if (timestamp) { node.dataset.time = timestamp.valueOf(); node.dataset.compactFormat = true; date.relativeDateElement(node, timestamp, true); } else { node.textContent = ''; node.removeAttribute('data-time'); } } }; return date; }); define('cards/lst/default_vscroll_data', { 'isPlaceholderData': true, 'id': 'INVALID', 'author': { 'name': '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583', 'address': '', 'contactId': null }, 'to': [{ 'name': ' ', 'address': ' ', 'contactId': null }], 'cc': null, 'bcc': null, 'date': '0', 'hasAttachments': false, 'snippet': '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583' + '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583' + '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583', 'isRead': true, 'isStarred': false, 'sendStatus': {}, 'subject': '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583' + '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583' + '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583' }); define('message_list_topbar', [ 'require', 'exports', 'module', 'l10n!', 'transition_end' ], function (require, exports, module) { var mozL10n = require('l10n!'), transitionEnd = require('transition_end'); var proto = { domNode: null, _scrollContainer: null, _vScroll: null, _delayedState: null, _newEmailCount: 0, _scrollTop: 0, _thresholdMultiplier: 2, visibleOffset: 0, createdCallback: function () { this.domNode.addEventListener('click', this._onClick.bind(this)); transitionEnd(this.domNode, this._onTransitionEnd.bind(this)); }, resetNodeForCache: function (node) { node.classList.remove('closing'); node.textContent = ''; node.dataset.state = ''; this.domNode.style.left = ''; }, bindToElements: function (scrollContainer, vScroll) { this._scrollContainer = scrollContainer; this._scrollContainer.addEventListener('scroll', this._onScroll.bind(this)); this._scrollTop = this._scrollContainer.scrollTop; this._vScroll = vScroll; }, showNewEmailCount: function (newEmailCount) { if (this._scrollTop <= this.visibleOffset) { return; } this._newEmailCount = newEmailCount; this._showState('message'); }, _getState: function () { return this.domNode.dataset.state; }, _showState: function (state) { var nodeState = this._getState(); if (nodeState === state) { return; } else if (!nodeState || !state) { this._animateState(state); } else if (nodeState !== state && (nodeState !== 'message' || state !== 'top')) { this._delayedState = state; if (!this._animating) { this._animateState(''); } } }, _animateState: function (state) { this._animating = true; if (!state && this._getState()) { this.domNode.classList.add('closing'); } else { this.domNode.classList.add('no-anim'); this.domNode.classList.toggle('horiz-message', state === 'message'); this.domNode.classList.toggle('horiz-top', state === 'top'); this.domNode.clientWidth; if (state === 'message') { mozL10n.setAttributes(this.domNode, 'new-messages', { n: this._newEmailCount }); } else if (state === 'top') { mozL10n.setAttributes(this.domNode, 'message-list-top-action'); } this.domNode.classList.remove('no-anim'); this.domNode.clientWidth; this.domNode.dataset.state = state; } }, _onTransitionEnd: function (evt) { this._animating = false; if (this.domNode.classList.contains('closing')) { this.domNode.classList.remove('closing'); this.domNode.dataset.state = ''; this.domNode.style.left = ''; } if (this._delayedState) { this._animateState(this._delayedState); this._delayedState = null; } }, _onScroll: function (evt) { if (!this._topThreshold) { var rect = this._scrollContainer.getBoundingClientRect(); this._topThreshold = rect.height * this._thresholdMultiplier; this.domNode.style.top = rect.top + 'px'; } if (this._vScroll.lastScrollTopSetTime && this._vScroll.lastScrollTopSetTime + 500 > Date.now()) { return; } var scrollTop = this._scrollContainer.scrollTop, scrollingDown = scrollTop > this._scrollTop, nodeState = this._getState(); if (scrollTop !== this._scrollTop) { if (scrollTop <= this.visibleOffset) { this._showState(''); } else if (scrollingDown) { this._showState(''); } else if (nodeState !== 'top' && scrollTop > this._topThreshold) { this._showState('top'); } } this._scrollTop = scrollTop; }, _onClick: function (evt) { if (this._vScroll) { this._vScroll.jumpToIndex(0); this._showState(''); } } }; function MessageListTopBar(domNode) { this.domNode = domNode; this.createdCallback(); } MessageListTopBar.prototype = proto; return MessageListTopBar; }); define('message_display', [ 'require', 'l10n!' ], function (require) { var mozL10n = require('l10n!'); return { subject: function (subjectNode, message) { var subject = message.subject && message.subject.trim(); if (subject) { subjectNode.textContent = subject; subjectNode.classList.remove('msg-no-subject'); subjectNode.removeAttribute('data-l10n-id'); } else { mozL10n.setAttributes(subjectNode, 'message-no-subject'); subjectNode.classList.add('msg-no-subject'); } } }; }); define('cards/lst/peep_dom', [], function () { return { update: function (peep) { peep.element.textContent = peep.name || peep.address; } }; }); define('vscroll', [ 'require', 'exports', 'module', 'evt' ], function (require, exports, module) { var evt = require('evt'), slice = Array.prototype.slice, useTransform = false; function setTop(node, value) { if (useTransform) { node.style.transform = 'translateY(' + value + 'px)'; } else { node.style.top = value + 'px'; } } function VScroll(container, scrollingContainer, template, defaultData) { evt.Emitter.call(this); this.container = container; this.scrollingContainer = scrollingContainer; this.template = template; this.defaultData = defaultData; this._inited = false; this._capturedScreenMetrics = false; this.firstRenderedIndex = 0; this._limited = false; this.nodes = []; this.nodesDataIndices = {}; this.nodesIndex = -1; this.scrollTop = 0; this.visibleOffset = 0; this.oldListSize = 0; this._lastEventTime = 0; this.onEvent = this.onEvent.bind(this); this.onChange = this.onChange.bind(this); this._scrollTimeoutPoll = this._scrollTimeoutPoll.bind(this); } VScroll.nodeClassName = 'vscroll-node'; VScroll.trimMessagesForCache = function (container, itemLimit) { var nodes = slice.call(container.querySelectorAll('.' + VScroll.nodeClassName)); nodes.forEach(function (node) { var index = parseInt(node.dataset.index, 10); delete node.dataset.index; if (index > itemLimit - 1) { container.removeChild(node); } }); }; VScroll.prototype = { eventRateLimitMillis: 0, itemsPerScreen: undefined, prerenderScreens: 3, prefetchScreens: 2, retainExtraRenderedScreens: 3, recalculatePaddingScreens: 1.5, lastScrollTopSetTime: 0, prerenderItemCount: undefined, prefetchItemCount: undefined, recalculatePaddingItemCount: undefined, itemDefaultDataClass: 'default-data', prepareData: function (highAbsoluteIndex) { }, bindData: function (model, node) { }, setData: function (list) { this.list = list; if (this._inited) { if (!this.waitingForRecalculate) { this._recalculate(0); } this.emit('dataChanged'); } else { this._init(); this.renderCurrentPosition(); } }, updateDataBind: function (index, dataList, removedCount) { if (!this._inited) { return; } if (this.oldListSize !== this.list.size() || removedCount) { if (!this.waitingForRecalculate) { this.waitingForRecalculate = true; this.once('scrollStopped', function () { this._recalculate(index); }.bind(this)); } return; } for (var i = 0; i < dataList.length; i++) { var absoluteIndex = index + i; var node = this._getNodeFromDataIndex(absoluteIndex); if (node) { this.bindData(dataList[i], node); } } }, onEvent: function () { this._lastEventTime = Date.now(); if (!this.eventRateLimitMillis) { this.onChange(); return; } if (this._limited) { return; } this._limited = true; setTimeout(this.onChange, this.eventRateLimitMillis); }, onChange: function () { this._limited = false; if (!this._inited) { return; } if (this.lastScrollTopSetTime) { if (this.lastScrollTopSetTime + 1000 < Date.now()) { this.lastScrollTopSetTime = 0; } } var startIndex, endIndex, scrollTop = this.scrollingContainer.scrollTop, scrollingDown = scrollTop >= this.scrollTop; this.scrollTop = scrollTop; var visibleRange = this.getVisibleIndexRange(); if (scrollingDown) { startIndex = visibleRange[0]; endIndex = visibleRange[1] + this.prerenderItemCount; this.prepareData(endIndex + this.prefetchItemCount); } else { startIndex = visibleRange[0] - this.prerenderItemCount; endIndex = visibleRange[1]; } this._render(startIndex, endIndex); this._startScrollStopPolling(); }, nowVisible: function () { if (!this._inited && this.list) { this._init(); this.onChange(); } }, renderCurrentPosition: function () { if (!this._inited) { return; } var scrollTop = this.scrollingContainer.scrollTop; this.scrollTop = scrollTop; var visibleRange = this.getVisibleIndexRange(); var startIndex = visibleRange[0] - this.recalculatePaddingItemCount; var endIndex = visibleRange[1] + this.recalculatePaddingItemCount; this._render(startIndex, endIndex); this.prepareData(endIndex); }, indexAtScrollPosition: function (position) { var top = position - this.visibleOffset; if (top < 0) { top = 0; } return this.itemHeight ? Math.floor(top / this.itemHeight) : 0; }, getVisibleIndexRange: function () { if (this.itemHeight === undefined) { return undefined; } var top = this.scrollTop; return [ this.indexAtScrollPosition(top), this.indexAtScrollPosition(top + this.innerHeight) ]; }, jumpToIndex: function (index) { this._setContainerScrollTop(index * this.itemHeight + this.visibleOffset); }, clearDisplay: function () { this.container.innerHTML = ''; this.container.style.height = '0px'; this.oldListSize = 0; }, destroy: function () { this.scrollingContainer.removeEventListener('scroll', this.onEvent); if (this._scrollTimeoutPoll) { clearTimeout(this._scrollTimeoutPoll); this._scrollTimeoutPoll = 0; } }, _setContainerScrollTop: function (value) { this.scrollingContainer.scrollTop = value; this.lastScrollTopSetTime = Date.now(); }, _render: function (startIndex, endIndex) { var i, listSize = this.list.size(); if (startIndex < 0) { startIndex = 0; } if (endIndex >= listSize) { endIndex = listSize - 1; } this.firstRenderedIndex = startIndex; if (!this._inited) { this._init(); } for (i = startIndex; i <= endIndex; i++) { if (this._getNodeFromDataIndex(i)) { continue; } var node = this._nextAvailableNode(startIndex, endIndex), data = this.list(i); if (!data) { data = this.defaultData; } if (node.parentNode) { node.parentNode.removeChild(node); } setTop(node, i * this.itemHeight); this._setNodeDataIndex(this.nodesIndex, i); this.bindData(data, node); this.container.appendChild(node); } }, _setNodeDataIndex: function (nodesIndex, dataIndex) { var oldDataIndex = this.nodes[nodesIndex].vScrollDataIndex; if (oldDataIndex > -1) { this.nodesDataIndices[oldDataIndex] = -1; } var node = this.nodes[nodesIndex]; node.vScrollDataIndex = dataIndex; node.dataset.index = dataIndex; this.nodesDataIndices[dataIndex] = nodesIndex; }, _getNodeFromDataIndex: function (dataIndex) { var index = this.nodesDataIndices[dataIndex]; if (index === undefined) { index = -1; } return index === -1 ? null : this.nodes[index]; }, captureScreenMetrics: function () { if (this._capturedScreenMetrics) { return; } this.innerHeight = this.scrollingContainer.getBoundingClientRect().height; if (this.innerHeight > 0) { this._capturedScreenMetrics = true; } }, _init: function () { if (this._inited) { return; } this.container.innerHTML = ''; var node = this.template.cloneNode(true); this.container.appendChild(node); this.itemHeight = node.clientHeight; this.container.removeChild(node); this.captureScreenMetrics(); if (!this.itemHeight || !this.innerHeight) { return; } this.scrollingContainer.addEventListener('scroll', this.onEvent); this.itemsPerScreen = Math.ceil(this.innerHeight / this.itemHeight); this.prerenderItemCount = Math.ceil(this.itemsPerScreen * this.prerenderScreens); this.prefetchItemCount = Math.ceil(this.itemsPerScreen * this.prefetchScreens); this.recalculatePaddingItemCount = Math.ceil(this.itemsPerScreen * this.recalculatePaddingScreens); this.nodeCount = this.itemsPerScreen + this.prerenderItemCount + Math.ceil(this.retainExtraRenderedScreens * this.itemsPerScreen); for (var i = 0; i < this.nodeCount; i++) { node = this.template.cloneNode(true); node.classList.add(VScroll.nodeClassName); setTop(node, -1 * this.itemHeight); this.nodes.push(node); this._setNodeDataIndex(i, -1); } this._calculateTotalHeight(); this._inited = true; this.emit('inited'); }, _nextAvailableNode: function (beginIndex, endIndex) { var i, node, vScrollDataIndex, count = 0; for (i = this.nodesIndex + 1; count < this.nodes.length; count++, i++) { if (i > this.nodes.length - 1) { i = 0; } node = this.nodes[i]; vScrollDataIndex = node.vScrollDataIndex; if (vScrollDataIndex < beginIndex || vScrollDataIndex > endIndex) { this.nodesIndex = i; break; } } return node; }, _recalculate: function (refIndex) { if (!this._inited) { return; } var node, index = this.indexAtScrollPosition(this.scrollTop), remainder = this.scrollTop % this.itemHeight, sizeDiff = this.list.size() - this.oldListSize; if (refIndex && refIndex < index && sizeDiff > 0 && this.oldListSize !== 0 && index !== 0) { index += sizeDiff; } console.log('VSCROLL scrollTop: ' + this.scrollTop + ', RECALCULATE: ' + index + ', ' + remainder); this._calculateTotalHeight(); for (var i = 0; i < this.nodeCount; i++) { node = this.nodes[i]; setTop(node, -1 * this.itemHeight); this._setNodeDataIndex(i, -1); } this.waitingForRecalculate = false; this._setContainerScrollTop(this.itemHeight * index + remainder); this.renderCurrentPosition(); this.emit('recalculated', index === 0, refIndex); }, _calculateTotalHeight: function () { var newListSize = this.list.size(); if (this.oldListSize !== newListSize || parseInt(this.container.style.height, 10) === 0) { this.totalHeight = this.itemHeight * newListSize; this.container.style.height = this.totalHeight + 'px'; this.oldListSize = newListSize; } }, _scrollTimeoutPoll: function () { this._scrollStopTimeout = 0; if (Date.now() > this._lastEventTime + 300) { this.emit('scrollStopped'); } else { this._scrollStopTimeout = setTimeout(this._scrollTimeoutPoll, 300); } }, _startScrollStopPolling: function () { if (!this._scrollStopTimeout) { this._scrollStopTimeout = setTimeout(this._scrollTimeoutPoll, 300); } } }; evt.mix(VScroll.prototype); var originalOn = VScroll.prototype.on; VScroll.prototype.on = function (id, fn) { if (id === 'scrollStopped') { this._startScrollStopPolling(); } return originalOn.apply(this, slice.call(arguments)); }; return VScroll; }); define('cards/mixins/data-prop', [], function () { return { templateInsertedCallback: function () { var node, parent, nodes = this.querySelectorAll('[data-prop]'), length = nodes.length; for (var i = 0; i < length; i++) { node = nodes[i]; parent = node; while (parent = parent.parentNode) { if (parent.nodeName.indexOf('-') !== -1) { if (parent !== this) { node = null; } break; } } if (node && parent) { this[node.dataset.prop] = nodes[i]; } } } }; }); define('cards/mixins/data-event', [], function () { var slice = Array.prototype.slice; return { templateInsertedCallback: function () { slice.call(this.querySelectorAll('[data-event]')).forEach(function (node) { var parent = node; while (parent = parent.parentNode) { if (parent.nodeName.indexOf('-') !== -1) { if (parent !== this) { return; } break; } } if (!parent) { return; } node.dataset.event.split(',').forEach(function (pair) { var evtName, method, parts = pair.split(':'); if (!parts[1]) { parts[1] = parts[0]; } evtName = parts[0].trim(); method = parts[1].trim(); if (typeof this[method] !== 'function') { throw new Error('"' + method + '" is not a function, cannot bind with data-event'); } node.addEventListener(evtName, function (evt) { evt.stopPropagation(); return this[method](evt); }.bind(this), false); }.bind(this)); }.bind(this)); } }; }); define('cards/base', [ 'require', 'evt', './mixins/data-prop', './mixins/data-event' ], function (require) { var Emitter = require('evt').Emitter; return function base(templateMixins) { return [ templateMixins ? templateMixins : {}, require('./mixins/data-prop'), require('./mixins/data-event'), Emitter.prototype, { createdCallback: function () { this.classList.add('email-ce'); Emitter.call(this); }, emitDomEvent: function (eventName, detail) { this.dispatchEvent(new CustomEvent(eventName, { detail: detail })); } } ]; }; }); define('cards/base_card', [ 'require', 'date', 'l10n!', './base' ], function (require) { var date = require('date'), mozL10n = require('l10n!'), base = require('./base'); (function () { var updatePrettyDate = function updatePrettyDate() { var labels = [...document.querySelectorAll('[data-time]')]; labels.forEach(label => { date.relativeDateElement(label, label.dataset.time); }); }; var timer = setInterval(updatePrettyDate, 60 * 1000); function updatePrettyDateOnEvent() { clearInterval(timer); updatePrettyDate(); timer = setInterval(updatePrettyDate, 60 * 1000); } mozL10n.ready(updatePrettyDateOnEvent); document.addEventListener('visibilitychange', function () { if (document && !document.hidden) { updatePrettyDateOnEvent(); } else { clearInterval(timer); } }); }()); return function baseCard(templateMixins) { return [ base(templateMixins), { createdCallback: function () { if (this.extraClasses) { this.classList.add.apply(this.classList, this.extraClasses); } this.classList.add('card'); }, batchAddClass: function (searchClass, classToAdd) { var nodes = this.getElementsByClassName(searchClass); for (var i = 0; i < nodes.length; i++) { nodes[i].classList.add(classToAdd); } } } ]; }; }); define('element', [ 'require', 'exports', 'module' ], function (require, exports, module) { 'use strict'; var slice = Array.prototype.slice, callbackSuffix = 'Callback', callbackSuffixLength = callbackSuffix.length, charRegExp = /[^a-z]/g, idToTag = function (id) { return id.toLowerCase().replace(charRegExp, '-'); }, moduleConfig = module.config(); if (moduleConfig.hasOwnProperty('idToTag')) { idToTag = moduleConfig.idToTag; } function makePropName(attrName) { var parts = attrName.split('-'); for (var i = 1; i < parts.length; i++) { parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].substring(1); } return parts.join(''); } function setPropFromAttr(instance, attrName, attrValue) { var proto = Object.getPrototypeOf(instance), propName = makePropName(attrName), descriptor = Object.getOwnPropertyDescriptor(proto, propName); if (descriptor && descriptor.set) { instance[propName] = attrValue; } } function makePropFn(prop) { return function () { var i, ret, args = slice.call(arguments), fns = this._element.props[prop]; for (i = 0; i < fns.length; i++) { ret = fns[i].apply(this, args); } return ret; }; } function mixFnProp(proto, prop, value, operation) { if (proto.hasOwnProperty(prop)) { var existing = proto._element.props[prop]; if (!existing) { existing = proto._element.props[prop] = [proto[prop]]; proto[prop] = makePropFn(prop); } operation = operation || 'push'; existing[operation](value); } else { proto[prop] = value; } } function mix(proto, mixin) { if (Array.isArray(mixin)) { mixin.forEach(function (mixin) { mix(proto, mixin); }); return; } Object.keys(mixin).forEach(function (key) { var suffixIndex, descriptor = Object.getOwnPropertyDescriptor(mixin, key); suffixIndex = key.indexOf(callbackSuffix); if (suffixIndex > 0 && suffixIndex === key.length - callbackSuffixLength) { mixFnProp(proto, key, descriptor.value); } else { Object.defineProperty(proto, key, descriptor); } }); } var element = { load: function (id, req, onload, config) { req([id], function (mod) { if (config.isBuild || !mod || typeof mod === 'function') { return onload(); } var proto = Object.create(HTMLElement.prototype); Object.defineProperty(proto, '_element', { enumerable: false, configurable: false, writable: false, value: {} }); proto._element.props = {}; mix(proto, mod); mixFnProp(proto, 'createdCallback', function attrCreated() { var i, item, attrs = this.attributes; for (i = 0; i < attrs.length; i++) { item = attrs.item(i); setPropFromAttr(this, item.nodeName, item.value); } }, 'unshift'); mixFnProp(proto, 'attributeChangedCallback', function attrChanged(name, oldValue, newValue) { setPropFromAttr(this, name, newValue); }, 'unshift'); var tagId = idToTag(id); onload(document.registerElement(tagId, { prototype: proto })); }); } }; return element; }); define('template', [ 'require', 'exports', 'module', 'element' ], function (require, exports, module) { var template, fetchText, isReady = false, readyQueue = [], tagRegExp = /<(\w+-[\w-]+)(\s|>)/g, commentRegExp = //g, attrIdRegExp = /\s(hrefid|srcid)="([^"]+)"/g, buildProtocol = 'build:', moduleConfig = module.config(), depPrefix = 'element!', buildMap = {}, tagToId = function (tag) { return tag; }; require('element'); if (moduleConfig.hasOwnProperty('depPrefix')) { depPrefix = moduleConfig.depPrefix; } if (moduleConfig.hasOwnProperty('tagToId')) { tagToId = moduleConfig.tagToId; } function onReady() { isReady = true; var bodyTemplate = document.querySelector('template#body'); if (bodyTemplate) { bodyTemplate.parentNode.removeChild(bodyTemplate); document.body.innerHTML = bodyTemplate.innerHTML; } readyQueue.forEach(function (fn) { fn(); }); readyQueue = []; } function makeFullId(id, refId) { if (id.indexOf('.') === 0 && refId) { var parts = refId.split('/'); parts.pop(); refId = parts.join('/'); id = (refId ? refId + '/' : '') + id; } return id; } function templateCreatedCallback() { if (this.dataset.cached === 'cached' || this.template) { if (this.dataset.cached !== 'cached' && this.template) { this.innerHTML = ''; this.appendChild(this.template()); } if (this.templateInsertedCallback) { this.templateInsertedCallback(); } } } if (typeof XMLHttpRequest !== 'undefined') { fetchText = function (url, onload, onerror) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onreadystatechange = function () { var status, err; if (xhr.readyState === 4) { status = xhr.status; if (status > 399 && status < 600) { err = new Error(url + ' HTTP status: ' + status); err.xhr = xhr; onerror(err); } else { onload(xhr.responseText); } } }; xhr.responseType = 'text'; xhr.send(null); }; } else { fetchText = function (url, onload) { onload(requirejs._readFile(url)); }; } template = { fetchText: fetchText, ready: function (fn) { if (isReady) { setTimeout(fn); } else { readyQueue.push(fn); } }, makeFullId: makeFullId, makeTemplateFn: function (text) { return function () { var e, frag = document.createDocumentFragment(), templateDiv = document.createElement('div'); templateDiv.innerHTML = text; while (e = templateDiv.firstChild) { frag.appendChild(e); } return frag; }; }, idsToUrls: function (text, refId) { text = text.replace(attrIdRegExp, function (match, type, id) { id = makeFullId(id, refId); var attr = type === 'hrefid' ? 'href' : 'src'; return ' ' + attr + '="' + require.toUrl(id) + '"'; }); return text; }, depsFromText: function (text) { var match, noCommentText, deps = []; noCommentText = text.replace(commentRegExp, ''); tagRegExp.lastIndex = 0; while (match = tagRegExp.exec(noCommentText)) { deps.push(depPrefix + tagToId(match[1])); } return deps; }, textToTemplate: function (text, id, skipTranslateIds) { var obj, deps = template.depsFromText(text); obj = { id: id, deps: deps, text: text }; if (!skipTranslateIds) { obj.text = template.idsToUrls(text, id); obj.fn = template.makeTemplateFn(obj.text); } return obj; }, objToFn: function (obj) { var text = template.idsToUrls(obj.text, obj.id); return template.makeTemplateFn(text); }, templateCreatedCallback: templateCreatedCallback, load: function (id, req, onload, config) { var isBuild = config.isBuild; if (id.indexOf(buildProtocol) === 0 && isBuild) { id = id.substring(buildProtocol.length); var idList = id.split(','), count = 0, buildIdDone = function () { count += 1; if (count === idList.length) { onload(); } }; buildIdDone.__requireJsBuild = true; id.split(',').forEach(function (moduleId) { var path = req.toUrl(moduleId); require(template.depsFromText(requirejs._readFile(path)), buildIdDone); }); } else { fetchText(req.toUrl(id), function (text) { var templateObj = template.textToTemplate(text, id, isBuild); req(templateObj.deps, function () { if (isBuild) { buildMap[id] = templateObj; } onload({ createdCallback: templateCreatedCallback, template: templateObj.fn }); }); }, onload.error); } }, write: function (pluginName, id, write) { if (buildMap.hasOwnProperty(id)) { var obj = buildMap[id], depString = JSON.stringify(obj.deps); depString = depString.replace(/^\s*\[/, '').replace(/\]\s*$/, '').trim(); if (depString) { depString = ', ' + depString; } write.asModule(pluginName + '!' + id, 'define([\'' + module.id + '\'' + depString + '], function(template) { return {\n' + 'createdCallback: template.templateCreatedCallback,\n' + 'template: template.objToFn(' + JSON.stringify(buildMap[id]) + ')}; });\n'); } } }; if (typeof document !== 'undefined') { var onDom, onDomDone = false; onDom = function () { if (onDomDone) { return; } onDomDone = true; var converted = template.textToTemplate(document.body.innerHTML); require(converted.deps, onReady); }; if (document.readyState === 'interactive' || document.readyState === 'complete') { onDom(); } else { window.addEventListener('DOMContentLoaded', onDom); } } return template; }); define('template!cards/lst/edit_header.html', ['template'], function (template) { return { createdCallback: template.templateCreatedCallback, template: template.objToFn({ 'id': 'cards/lst/edit_header.html', 'deps': [], 'text': '
    \n
    \n \n \n \n

    \n
    \n
    \n' }) }; }); define('cards/mixins/dom_evt', [], function () { return { domEvt: function (event) { var target = event.currentTarget, eventName = target.dataset.domevtName, eventData = {}, eventDetail = target.dataset.domevtDetail; event.preventDefault(); event.stopPropagation(); if (!eventName) { throw new Error('No data-domevt-name on element <' + target.nodeName + ' class="' + target.className + '">'); } if (eventDetail) { eventData.detail = eventDetail; } this.dispatchEvent(new CustomEvent(eventName, eventData)); } }; }); define('cards/lst/edit_header', [ 'require', 'exports', 'module', 'l10n!', '../base', 'template!./edit_header.html', '../mixins/dom_evt' ], function (require, exports) { var mozL10n = require('l10n!'); return [ require('../base')(require('template!./edit_header.html')), require('../mixins/dom_evt'), { updateDomHeaderCount: function (count) { mozL10n.setAttributes(this.headerNode, 'message-multiedit-header', { n: count }); } } ]; }); define('container_listen', [], function () { return function containerListen(containerNode, eventName, func) { containerNode.addEventListener(eventName, function (event) { var node = event.target; if (node === containerNode) { return; } while (node && node.parentNode !== containerNode) { node = node.parentNode; } if (node) { func(node, event); } }, false); }; }); define('tmpl!cards/msg/header_item.html', ['tmpl'], function (tmpl) { return tmpl.toDom('\n \n
    \n \n \n \n \n
    \n
    \n
    \n
    \n \n \n
    \n'); }); define('template!cards/lst/msg_vscroll.html', ['template'], function (template) { return { createdCallback: template.templateCreatedCallback, template: template.objToFn({ 'id': 'cards/lst/msg_vscroll.html', 'deps': [], 'text': '
    \n\n
    \n \n \n
    \n' }) }; }); define('cards/lst/msg_vscroll', [ 'require', 'exports', 'module', 'cards', 'container_listen', 'l10n!', 'tmpl!../msg/header_item.html', 'toaster', 'vscroll', '../base', 'template!./msg_vscroll.html' ], function (require, exports) { var cards = require('cards'), containerListen = require('container_listen'), mozL10n = require('l10n!'), msgHeaderItemNode = require('tmpl!../msg/header_item.html'), toaster = require('toaster'), VScroll = require('vscroll'); var sliceEvents = [ 'splice', 'change', 'status', 'complete' ]; var MINIMUM_ITEMS_FOR_SCROLL_CALC = 10; var MAXIMUM_MS_BETWEEN_SNIPPET_REQUEST = 6000; var MAXIMUM_BYTES_PER_MESSAGE_DURING_SCROLL = 4 * 1024; return [ require('../base')(require('template!./msg_vscroll.html')), { createdCallback: function () { this.setAttribute('role', 'listbox'); this.setAttribute('aria-multiselectable', 'true'); mozL10n.setAttributes(this.messageEmptyText, this.dataset.emptyL10nId); containerListen(this.vScrollContainer, 'click', this.onClickMessage.bind(this)); }, init: function (scrollContainer, bindData, defaultVScrollData) { this.scrollContainer = scrollContainer; var listFunc = function (index) { return this.headerCursor.messagesSlice.items[index]; }.bind(this); listFunc.size = function () { var slice = this.headerCursor.messagesSlice; return Math.max(slice.headerCount || 0, slice.items.length); }.bind(this); this.listFunc = listFunc; this.waitingOnChunk = true; this.desiredHighAbsoluteIndex = 0; this._needVScrollData = false; this.vScroll = new VScroll(this.vScrollContainer, this.scrollContainer, msgHeaderItemNode, defaultVScrollData); this.vScroll.bindData = bindData; this.vScroll.prepareData = function (highAbsoluteIndex) { var items = this.headerCursor.messagesSlice && this.headerCursor.messagesSlice.items, headerCount = this.headerCursor.messagesSlice.headerCount; if (!items || !headerCount) { return; } if (highAbsoluteIndex > headerCount - 1) { highAbsoluteIndex = headerCount - 1; } if (highAbsoluteIndex < items.length) { return; } this.loadNextChunk(highAbsoluteIndex); }.bind(this); }, setHeaderCursor: function (headerCursor) { if (this.headerCursor) { throw new Error('headerCursor already set'); } this.headerCursor = headerCursor; sliceEvents.forEach(function (type) { var name = 'messages_' + type; this[name] = this[name].bind(this); headerCursor.on(name, this[name]); }.bind(this)); this.onCurrentMessage = this.onCurrentMessage.bind(this); headerCursor.on('currentMessage', this.onCurrentMessage); var parent = this; while (parent = parent.parentNode) { if (parent.classList.contains('card')) { break; } } this.cardParent = parent; this._onVScrollStopped = this._onVScrollStopped.bind(this); this.vScroll.on('scrollStopped', this._onVScrollStopped); }, removeMessagesHtml: function () { this.vScrollContainer.innerHTML = ''; }, onClickMessage: function (node, event) { this.emitDomEvent('messageClick', node); }, _onVScrollStopped: function () { if (!this.headerCursor.messagesSlice || this.headerCursor.messagesSlice.pendingRequestCount) { return; } if (cards.isVisible(this.cardParent) && !this._hasSnippetRequest()) { this._requestSnippets(); } }, onGetMoreMessages: function () { if (!this.headerCursor.messagesSlice) { return; } this.vScroll.once('recalculated', function (calledFromTop, refIndex) { this.vScrollContainer.querySelector('[data-index="' + refIndex + '"]').focus(); }.bind(this)); this.headerCursor.messagesSlice.requestGrowth(1, true); }, isEmpty: function () { return this.headerCursor.messagesSlice.items.length === 0; }, showEmptyLayout: function () { this.messageEmptyContainer.classList.remove('collapsed'); this.emit('emptyLayoutShown'); }, hideEmptyLayout: function () { this.messageEmptyContainer.classList.add('collapsed'); this.emit('emptyLayoutHidden'); }, messages_status: function (newStatus) { var syncInProgress = true; if (newStatus === 'synchronizing' || newStatus === 'syncblocked') { this.syncingNode.classList.remove('collapsed'); this.syncMoreNode.classList.add('collapsed'); this.hideEmptyLayout(); } else if (newStatus === 'syncfailed' || newStatus === 'synced') { syncInProgress = false; if (newStatus === 'syncfailed') { toaster.toast({ text: mozL10n.get('toaster-retryable-syncfailed') }); } this.syncingNode.classList.add('collapsed'); } this.emit('syncInProgress', syncInProgress); }, messages_complete: function (newEmailCount) { var headerCursor = this.headerCursor; console.log('message_list complete:', headerCursor.messagesSlice.items.length, 'items of', headerCursor.messagesSlice.headerCount, 'alleged known headers. canGrow:', headerCursor.messagesSlice.userCanGrowDownwards); if (headerCursor.messagesSlice.userCanGrowDownwards && headerCursor.messagesSlice.headerCount) { this.syncMoreNode.classList.remove('collapsed'); } else { this.syncMoreNode.classList.add('collapsed'); } if (headerCursor.messagesSlice.items.length === 0) { this.showEmptyLayout(); } this.waitingOnChunk = false; if (this.desiredHighAbsoluteIndex) { this.loadNextChunk(this.desiredHighAbsoluteIndex); this.desiredHighAbsoluteIndex = 0; } this.vScroll.updateDataBind(0, [], 0); this.emit('messagesComplete', newEmailCount); }, messages_splice: function (index, howMany, addedItems, requested, moreExpected) { var headerCursor = this.headerCursor; if (index === 0 && howMany === 0 && !addedItems.length) { return; } this.emit('messagesSpliceStart', index, howMany, addedItems, requested, moreExpected); if (this._needVScrollData) { this.vScroll.setData(this.listFunc); this._needVScrollData = false; } this.vScroll.updateDataBind(index, addedItems, howMany); if (addedItems.length > 0) { this.hideEmptyLayout(); } if (!headerCursor.messagesSlice.headerCount) { this.vScroll.once('scrollStopped', function () { if (!headerCursor.messagesSlice.headerCount) { this.showEmptyLayout(); } }.bind(this)); } this.emit('messagesSpliceEnd', index, howMany, addedItems, requested, moreExpected); }, messages_change: function (message, index) { this.emit('messagesChange', message, index); }, loadNextChunk: function (desiredHighAbsoluteIndex) { if (this.vScroll.waitingForRecalculate) { return; } if (this.waitingOnChunk) { this.desiredHighAbsoluteIndex = desiredHighAbsoluteIndex; return; } var headerCursor = this.headerCursor; if (desiredHighAbsoluteIndex >= headerCursor.messagesSlice.headerCount) { desiredHighAbsoluteIndex = headerCursor.messagesSlice.headerCount - 1; } var items = headerCursor.messagesSlice.items; var curHighAbsoluteIndex = items.length - 1; var amount = desiredHighAbsoluteIndex - curHighAbsoluteIndex; if (amount > 0) { console.log('message_list loadNextChunk growing', amount, amount === 1 ? '(will get boosted to 15!) to' : 'to', desiredHighAbsoluteIndex + 1, 'items out of', headerCursor.messagesSlice.headerCount, 'alleged known'); headerCursor.messagesSlice.requestGrowth(amount, false); this.waitingOnChunk = true; } }, onCurrentMessage: function (currentMessage, index) { if (!currentMessage) { return; } var visibleIndices = this.vScroll.getVisibleIndexRange(); if (visibleIndices && (index < visibleIndices[0] || index > visibleIndices[1])) { this.vScroll.jumpToIndex(index); } }, _hasSnippetRequest: function () { var max = MAXIMUM_MS_BETWEEN_SNIPPET_REQUEST; var now = Date.now(); var beforeTimeout = this._lastSnippetRequest + max > now; if (this._snippetRequestPending && beforeTimeout) { return true; } return false; }, _pendingSnippetRequest: function () { this._snippetRequestPending = true; this._lastSnippetRequest = Date.now(); }, _clearSnippetRequest: function () { this._snippetRequestPending = false; }, _requestSnippets: function () { var headerCursor = this.headerCursor; var items = headerCursor.messagesSlice.items; var len = items.length; if (!len) { return; } var clearSnippets = this._clearSnippetRequest.bind(this); var options = { maximumBytesToFetch: MAXIMUM_BYTES_PER_MESSAGE_DURING_SCROLL }; if (len < MINIMUM_ITEMS_FOR_SCROLL_CALC) { this._pendingSnippetRequest(); headerCursor.messagesSlice.maybeRequestBodies(0, MINIMUM_ITEMS_FOR_SCROLL_CALC - 1, options, clearSnippets); return; } var visibleIndices = this.vScroll.getVisibleIndexRange(); if (visibleIndices) { this._pendingSnippetRequest(); headerCursor.messagesSlice.maybeRequestBodies(visibleIndices[0], visibleIndices[1], options, clearSnippets); } }, die: function () { sliceEvents.forEach(function (type) { var name = 'messages_' + type; this.headerCursor.removeListener(name, this[name]); }.bind(this)); this.headerCursor.removeListener('currentMessage', this.onCurrentMessage); this.vScroll.destroy(); } } ]; }); define('template!cards/lst/edit_toolbar.html', ['template'], function (template) { return { createdCallback: template.templateCreatedCallback, template: template.objToFn({ 'id': 'cards/lst/edit_toolbar.html', 'deps': [], 'text': '\n' }) }; }); define('cards/lst/edit_toolbar', [ 'require', 'exports', 'module', 'l10n!', '../base', 'template!./edit_toolbar.html', '../mixins/dom_evt' ], function (require, exports) { var mozL10n = require('l10n!'), toolbarEditButtonNames = [ 'starBtn', 'readBtn', 'deleteBtn', 'moveBtn' ]; return [ require('../base')(require('template!./edit_toolbar.html')), require('../mixins/dom_evt'), { updateDomFolderType: function (folderType) { this.moveBtn.classList.toggle('collapsed', folderType === 'localdrafts' || folderType === 'outbox'); this.starBtn.classList.toggle('collapsed', folderType === 'outbox'); this.readBtn.classList.toggle('collapsed', folderType === 'outbox'); }, updateDomStartButton: function (isStarred) { mozL10n.setAttributes(this.starBtn, isStarred ? 'message-star-button' : 'message-unstar-button'); }, updateDomReadButton: function (hasUnread) { this.readBtn.classList.toggle('unread', hasUnread); mozL10n.setAttributes(this.readBtn, hasUnread ? 'message-mark-unread-button' : 'message-mark-read-button'); }, updateDomEditButtons: function (hasMessages) { toolbarEditButtonNames.forEach(function (key) { this[key].disabled = !hasMessages; }.bind(this)); } } ]; }); define('template!cards/message_list.html', [ 'template', 'element!cards/lst/edit_header', 'element!cards/lst/msg_vscroll', 'element!cards/lst/edit_toolbar' ], function (template) { return { createdCallback: template.templateCreatedCallback, template: template.objToFn({ 'id': 'cards/message_list.html', 'deps': [ 'element!cards/lst/edit_header', 'element!cards/lst/msg_vscroll', 'element!cards/lst/edit_toolbar' ], 'text': '\n
    \n
    \n \n \n \n \n \n \n \n

    \n \n \n

    \n
    \n
    \n\n\n\n\n
    \n \n
    \n \n \n \n \n
    \n
    \n\n\n
    \n\n\n\n\n\n\n\n' }) }; }); define('template!cards/confirm_dialog.html', ['template'], function (template) { return { createdCallback: template.templateCreatedCallback, template: template.objToFn({ 'id': 'cards/confirm_dialog.html', 'deps': [], 'text': '
    \n \n
    \n' }) }; }); define('cards/confirm_dialog', [ 'require', 'cards', './base_card', 'template!./confirm_dialog.html' ], function (require) { var cards = require('cards'); return [ require('./base_card')(require('template!./confirm_dialog.html')), { onArgs: function (args) { var dialogBodyNode = args.dialogBodyNode, confirm = args.confirm, cancel = args.cancel, callback = args.callback; if (dialogBodyNode) { this.appendChild(dialogBodyNode); } else { dialogBodyNode = this.querySelector('.confirm-dialog-form'); dialogBodyNode.querySelector('.confirm-dialog-message').textContent = args.message; dialogBodyNode.classList.remove('collapsed'); confirm = { handler: function () { callback(true); } }; cancel = { handler: function () { callback(false); } }; } dialogBodyNode.addEventListener('submit', function (evt) { evt.preventDefault(); evt.stopPropagation(); this.hide(); var target = evt.explicitOriginalTarget, targetId = target.id, isOk = target.classList.contains('confirm-dialog-ok'), isCancel = target.classList.contains('confirm-dialog-cancel'); if ((isOk || targetId === confirm.id) && confirm.handler) { confirm.handler(); } else if ((isCancel || targetId === cancel.id) && cancel.handler) { cancel.handler(); } }.bind(this)); }, hide: function () { cards.removeCardAndSuccessors(this, 'immediate', 1, null, true); }, die: function () { } } ]; }); define('confirm_dialog', [ 'require', 'exports', 'module', 'cards', 'element!cards/confirm_dialog' ], function (require, exports) { var cards = require('cards'), ConfirmDialog = require('element!cards/confirm_dialog'); ConfirmDialog.show = function (message, callback, cancel) { var dialogBodyNode; if (typeof message !== 'string') { dialogBodyNode = message; message = null; } cards.pushCard('confirm_dialog', 'immediate', { dialogBodyNode: dialogBodyNode, message: message, confirm: callback, callback: callback, cancel: cancel }, 'right'); }; return ConfirmDialog; }); define('tmpl!cards/msg/delete_confirm.html', ['tmpl'], function (tmpl) { return tmpl.toDom(''); }); define('cards/lst/edit_controller', [ 'require', 'exports', 'module', 'cards', 'confirm_dialog', 'tmpl!../msg/delete_confirm.html', 'l10n!', 'toaster' ], function (require, exports) { var cards = require('cards'), ConfirmDialog = require('confirm_dialog'), deleteConfirmMsgNode = require('tmpl!../msg/delete_confirm.html'), mozL10n = require('l10n!'), toaster = require('toaster'); return { createdCallback: function () { this.editMode = false; this.selectedMessages = null; this.editModeEnabled = false; }, setEditMode: function (editMode) { if (!this.editModeEnabled) { return; } this._setEditMode(editMode); }, _setEditMode: function (editMode) { var i; this.editMode = editMode; if (editMode) { this.classList.add('show-edit'); this.selectedMessages = []; this.updateDomEditControls(); } else { this.classList.remove('show-edit'); this.selectedMessages = null; } var msgNodes = this.msgVScroll.querySelectorAll('.msg-header-item'); for (i = 0; i < msgNodes.length; i++) { this.updateDomMessageChecked(msgNodes[i], false); } if (this.editModeChanged) { this.editModeChanged(editMode); } }, setEditModeStart: function () { this.setEditMode(true); }, editHeaderClose: function () { this.setEditMode(false); }, toggleSelection: function (msgNode) { var header = msgNode.message; var idx = this.selectedMessages.indexOf(header); if (idx !== -1) { this.selectedMessages.splice(idx, 1); } else { this.selectedMessages.push(header); } this.updateDomMessageChecked(msgNode, idx === -1); this.updateDomEditControls(); }, updateDomSelectState: function (msgNode, message) { if (this.editMode) { this.updateDomMessageChecked(msgNode, this.selectedMessages.indexOf(message) !== -1); } else { msgNode.removeAttribute('aria-selected'); } }, updateDomMessageChecked: function (msgNode, checked) { var checkbox = msgNode.querySelector('input[type=checkbox]'); checkbox.checked = checked; msgNode.setAttribute('aria-selected', checked); }, updateDomEditControls: function () { this.editHeader.updateDomHeaderCount(this.selectedMessages.length); this.editToolbar.updateDomEditButtons(this.selectedMessages.length > 0); var numStarred = 0, numRead = 0; for (var i = 0; i < this.selectedMessages.length; i++) { var msg = this.selectedMessages[i]; if (msg.isStarred) { numStarred++; } if (msg.isRead) { numRead++; } } this.setAsStarred = !(numStarred && numStarred === this.selectedMessages.length); this.editToolbar.updateDomStartButton(this.setAsStarred); this.setAsRead = !!this.selectedMessages.length && numRead === 0; this.editToolbar.updateDomReadButton(numRead > 0); }, onDeleteMessages: function () { if (this.selectedMessages.length === 0) { return this.setEditMode(false); } var dialog = deleteConfirmMsgNode.cloneNode(true); var content = dialog.getElementsByTagName('p')[0]; mozL10n.setAttributes(content, 'message-multiedit-delete-confirm', { n: this.selectedMessages.length }); ConfirmDialog.show(dialog, { id: 'msg-delete-ok', handler: function () { var op = this.model.api.deleteMessages(this.selectedMessages); toaster.toastOperation(op); this.setEditMode(false); }.bind(this) }, { id: 'msg-delete-cancel', handler: null }); }, onStarMessages: function () { var op = this.model.api.markMessagesStarred(this.selectedMessages, this.setAsStarred); this.setEditMode(false); toaster.toastOperation(op); }, onMarkMessagesRead: function () { var op = this.model.api.markMessagesRead(this.selectedMessages, this.setAsRead); this.setEditMode(false); toaster.toastOperation(op); }, onMoveMessages: function () { cards.folderSelector(this.model, function (folder) { var op = this.model.api.moveMessages(this.selectedMessages, folder); toaster.toastOperation(op); this.setEditMode(false); }.bind(this), function (folder) { return folder.isValidMoveTarget; }); } }; }); define('tmpl!cards/msg/large_message_confirm.html', ['tmpl'], function (tmpl) { return tmpl.toDom('\n'); }); define('cards/lst/msg_click', [ 'require', 'exports', 'module', 'cards', 'confirm_dialog', 'tmpl!../msg/large_message_confirm.html' ], function (require, exports) { var cards = require('cards'), ConfirmDialog = require('confirm_dialog'), largeMsgConfirmMsgNode = require('tmpl!../msg/large_message_confirm.html'); function showLargeMessageWarning(size, cb) { var dialog = largeMsgConfirmMsgNode.cloneNode(true); ConfirmDialog.show(dialog, { id: 'msg-large-message-ok', handler: function () { cb(true); } }, { id: 'msg-large-message-cancel', handler: function () { cb(false); } }); } return { onClickMessage: function (event) { var messageNode = event.detail; if (this.curFolder && this.curFolder.type === 'outbox' && this.outboxSyncInProgress) { return; } var header = messageNode.message; if (header && header.isPlaceholderData) { return; } if (this.editMode) { this.toggleSelection(messageNode); return; } if (this.curFolder && this.curFolder.type === 'localdrafts') { var composer = header.editAsDraft(() => { cards.pushCard('compose', 'animate', { model: this.model, composer }); }); return; } if (this.curFolder && this.curFolder.type === 'outbox') { if (header.sendStatus.state === 'sending') { return; } var draftsFolder = this.model.foldersSlice.getFirstFolderWithType('localdrafts'); console.log('outbox: Moving message to localdrafts.'); this.model.api.moveMessages([header], draftsFolder, moveMap => { header.id = moveMap[header.id]; console.log('outbox: Editing message in localdrafts.'); var composer = header.editAsDraft(() => { cards.pushCard('compose', 'animate', { model: this.model, composer }); }); }); return; } var model = this.model, headerCursor = this.headerCursor; function pushMessageCard() { cards.pushCard('message_reader', 'animate', { model, headerCursor, header, messageSuid: messageNode.dataset.id }); } if (header) { this.headerCursor.setCurrentMessage(header); } else if (messageNode.dataset.id) { this.headerCursor.setCurrentMessageBySuid(messageNode.dataset.id); } else { return; } var LARGE_MESSAGE_SIZE = 1 * 1024 * 1024; if (header && header.bytesToDownloadForBodyDisplay > LARGE_MESSAGE_SIZE) { showLargeMessageWarning(header.bytesToDownloadForBodyDisplay, function (result) { if (result) { pushMessageCard(); } else { } }); } else { pushMessageCard(); } } }; }); define('cards/message_list', [ 'require', 'exports', 'module', 'cards', 'date', './lst/default_vscroll_data', 'evt', 'toaster', 'header_cursor', 'html_cache', 'l10n!', 'message_list_topbar', 'message_display', './lst/peep_dom', 'vscroll', './base_card', 'template!./message_list.html', './lst/edit_controller', './lst/msg_click' ], function (require, exports, module) { var cards = require('cards'), date = require('date'), defaultVScrollData = require('./lst/default_vscroll_data'), evt = require('evt'), toaster = require('toaster'), HeaderCursor = require('header_cursor'), htmlCache = require('html_cache'), mozL10n = require('l10n!'), MessageListTopBar = require('message_list_topbar'), messageDisplay = require('message_display'), updatePeepDom = require('./lst/peep_dom').update, VScroll = require('vscroll'); return [ require('./base_card')(require('template!./message_list.html')), require('./lst/edit_controller'), require('./lst/msg_click'), { createdCallback: function () { this._needsSizeLastSync = true; this.updateLastSynced(); this.curFolder = null; this.isIncomingFolder = true; this._hideSearchBoxByScrolling = this._hideSearchBoxByScrolling.bind(this); this._folderChanged = this._folderChanged.bind(this); this.onNewMail = this.onNewMail.bind(this); this.onFoldersSliceChange = this.onFoldersSliceChange.bind(this); this.usingCachedNode = this.dataset.cached === 'cached'; this.msgVScroll.on('messagesSpliceStart', function (index, howMany, addedItems, requested, moreExpected) { this._clearCachedMessages(); }.bind(this)); this.msgVScroll.on('messagesSpliceEnd', function (index, howMany, addedItems, requested, moreExpected) { if (addedItems.length || howMany) { this._considerCacheDom(index); } }.bind(this)); this.msgVScroll.on('messagesChange', function (message, index) { this.onMessagesChange(message, index); }.bind(this)); this._emittedContentEvents = false; this.msgVScroll.on('messagesComplete', function (newEmailCount) { this.onNewMail(newEmailCount); if (!this._emittedContentEvents) { evt.emit('metrics:contentDone'); this._emittedContentEvents = true; } }.bind(this)); var oldMessagesStatus = this.msgVScroll.messages_status; this.msgVScroll.messages_status = function (newStatus) { if (!this.curFolder || this.curFolder.type === 'outbox') { return; } return oldMessagesStatus.call(this.msgVScroll, newStatus); }.bind(this); this.msgVScroll.on('emptyLayoutShown', function () { this._clearCachedMessages(); if (this.curFolder.type === 'outbox') { this.refreshBtn.disabled = true; } this.editBtn.disabled = true; this._hideSearchBoxByScrolling(); }.bind(this)); this.msgVScroll.on('emptyLayoutHidden', function () { this.editBtn.disabled = false; this.refreshBtn.disabled = false; }.bind(this)); this.msgVScroll.on('syncInProgress', function (syncInProgress) { if (syncInProgress) { this.setRefreshState(true); } else { this.setRefreshState(false); this._manuallyTriggeredSync = false; } }.bind(this)); var vScrollBindData = function bindNonSearch(model, node) { model.element = node; node.message = model; this.updateMessageDom(model); }.bind(this); this.msgVScroll.init(this.scrollContainer, vScrollBindData, defaultVScrollData); this.msgVScroll.vScroll.on('inited', this._hideSearchBoxByScrolling); this.msgVScroll.vScroll.on('dataChanged', this._hideSearchBoxByScrolling); this.msgVScroll.vScroll.on('recalculated', function (calledFromTop) { if (calledFromTop) { this._hideSearchBoxByScrolling(); } }.bind(this)); this._topBar = new MessageListTopBar(this.querySelector('.message-list-topbar')); this._topBar.bindToElements(this.scrollContainer, this.msgVScroll.vScroll); this.onFolderPickerClosing = this.onFolderPickerClosing.bind(this); evt.on('folderPickerClosing', this.onFolderPickerClosing); }, onArgs: function (args) { var model = this.model = args.model; var headerCursor = this.headerCursor = args.headerCursor || new HeaderCursor(model); this.msgVScroll.setHeaderCursor(headerCursor); model.latest('folder', this._folderChanged); model.on('newInboxMessages', this.onNewMail); model.on('backgroundSendStatus', this.onBackgroundSendStatus.bind(this)); model.on('foldersSliceOnChange', this.onFoldersSliceChange); if (this.curFolder) { var items = headerCursor.messagesSlice && headerCursor.messagesSlice.items; if (items && items.length) { this.msgVScroll.messages_splice(0, 0, items); this.msgVScroll.messages_complete(0); } } }, skipEmitContentEvents: true, postInsert: function () { this._hideSearchBoxByScrolling(); this.msgVScroll.vScroll.visibleOffset = this.searchBar.getBoundingClientRect().height; this._topBar.visibleOffset = this.msgVScroll.vScroll.visibleOffset; this.msgVScroll.vScroll.captureScreenMetrics(); }, onSearchButton: function () { if (!this.curFolder) { return; } cards.pushCard('message_list_search', 'animate', { model: this.model, folder: this.curFolder }); }, _hideSearchBoxByScrolling: function () { var searchBar = this.searchBar, scrollContainer = this.scrollContainer; if (searchBar.classList.contains('collapsed')) { searchBar.classList.remove('collapsed'); scrollContainer.scrollTop += searchBar.offsetHeight; } if (scrollContainer.scrollTop === 0) { scrollContainer.scrollTop = searchBar.offsetHeight; } }, onShowFolders: function () { cards.pushCard('folder_picker', 'immediate', { model: this.model, onPushed: function () { this.headerMenuNode.classList.add('transparent'); }.bind(this) }); }, onCompose: function () { cards.pushCard('compose', 'animate', { model: this.model }); }, sizeLastSync: function () { if (this._needsSizeLastSync && this.lastSyncedLabel.scrollWidth) { var label = this.lastSyncedLabel; var overHalf = label.scrollWidth > label.parentNode.clientWidth / 2; label.parentNode.classList[overHalf ? 'add' : 'remove']('long'); this._needsSizeLastSync = false; } }, updateLastSynced: function (value) { var method = value ? 'remove' : 'add'; this.lastSyncedLabel.classList[method]('collapsed'); date.setPrettyNodeDate(this.lastSyncedAtNode, value); this.sizeLastSync(); }, updateUnread: function (num) { var content = ''; if (num > 0) { content = num > 999 ? mozL10n.get('messages-folder-unread-max') : num; } this.folderUnread.textContent = content; this.folderUnread.classList.toggle('collapsed', !content); this.callHeaderFontSize(); }, onFoldersSliceChange: function (folder) { if (folder === this.curFolder) { this.updateUnread(folder.unread); this.updateLastSynced(folder.lastSyncedAt); } }, callHeaderFontSize: function (node) { requestAnimationFrame(function () { FontSizeUtils._reformatHeaderText(this.folderLabel); }.bind(this)); }, showFolder: function (folder, forceNewSlice) { if (folder === this.curFolder && !forceNewSlice) { return false; } if (!this.usingCachedNode) { this.msgVScroll.vScroll.clearDisplay(); } this.msgVScroll._needVScrollData = true; this.curFolder = folder; this.editModeEnabled = true; switch (folder.type) { case 'drafts': case 'localdrafts': case 'outbox': case 'sent': this.isIncomingFolder = false; break; default: this.isIncomingFolder = true; break; } this.folderNameNode.textContent = folder.name; this.updateUnread(folder.unread); this.msgVScroll.setAttribute('aria-label', folder.name); this.msgVScroll.hideEmptyLayout(); this.refreshBtn.classList.toggle('collapsed', folder.type === 'localdrafts'); this.editToolbar.updateDomFolderType(folder.type); this.updateLastSynced(folder.lastSyncedAt); if (forceNewSlice) { this.msgVScroll._snippetRequestPending = false; this.headerCursor.freshMessagesSlice(); } this.onFolderShown(); return true; }, setEditMode: function (editMode) { if (!this.editModeEnabled) { return; } if (this.curFolder.type === 'outbox') { if (editMode && this.outboxSyncInProgress) { return; } var model = this.model; model.api.setOutboxSyncEnabled(model.account, !editMode, function () { this._setEditMode(editMode); }.bind(this)); } else { this._setEditMode(editMode); } }, setRefreshState: function (syncing) { if (syncing) { this.refreshBtn.dataset.state = 'synchronizing'; this.refreshBtn.setAttribute('role', 'progressbar'); mozL10n.setAttributes(this.refreshBtn, 'messages-refresh-progress'); } else { this.refreshBtn.dataset.state = 'synchronized'; this.refreshBtn.removeAttribute('role'); mozL10n.setAttributes(this.refreshBtn, 'messages-refresh-button'); } }, onNewMail: function (newEmailCount) { var inboxFolder = this.model.foldersSlice.getFirstFolderWithType('inbox'); if (inboxFolder.id === this.curFolder.id && newEmailCount && newEmailCount > 0) { if (!cards.isVisible(this)) { this._whenVisible = this.onNewMail.bind(this, newEmailCount); return; } if (this._manuallyTriggeredSync) { this.msgVScroll.vScroll.jumpToIndex(0); } else { this._topBar.showNewEmailCount(newEmailCount); } } }, onBackgroundSendStatus: function (data) { if (this.curFolder.type === 'outbox') { if (data.state === 'sending') { this.toggleOutboxSyncingDisplay(true); } else if (data.state === 'syncDone') { this.toggleOutboxSyncingDisplay(false); } } if (data.emitNotifications) { toaster.toast({ text: data.localizedDescription }); } }, _cacheListLimit: 7, _cacheDomTimeoutId: 0, _isCacheableCardState: function () { return this.cacheableFolderId === this.curFolder.id && !this.editMode; }, _cacheDom: function () { this._cacheDomTimeoutId = 0; if (!this._isCacheableCardState()) { return; } var cacheNode = htmlCache.cloneAsInertNodeAvoidingCustomElementHorrors(this); cacheNode.querySelector('menu[type="toolbar"]').classList.remove('transparent'); var removableCacheNode = cacheNode.querySelector('.msg-search-tease-bar'); if (removableCacheNode) { removableCacheNode.classList.add('collapsed'); } removableCacheNode = cacheNode.querySelector('.message-list-topbar'); if (removableCacheNode) { this._topBar.resetNodeForCache(removableCacheNode); } var tempNode = cacheNode.querySelector('.msg-last-synced-label'); if (tempNode) { tempNode.classList.add('collapsed'); } tempNode = cacheNode.querySelector('.msg-last-synced-value'); if (tempNode) { tempNode.innerHTML = ''; } VScroll.trimMessagesForCache(cacheNode.querySelector('.msg-vscroll-container'), this._cacheListLimit); htmlCache.saveFromNode(module.id, cacheNode); }, _considerCacheDom: function (index) { if (!this._cacheDomTimeoutId && this._isCacheableCardState() && this.msgVScroll.vScroll.firstRenderedIndex === 0 && (index || index === 0) && index < this._cacheListLimit) { this._cacheDomTimeoutId = setTimeout(this._cacheDom.bind(this), 600); } }, _clearCachedMessages: function () { if (this.usingCachedNode) { this.msgVScroll.removeMessagesHtml(); this.usingCachedNode = false; } }, onMessagesChange: function (message, index) { this.updateMessageDom(message); this._considerCacheDom(index); }, updateMessageDom: function (message) { var msgNode = message.element; if (!msgNode) { return; } var classAction = message.isPlaceholderData ? 'add' : 'remove'; var defaultDataClass = this.msgVScroll.vScroll.itemDefaultDataClass; msgNode.classList[classAction](defaultDataClass); msgNode.dataset.id = message.id; var dateNode = msgNode.querySelector('.msg-header-date'); var subjectNode = msgNode.querySelector('.msg-header-subject'); var snippetNode = msgNode.querySelector('.msg-header-snippet'); var listPerson; if (this.isIncomingFolder) { listPerson = message.author; } else if (message.to && message.to.length) { listPerson = message.to[0]; } else if (message.cc && message.cc.length) { listPerson = message.cc[0]; } else if (message.bcc && message.bcc.length) { listPerson = message.bcc[0]; } else { listPerson = message.author; } listPerson.element = msgNode.querySelector('.msg-header-author'); listPerson.onchange = updatePeepDom; listPerson.onchange(listPerson); var dateTime = dateNode.dataset.time = message.date.valueOf(); date.relativeDateElement(dateNode, dateTime); messageDisplay.subject(msgNode.querySelector('.msg-header-subject'), message); var attachmentsNode = msgNode.querySelector('.msg-header-attachments'); attachmentsNode.classList.toggle('msg-header-attachments-yes', message.hasAttachments); snippetNode.classList.toggle('icon-short', message.hasAttachments); snippetNode.textContent = message.snippet; msgNode.classList.toggle('unread', !message.isRead); var starNode = msgNode.querySelector('.msg-header-star'); starNode.classList.toggle('msg-header-star-starred', message.isStarred); subjectNode.classList.toggle('icon-short', message.isStarred); var syncNode = msgNode.querySelector('.msg-header-syncing-section'); var sendState = message.sendStatus && message.sendStatus.state; syncNode.classList.toggle('msg-header-syncing-section-syncing', sendState === 'sending'); syncNode.classList.toggle('msg-header-syncing-section-error', sendState === 'error'); if (sendState) { mozL10n.setAttributes(syncNode, 'message-header-state-' + sendState); } else { syncNode.removeAttribute('data-l10n-id'); } this.updateDomSelectState(msgNode, message); }, onFolderPickerClosing: function () { this.headerMenuNode.classList.remove('transparent'); }, onFolderShown: function () { var model = this.model, account = model.account, foldersSlice = model.foldersSlice; if (!document.hidden && account && foldersSlice && this.curFolder) { var inboxFolder = foldersSlice.getFirstFolderWithType('inbox'); if (inboxFolder === this.curFolder) { evt.emit('inboxShown', account.id); } if (document.activeElement === this.searchTextTease) { this.onSearchButton(); } } }, onCurrentCardDocumentVisibilityChange: function () { this.onFolderShown(); }, onCardVisible: function () { if (this._whenVisible) { var fn = this._whenVisible; this._whenVisible = null; fn(); } this.msgVScroll.vScroll.nowVisible(); this.sizeLastSync(); }, toggleOutboxSyncingDisplay: function (syncing) { if (syncing === this._outboxSyncing) { return; } this._outboxSyncing = syncing; var i; var items = this.msgVScroll.getElementsByClassName('msg-header-syncing-section'); if (syncing) { for (i = 0; i < items.length; i++) { items[i].classList.add('msg-header-syncing-section-syncing'); items[i].classList.remove('msg-header-syncing-section-error'); } this.editBtn.disabled = true; } else { this.editBtn.disabled = this.msgVScroll.isEmpty(); for (i = 0; i < items.length; i++) { items[i].classList.remove('msg-header-syncing-section-syncing'); } } this.setRefreshState(syncing); }, onRefresh: function () { var headerCursor = this.headerCursor; if (!headerCursor.messagesSlice) { return; } if (this.curFolder.type === 'outbox') { this.toggleOutboxSyncingDisplay(true); } else { switch (headerCursor.messagesSlice.status) { case 'new': case 'synchronizing': break; case 'synced': this._manuallyTriggeredSync = true; headerCursor.messagesSlice.refresh(); break; case 'syncfailed': if (headerCursor.messagesSlice.items.length) { headerCursor.messagesSlice.refresh(); } else { this.showFolder(this.curFolder, true); } break; } } this.model.api.sendOutboxMessages(this.model.account); }, _folderChanged: function (folder) { if (!this.model.foldersSlice) { return; } var model = this.model; var inboxFolder = model.foldersSlice.getFirstFolderWithType('inbox'); this.cacheableFolderId = model.account === model.acctsSlice.defaultAccount ? inboxFolder.id : null; if (this.showFolder(folder)) { this._hideSearchBoxByScrolling(); } }, die: function () { this.msgVScroll.die(); evt.removeListener('folderPickerClosing', this.onFolderPickerClosing); if (this.headerCursor) { this.headerCursor.die(); } var model = this.model; model.removeListener('folder', this._folderChanged); model.removeListener('newInboxMessages', this.onNewMail); model.removeListener('foldersSliceOnChange', this.onFoldersSliceChange); } } ]; });