VulpesOS/assets/webapps/email.gaiamobile.org/js/ext/main-frame-setup.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

3877 lines
No EOL
146 KiB
JavaScript

define('ext/ext/equal', ['require'], function (require) {
var COMPARE_DEPTH = 6;
function boundedCmpObjs(a, b, depthLeft) {
var aAttrCount = 0, bAttrCount = 0, key, nextDepth = depthLeft - 1;
if ('toJSON' in a)
a = a.toJSON();
if ('toJSON' in b)
b = b.toJSON();
for (key in a) {
aAttrCount++;
if (!(key in b))
return false;
if (depthLeft) {
if (!equal(a[key], b[key], nextDepth))
return false;
} else {
if (a[key] !== b[key])
return false;
}
}
for (key in b) {
bAttrCount++;
}
if (aAttrCount !== bAttrCount)
return false;
return true;
}
function equal(a, b, depthLeft) {
if (depthLeft === undefined) {
depthLeft = COMPARE_DEPTH;
}
var ta = typeof a, tb = typeof b;
if (ta !== 'object' || tb !== ta || a == null || b == null)
return a === b;
if (a === b)
return true;
if (Array.isArray(a)) {
if (!Array.isArray(b))
return false;
if (a.length !== b.length)
return false;
for (var iArr = 0; iArr < a.length; iArr++) {
if (!equal(a[iArr], b[iArr], depthLeft - 1))
return false;
}
return true;
}
return boundedCmpObjs(a, b, depthLeft);
}
return equal;
});
;
define('ext/logic', [
'require',
'evt',
'./ext/equal'
], function (require) {
var evt = require('evt');
var equal = require('./ext/equal');
function logic() {
return logic.event.apply(logic, arguments);
}
evt.mix(logic);
logic.scope = function (namespace, defaultDetails) {
return new Scope(namespace, defaultDetails);
};
var objectToScope = new WeakMap();
function toScope(scope) {
if (!(scope instanceof Scope)) {
scope = objectToScope.get(scope);
if (!scope) {
throw new Error('Invalid scope ' + scope + ' passed to logic.event(); ' + 'did you remember to call logic.defineScope()? ' + new Error().stack);
}
}
return scope;
}
logic.defineScope = function (obj, namespace, defaultDetails) {
if (!namespace && obj && obj.constructor && obj.constructor.name) {
namespace = obj.constructor.name;
}
var scope = new Scope(namespace, defaultDetails);
objectToScope.set(obj, scope);
return scope;
};
logic.subscope = function (scope, defaultDetails) {
scope = toScope(scope);
return new Scope(scope.namespace, into(shallowClone(scope.defaultDetails), shallowClone(defaultDetails)));
};
logic.event = function (scope, type, details) {
scope = toScope(scope);
var isDefaultPrevented = false;
var preprocessEvent = {
scope: scope,
namespace: scope.namespace,
type: type,
details: details,
preventDefault: function () {
isDefaultPrevented = true;
}
};
logic.emit('preprocessEvent', preprocessEvent);
if (isDefaultPrevented) {
return { id: 0 };
}
type = preprocessEvent.type;
details = preprocessEvent.details;
if (typeof type !== 'string') {
throw new Error('Invalid "type" passed to logic.event(); ' + 'expected a string, got "' + type + '"');
}
if (scope.defaultDetails) {
if (isPlainObject(details)) {
details = into(shallowClone(scope.defaultDetails), shallowClone(details));
} else {
details = shallowClone(scope.defaultDetails);
}
} else {
details = shallowClone(details);
}
var event = new LogicEvent(scope, type, details);
logic.emit('censorEvent', event);
logic.emit('event', event);
if (logic.realtimeLogEverything) {
dump('logic: ' + event.toString() + '\n');
}
return event;
};
logic.underTest = false;
logic._currentTestRejectFunction = null;
logic.fail = function (ex) {
if (logic.underTest) {
if (logic._currentTestRejectFunction) {
logic._currentTestRejectFunction(ex);
} else {
throw ex;
}
} else {
console.error('Logic fail:', ex);
}
};
var nextId = 1;
logic.uniqueId = function () {
return nextId++;
};
logic.isCensored = false;
logic.realtimeLogEverything = false;
var interceptions = {};
logic.interceptable = function (type, fn) {
if (interceptions[type]) {
return interceptions[type]();
} else {
return fn();
}
};
logic.interceptOnce = function (type, replacementFn) {
var prevFn = interceptions[type];
interceptions[type] = function () {
interceptions[type] = prevFn;
return replacementFn();
};
};
logic.match = function (ns, type, detailPredicate) {
return new LogicMatcher(LogicMatcher.normalizeMatchArgs(ns, type, detailPredicate));
};
function MismatchError(matcher, event) {
this.matcher = matcher;
this.event = event;
}
MismatchError.prototype = Object.create(Error.prototype, {
constructor: { value: MismatchError },
toString: {
value: function () {
if (this.matcher.not) {
return 'MismatchError: expected ' + this.event + ' to not occur (failIfMatched ' + this.matcher + ').';
} else {
return 'MismatchError: expected ' + this.event + ' to match ' + JSON.stringify(this.matcher.detailPredicate) + '.';
}
}
}
});
function LogicMatcher(opts) {
this.matchedLogs = opts.prevMatcher ? opts.prevMatcher.matchedLogs : [];
this.capturedLogs = [];
this.ns = opts.ns;
this.type = opts.type;
this.detailPredicate = opts.detailPredicate;
this.failOnMismatchedDetails = true;
this.not = opts.not;
this.timeoutMS = 2000;
this.resolved = false;
this.anotherMatcherNeedsMyLogs = false;
if (opts.prevMatcher) {
opts.prevMatcher.anotherMatcherNeedsMyLogs = true;
}
logic.defineScope(this, 'LogicMatcher');
var hasPrevPromise = !!opts.prevPromise;
var normalizedPrevPromise = opts.prevPromise || Promise.resolve();
if (this.not) {
this.promise = normalizedPrevPromise.then(() => {
this.capturedLogs.some(event => {
if ((!this.ns || event.namespace === this.ns) && event.matches(this.type, this.detailPredicate)) {
throw new MismatchError(this, event);
}
});
});
} else if (this.type) {
this.promise = new Promise((resolve, reject) => {
var subscribeToNextMatch = () => {
var timeoutId = setTimeout(() => {
logic(this, 'failedMatch', {
ns: this.ns,
type: this.type,
detailPredicate: this.detailPredicate,
capturedLogs: this.capturedLogs
});
reject(new Error('LogicMatcherTimeout: ' + this));
}, this.timeoutMS);
var resolveThisMatcher = event => {
this.resolved = true;
this.capturedLogs = [];
if (!this.anotherMatcherNeedsMyLogs) {
this.removeMatchListener();
}
};
var matchFn = event => {
this.capturedLogs.push(event);
if (this.resolved) {
return;
}
if (this.ns && event.namespace !== this.ns || event.type !== this.type) {
return false;
}
if (event.matches(this.type, this.detailPredicate)) {
resolveThisMatcher(event);
this.matchedLogs.push(event);
clearTimeout(timeoutId);
logic(this, 'match', {
ns: this.ns,
type: this.type,
event: event
});
resolve(event);
return true;
} else {
if (this.failOnMismatchedDetails) {
resolveThisMatcher(event);
reject(new MismatchError(this, event));
return true;
} else {
}
}
return false;
};
this.removeMatchListener = () => {
logic.removeListener('event', matchFn);
};
logic.on('event', matchFn);
if (opts.prevMatcher) {
var prevLogs = opts.prevMatcher.capturedLogs;
var matchIndex = prevLogs.findIndex(matchFn);
if (matchIndex !== -1) {
this.capturedLogs = prevLogs.slice(matchIndex + 1);
}
opts.prevMatcher.removeMatchListener();
}
};
if (hasPrevPromise) {
normalizedPrevPromise.then(subscribeToNextMatch, e => reject(e));
} else {
try {
subscribeToNextMatch();
} catch (e) {
reject(e);
}
}
});
} else {
this.promise = normalizedPrevPromise;
}
}
LogicMatcher.normalizeMatchArgs = function (ns, type, details) {
if (typeof type === 'object') {
details = type;
type = ns;
ns = null;
}
return {
ns: ns,
type: type,
detailPredicate: details
};
};
LogicMatcher.prototype = {
match(ns, type, details) {
var args = LogicMatcher.normalizeMatchArgs(ns, type, details);
args.prevMatcher = this;
args.prevPromise = this.promise;
return new LogicMatcher(args);
},
failIfMatched(ns, type, details) {
var args = LogicMatcher.normalizeMatchArgs(ns, type, details);
args.not = true;
args.prevMatcher = this;
args.prevPromise = this.promise;
return new LogicMatcher(args);
},
then(fn, catchFn) {
return new LogicMatcher({
prevPromise: this.promise.then(() => {
var ret = fn(this.matchedLogs.slice());
if (ret instanceof Promise) {
ret = new LogicMatcher({ prevPromise: ret });
}
return ret;
}, catchFn)
});
},
toString() {
return '<LogicMatcher ' + (this.ns ? this.ns + '/' : '') + this.type + ' ' + new ObjectSimplifier().simplify(this.detailPredicate) + '>';
}
};
function Scope(namespace, defaultDetails) {
this.namespace = namespace;
if (defaultDetails && !isPlainObject(defaultDetails)) {
throw new Error('Invalid defaultDetails; expected a plain-old object: ' + defaultDetails);
}
this.defaultDetails = defaultDetails;
}
function ObjectSimplifier(opts) {
opts = opts || {};
this.maxDepth = opts.maxDepth || 10;
this.maxStringLength = opts.maxStringLength || 1000;
this.maxArrayLength = opts.maxArrayLength || 1000;
this.maxObjectLength = opts.maxObjectLength || 10;
}
ObjectSimplifier.prototype = {
simplify: function (x) {
return this._simplify(x, 0, new WeakSet());
},
_simplify: function (x, depth, cacheSet) {
if (cacheSet.has(x)) {
return '(cycle)';
}
if (typeof x === 'number') {
return x;
} else if (typeof x === 'string') {
return x.slice(0, this.maxStringLength);
} else if (x && x.BYTES_PER_ELEMENT) {
return x.slice(0, this.maxArrayLength);
} else if (Array.isArray(x)) {
if (depth < this.maxDepth) {
return x.slice(0, this.maxArrayLength).map(element => this._simplify(element, depth + 1, cacheSet));
} else {
return '[Array length=' + x.length + ']';
}
} else if (x && typeof x === 'object') {
cacheSet.add(x);
if (!isPlainObject(x)) {
if (x.toJSON) {
return this._simplify(x.toJSON(), depth, cacheSet);
} else if (x.toString) {
return this._simplify(x.toString(), depth, cacheSet);
} else {
return '(?)';
}
} else {
if (depth < this.maxDepth) {
var retObj = {};
var idx = 0;
for (var key in x) {
if (idx > this.maxObjectLength) {
break;
}
retObj[key] = this._simplify(x[key], depth + 1, cacheSet);
idx++;
}
return retObj;
} else if (x.toString) {
return this._simplify(x.toString(), depth, cacheSet);
} else {
return '(object?)';
}
}
} else if (typeof x === 'function') {
return '(function)';
} else {
return x;
}
}
};
function LogicEvent(scope, type, details) {
if (!(scope instanceof Scope)) {
throw new Error('Invalid "scope" passed to LogicEvent(); ' + 'did you remember to call logic.defineScope()?');
}
this.scope = scope;
this.type = type;
this.details = details;
this.time = Date.now();
this.id = logic.uniqueId();
this.jsonRepresentation = {
namespace: this.scope.namespace,
type: this.type,
details: new ObjectSimplifier().simplify(this.details),
time: this.time,
id: this.id
};
}
LogicEvent.fromJSON = function (data) {
var event = new LogicEvent(new Scope(data.namespace), data.type, data.details);
event.time = data.time;
event.id = data.id;
return event;
};
LogicEvent.prototype = {
get namespace() {
return this.scope.namespace;
},
toJSON: function () {
return this.jsonRepresentation;
},
toString: function () {
return '<LogicEvent ' + this.namespace + '/' + this.type + ' ' + JSON.stringify(this.jsonRepresentation.details) + '>';
},
matches: function (type, detailPredicate) {
if (this.type !== type) {
return false;
}
if (typeof detailPredicate === 'function') {
return !!detailPredicate(this.details);
} else if (isPlainObject(detailPredicate)) {
for (var key in detailPredicate) {
var expected = detailPredicate && detailPredicate[key];
var actual = this.details && this.details[key];
if (actual === undefined) {
actual = null;
}
if (expected === undefined) {
continue;
} else if (!this.details || !equal(expected, actual)) {
return false;
}
}
return true;
} else if (detailPredicate != null) {
return equal(this.details, detailPredicate);
} else {
return true;
}
}
};
function isPlainObject(obj) {
if (!obj || typeof obj !== 'object') {
return false;
}
if (obj.toString && obj.toString() !== '[object Object]') {
return false;
}
for (var k in obj) {
if (typeof k === 'function') {
return false;
}
}
return true;
}
logic.isPlainObject = isPlainObject;
var promiseToStartEventMap = new WeakMap();
var promiseToResultEventMap = new WeakMap();
logic.startAsync = function (scope, type, details) {
var resolve, reject;
var promise = logic.async(scope, type, details, (_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
return {
resolve: resolve,
reject: reject
};
};
logic.async = function (scope, type, details, fn) {
if (!fn && typeof details === 'function') {
fn = details;
details = null;
}
scope = logic.subscope(scope, details);
var startEvent;
var promise = new Promise((resolve, reject) => {
startEvent = logic(scope, 'begin ' + type, {
asyncStatus: 0,
asyncName: type
});
fn(result => {
promiseToResultEventMap.set(promise, logic(scope, type, {
asyncStatus: 1,
sourceEventIds: [startEvent.id],
result: result
}));
resolve(result);
}, error => {
promiseToResultEventMap.set(promise, logic(scope, type, {
asyncStatus: 2,
sourceEventIds: [startEvent.id],
error: error
}));
reject(error);
});
});
promiseToStartEventMap.set(promise, startEvent);
return promise;
};
logic.await = function (scope, type, details, promise) {
if (!promise && details.then) {
promise = details;
details = null;
}
scope = logic.subscope(scope, details).subscope(scope);
var startEvent = promiseToStartEventMap.get(promise);
var awaitEvent = logic.event(scope, 'await ' + type, {
awaitStatus: 0,
sourceEventIds: startEvent ? [startEvent.id] : null,
awaitName: type
});
return promise.then(result => {
var resultEvent = promiseToResultEventMap.get(promise);
logic(scope, type, {
awaitStatus: 1,
result: result,
sourceEventIds: resultEvent ? [
resultEvent.id,
awaitEvent.id
] : [awaitEvent.id]
});
return result;
}, error => {
var resultEvent = promiseToResultEventMap.get(promise);
logic(scope, type, {
awaitStatus: 2,
error: error,
sourceEventIds: resultEvent ? [
resultEvent.id,
awaitEvent.id
] : [awaitEvent.id]
});
throw error;
});
};
function shallowClone(x) {
if (isPlainObject(x)) {
var ret = {};
for (var key in x) {
ret[key] = x[key];
}
return ret;
} else {
return x;
}
}
function into(target, source) {
if (!target) {
target = {};
}
for (var key in source) {
target[key] = source[key];
}
return target;
}
return logic;
});
(function (root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define('ext/ext/addressparser', factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.addressparser = factory();
}
}(this, function () {
'use strict';
var addressparser = {};
addressparser.parse = function (str) {
var tokenizer = new addressparser.Tokenizer(str), tokens = tokenizer.tokenize();
var addresses = [], address = [], parsedAddresses = [];
tokens.forEach(function (token) {
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
if (address.length) {
addresses.push(address);
}
address = [];
} else {
address.push(token);
}
});
if (address.length) {
addresses.push(address);
}
addresses.forEach(function (address) {
address = addressparser._handleAddress(address);
if (address.length) {
parsedAddresses = parsedAddresses.concat(address);
}
});
return parsedAddresses;
};
addressparser._handleAddress = function (tokens) {
var token, isGroup = false, state = 'text', address, addresses = [], data = {
address: [],
comment: [],
group: [],
text: []
}, i, len;
for (i = 0, len = tokens.length; i < len; i++) {
token = tokens[i];
if (token.type === 'operator') {
switch (token.value) {
case '<':
state = 'address';
break;
case '(':
state = 'comment';
break;
case ':':
state = 'group';
isGroup = true;
break;
default:
state = 'text';
}
} else {
if (token.value) {
data[state].push(token.value);
}
}
}
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
if (isGroup) {
data.text = data.text.join(' ');
addresses.push({
name: data.text || address && address.name,
group: data.group.length ? addressparser.parse(data.group.join(',')) : []
});
} else {
if (!data.address.length && data.text.length) {
for (i = data.text.length - 1; i >= 0; i--) {
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
data.address = data.text.splice(i, 1);
break;
}
}
var _regexHandler = function (address) {
if (!data.address.length) {
data.address = [address.trim()];
return ' ';
} else {
return address;
}
};
if (!data.address.length) {
for (i = data.text.length - 1; i >= 0; i--) {
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^@\s]+\b\s*/, _regexHandler).trim();
if (data.address.length) {
break;
}
}
}
}
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
if (data.address.length > 1) {
data.text = data.text.concat(data.address.splice(1));
}
data.text = data.text.join(' ');
data.address = data.address.join(' ');
if (!data.address && isGroup) {
return [];
} else {
address = {
address: data.address || data.text || '',
name: data.text || data.address || ''
};
if (address.address === address.name) {
if ((address.address || '').match(/@/)) {
address.name = '';
} else {
address.address = '';
}
}
addresses.push(address);
}
}
return addresses;
};
addressparser.Tokenizer = function (str) {
this.str = (str || '').toString();
this.operatorCurrent = '';
this.operatorExpecting = '';
this.node = null;
this.escaped = false;
this.list = [];
};
addressparser.Tokenizer.prototype.operators = {
'"': '"',
'(': ')',
'<': '>',
',': '',
':': ';',
';': ''
};
addressparser.Tokenizer.prototype.tokenize = function () {
var chr, list = [];
for (var i = 0, len = this.str.length; i < len; i++) {
chr = this.str.charAt(i);
this.checkChar(chr);
}
this.list.forEach(function (node) {
node.value = (node.value || '').toString().trim();
if (node.value) {
list.push(node);
}
});
return list;
};
addressparser.Tokenizer.prototype.checkChar = function (chr) {
if ((chr in this.operators || chr === '\\') && this.escaped) {
this.escaped = false;
} else if (this.operatorExpecting && chr === this.operatorExpecting) {
this.node = {
type: 'operator',
value: chr
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = '';
this.escaped = false;
return;
} else if (!this.operatorExpecting && chr in this.operators) {
this.node = {
type: 'operator',
value: chr
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = this.operators[chr];
this.escaped = false;
return;
}
if (!this.escaped && chr === '\\') {
this.escaped = true;
return;
}
if (!this.node) {
this.node = {
type: 'text',
value: ''
};
this.list.push(this.node);
}
if (this.escaped && chr !== '\\') {
this.node.value += '\\';
}
this.node.value += chr;
this.escaped = false;
};
return addressparser;
}));
define('ext/mailapi', [
'exports',
'./logic',
'./ext/addressparser'
], function (exports, logic, addressparser) {
function objCopy(obj) {
var copy = {};
Object.keys(obj).forEach(function (key) {
copy[key] = obj[key];
});
return copy;
}
function reportError() {
var msg = null;
for (var i = 0; i < arguments.length; i++) {
if (msg)
msg += ' ' + arguments[i];
else
msg = '' + arguments[i];
}
logic.fail(new Error(msg));
}
var unexpectedBridgeDataError = reportError, internalError = reportError, reportClientCodeError = reportError;
var HEADER_CACHE_LIMIT = 8;
function MailAccount(api, wireRep, acctsSlice) {
this._api = api;
this.id = wireRep.id;
this._wireRep = wireRep;
this.acctsSlice = acctsSlice;
this.type = wireRep.type;
this.name = wireRep.name;
this.syncRange = wireRep.syncRange;
this.syncInterval = wireRep.syncInterval;
this.notifyOnNew = wireRep.notifyOnNew;
this.playSoundOnSend = wireRep.playSoundOnSend;
this.enabled = wireRep.enabled;
this.problems = wireRep.problems;
this.identities = [];
for (var iIdent = 0; iIdent < wireRep.identities.length; iIdent++) {
this.identities.push(new MailSenderIdentity(this._api, wireRep.identities[iIdent]));
}
this.username = wireRep.credentials.username;
this.servers = wireRep.servers;
this.authMechanism = wireRep.credentials.oauth2 ? 'oauth2' : 'password';
this.element = null;
this.data = null;
}
MailAccount.prototype = {
toString: function () {
return '[MailAccount: ' + this.type + ' ' + this.id + ']';
},
toJSON: function () {
return {
type: 'MailAccount',
accountType: this.type,
id: this.id
};
},
__update: function (wireRep) {
this.enabled = wireRep.enabled;
this.problems = wireRep.problems;
this.syncRange = wireRep.syncRange;
this.syncInterval = wireRep.syncInterval;
this.notifyOnNew = wireRep.notifyOnNew;
this.playSoundOnSend = wireRep.playSoundOnSend;
this._wireRep.defaultPriority = wireRep.defaultPriority;
for (var i = 0; i < wireRep.identities.length; i++) {
if (this.identities[i]) {
this.identities[i].__update(wireRep.identities[i]);
} else {
this.identities.push(new MailSenderIdentity(this._api, wireRep.identities[i]));
}
}
},
__die: function () {
},
clearProblems: function (callback) {
this._api._clearAccountProblems(this, callback);
},
modifyAccount: function (mods, callback) {
this._api._modifyAccount(this, mods, callback);
},
deleteAccount: function () {
this._api._deleteAccount(this);
},
get isDefault() {
if (!this.acctsSlice)
throw new Error('No account slice available');
return this.acctsSlice.defaultAccount === this;
}
};
function MailSenderIdentity(api, wireRep) {
this._api = api;
this.id = wireRep.id;
this.name = wireRep.name;
this.address = wireRep.address;
this.replyTo = wireRep.replyTo;
this.signature = wireRep.signature;
this.signatureEnabled = wireRep.signatureEnabled;
}
MailSenderIdentity.prototype = {
toString: function () {
return '[MailSenderIdentity: ' + this.type + ' ' + this.id + ']';
},
toJSON: function () {
return { type: 'MailSenderIdentity' };
},
__update: function (wireRep) {
this.id = wireRep.id;
this.name = wireRep.name;
this.address = wireRep.address;
this.replyTo = wireRep.replyTo;
this.signature = wireRep.signature;
this.signatureEnabled = wireRep.signatureEnabled;
},
modifyIdentity: function (mods, callback) {
if (typeof mods.signature !== 'undefined') {
this.signature = mods.signature;
}
if (typeof mods.signatureEnabled !== 'undefined') {
this.signatureEnabled = mods.signatureEnabled;
}
this._api._modifyIdentity(this, mods, callback);
},
__die: function () {
}
};
exports._MailFolder = MailFolder;
function MailFolder(api, wireRep) {
this._api = api;
this.__update(wireRep);
this.onchange = null;
this.onremove = null;
this.element = null;
this.data = null;
}
MailFolder.prototype = {
toString: function () {
return '[MailFolder: ' + this.path + ']';
},
toJSON: function () {
return {
type: this.type,
path: this.path
};
},
__update: function (wireRep) {
this._wireRep = wireRep;
this.unread = wireRep.unreadCount;
this.lastSyncedAt = wireRep.lastSyncedAt ? new Date(wireRep.lastSyncedAt) : null;
this.path = wireRep.path;
this.id = wireRep.id;
this.name = wireRep.name;
this.path = wireRep.path;
this.depth = wireRep.depth;
this.type = wireRep.type;
this.name = this._api.l10n_folder_name(this.name, this.type);
this.selectable = wireRep.type !== 'account' && wireRep.type !== 'nomail';
this.neededForHierarchy = !this.selectable;
switch (this.type) {
case 'localdrafts':
case 'outbox':
case 'account':
case 'nomail':
this.isValidMoveTarget = false;
break;
default:
this.isValidMoveTarget = true;
}
},
__die: function () {
}
};
function filterOutBuiltinFlags(flags) {
var outFlags = [];
for (var i = flags.length - 1; i >= 0; i--) {
if (flags[i][0] !== '\\')
outFlags.push(flags[i]);
}
return outFlags;
}
function serializeMessageName(x) {
return {
date: x.date.valueOf(),
suid: x.id,
guid: x.guid
};
}
var ContactCache = exports.ContactCache = {
_contactCache: Object.create(null),
_cacheHitEntries: 0,
_cacheEmptyEntries: 0,
MAX_CACHE_HITS: 256,
MAX_CACHE_EMPTY: 1024,
_livePeepsById: Object.create(null),
_livePeepsByEmail: Object.create(null),
pendingLookupCount: 0,
callbacks: [],
init: function () {
var contactsAPI = navigator.mozContacts;
if (!contactsAPI)
return;
contactsAPI.oncontactchange = this._onContactChange.bind(this);
},
_resetCache: function () {
this._contactCache = Object.create(null);
this._cacheHitEntries = 0;
this._cacheEmptyEntries = 0;
},
shutdown: function () {
var contactsAPI = navigator.mozContacts;
if (!contactsAPI)
return;
contactsAPI.oncontactchange = null;
},
_onContactChange: function (event) {
var contactsAPI = navigator.mozContacts;
var livePeepsById = this._livePeepsById, livePeepsByEmail = this._livePeepsByEmail;
if (this._cacheHitEntries || this._cacheEmptyEntries)
this._resetCache();
if (event.reason === 'remove') {
function cleanOutPeeps(livePeeps) {
for (var iPeep = 0; iPeep < livePeeps.length; iPeep++) {
var peep = livePeeps[iPeep];
peep.contactId = null;
if (peep.onchange) {
try {
peep.onchange(peep);
} catch (ex) {
reportClientCodeError('peep.onchange error', ex, '\n', ex.stack);
}
}
}
}
var livePeeps;
if (!event.contactID) {
for (var contactId in livePeepsById) {
livePeeps = livePeepsById[contactId];
cleanOutPeeps(livePeeps);
this._livePeepsById = Object.create(null);
}
} else {
livePeeps = livePeepsById[event.contactID];
if (livePeeps) {
cleanOutPeeps(livePeeps);
delete livePeepsById[event.contactID];
}
}
} else {
var req = contactsAPI.find({
filterBy: ['id'],
filterOp: 'equals',
filterValue: event.contactID
});
req.onsuccess = function () {
if (!req.result.length)
return;
var contact = req.result[0], livePeeps, iPeep, peep;
if (event.reason === 'update') {
livePeeps = livePeepsById[contact.id];
if (livePeeps) {
var contactEmails = contact.email ? contact.email.map(function (e) {
return e.value;
}) : [];
for (iPeep = 0; iPeep < livePeeps.length; iPeep++) {
peep = livePeeps[iPeep];
if (contactEmails.indexOf(peep.address) === -1) {
livePeeps.splice(iPeep--, 1);
peep.contactId = null;
if (peep.onchange) {
try {
peep.onchange(peep);
} catch (ex) {
reportClientCodeError('peep.onchange error', ex, '\n', ex.stack);
}
}
}
}
if (livePeeps.length === 0)
delete livePeepsById[contact.id];
}
}
if (!contact.email)
return;
for (var iEmail = 0; iEmail < contact.email.length; iEmail++) {
var email = contact.email[iEmail].value;
livePeeps = livePeepsByEmail[email];
if (!livePeeps)
continue;
for (iPeep = 0; iPeep < livePeeps.length; iPeep++) {
peep = livePeeps[iPeep];
if (!peep.contactId) {
peep.contactId = contact.id;
var idLivePeeps = livePeepsById[peep.contactId];
if (idLivePeeps === undefined)
idLivePeeps = livePeepsById[peep.contactId] = [];
idLivePeeps.push(peep);
} else if (peep.contactId !== contact.id) {
continue;
}
if (contact.name && contact.name.length)
peep.name = contact.name[0];
if (peep.onchange) {
try {
peep.onchange(peep);
} catch (ex) {
reportClientCodeError('peep.onchange error', ex, '\n', ex.stack);
}
}
}
}
};
}
},
resolvePeeps: function (addressPairs) {
if (addressPairs == null)
return null;
var resolved = [];
for (var i = 0; i < addressPairs.length; i++) {
resolved.push(this.resolvePeep(addressPairs[i]));
}
return resolved;
},
resolvePeep: function (addressPair) {
if (!addressPair) {
console.error('NO ADDRESS PAIR?', new Error().stack);
return;
}
var emailAddress = addressPair.address;
var entry = this._contactCache[emailAddress], contact, peep;
var contactsAPI = navigator.mozContacts;
if (entry === null || !contactsAPI) {
peep = new MailPeep(addressPair.name || '', emailAddress, null, null);
if (!contactsAPI)
return peep;
} else if (entry !== undefined) {
var name = addressPair.name || '';
if (entry.name && entry.name.length)
name = entry.name[0];
peep = new MailPeep(name, emailAddress, entry.id, entry.photo && entry.photo.length ? entry.photo[0] : null);
} else {
peep = new MailPeep(addressPair.name || '', emailAddress, null, null);
this._contactCache[emailAddress] = null;
this.pendingLookupCount++;
var filterValue = emailAddress ? emailAddress.toLowerCase() : '';
var req = contactsAPI.find({
filterBy: ['email'],
filterOp: 'equals',
filterValue: filterValue
});
var self = this, handleResult = function () {
if (req.result && req.result.length) {
var contact = req.result[0];
ContactCache._contactCache[emailAddress] = contact;
if (++ContactCache._cacheHitEntries > ContactCache.MAX_CACHE_HITS)
self._resetCache();
var peepsToFixup = self._livePeepsByEmail[emailAddress];
if (!peepsToFixup)
return;
for (var i = 0; i < peepsToFixup.length; i++) {
var peep = peepsToFixup[i];
if (!peep.contactId) {
peep.contactId = contact.id;
var livePeeps = self._livePeepsById[peep.contactId];
if (livePeeps === undefined)
livePeeps = self._livePeepsById[peep.contactId] = [];
livePeeps.push(peep);
}
if (contact.name && contact.name.length)
peep.name = contact.name[0];
if (contact.photo && contact.photo.length)
peep._thumbnailBlob = contact.photo[0];
if (!self.callbacks.length) {
if (peep.onchange) {
try {
peep.onchange(peep);
} catch (ex) {
reportClientCodeError('peep.onchange error', ex, '\n', ex.stack);
}
}
}
}
} else {
ContactCache._contactCache[emailAddress] = null;
if (++ContactCache._cacheEmptyEntries > ContactCache.MAX_CACHE_EMPTY)
self._resetCache();
}
if (--self.pendingLookupCount === 0) {
for (i = 0; i < ContactCache.callbacks.length; i++) {
ContactCache.callbacks[i]();
}
ContactCache.callbacks.splice(0, ContactCache.callbacks.length);
}
};
req.onsuccess = handleResult;
req.onerror = handleResult;
}
var livePeeps;
livePeeps = this._livePeepsByEmail[emailAddress];
if (livePeeps === undefined)
livePeeps = this._livePeepsByEmail[emailAddress] = [];
livePeeps.push(peep);
if (peep.contactId) {
livePeeps = this._livePeepsById[peep.contactId];
if (livePeeps === undefined)
livePeeps = this._livePeepsById[peep.contactId] = [];
livePeeps.push(peep);
}
return peep;
},
forgetPeepInstances: function () {
var livePeepsById = this._livePeepsById, livePeepsByEmail = this._livePeepsByEmail;
for (var iArg = 0; iArg < arguments.length; iArg++) {
var peeps = arguments[iArg];
if (!peeps)
continue;
for (var iPeep = 0; iPeep < peeps.length; iPeep++) {
var peep = peeps[iPeep], livePeeps, idx;
if (peep.contactId) {
livePeeps = livePeepsById[peep.contactId];
if (livePeeps) {
idx = livePeeps.indexOf(peep);
if (idx !== -1) {
livePeeps.splice(idx, 1);
if (livePeeps.length === 0)
delete livePeepsById[peep.contactId];
}
}
}
livePeeps = livePeepsByEmail[peep.address];
if (livePeeps) {
idx = livePeeps.indexOf(peep);
if (idx !== -1) {
livePeeps.splice(idx, 1);
if (livePeeps.length === 0)
delete livePeepsByEmail[peep.address];
}
}
}
}
}
};
function revokeImageSrc() {
var useWin = this.ownerDocument.defaultView || window;
useWin.URL.revokeObjectURL(this.src);
}
function showBlobInImg(imgNode, blob) {
var useWin = imgNode.ownerDocument.defaultView || window;
imgNode.src = useWin.URL.createObjectURL(blob);
imgNode.addEventListener('load', revokeImageSrc);
}
function MailPeep(name, address, contactId, thumbnailBlob) {
this.name = name;
this.address = address;
this.contactId = contactId;
this._thumbnailBlob = thumbnailBlob;
this.element = null;
this.data = null;
this.type = null;
this.onchange = null;
}
MailPeep.prototype = {
get isContact() {
return this.contactId !== null;
},
toString: function () {
return '[MailPeep: ' + this.address + ']';
},
toJSON: function () {
return {
name: this.name,
address: this.address,
contactId: this.contactId
};
},
toWireRep: function () {
return {
name: this.name,
address: this.address
};
},
get hasPicture() {
return this._thumbnailBlob !== null;
},
displayPictureInImageTag: function (imgNode) {
if (this._thumbnailBlob)
showBlobInImg(imgNode, this._thumbnailBlob);
}
};
function MailHeader(slice, wireRep) {
this._slice = slice;
this._wireRep = wireRep;
this.id = wireRep.suid;
this.guid = wireRep.guid;
this.author = ContactCache.resolvePeep(wireRep.author);
this.to = ContactCache.resolvePeeps(wireRep.to);
this.cc = ContactCache.resolvePeeps(wireRep.cc);
this.bcc = ContactCache.resolvePeeps(wireRep.bcc);
this.replyTo = wireRep.replyTo;
this.date = new Date(wireRep.date);
this.__update(wireRep);
this.hasAttachments = wireRep.hasAttachments;
this.subject = wireRep.subject;
this.snippet = wireRep.snippet;
this.onchange = null;
this.onremove = null;
this.element = null;
this.data = null;
}
MailHeader.prototype = {
toString: function () {
return '[MailHeader: ' + this.id + ']';
},
toJSON: function () {
return {
type: 'MailHeader',
id: this.id
};
},
makeCopy: function () {
return new MailHeader(this._slice, this._wireRep);
},
__update: function (wireRep) {
this._wireRep = wireRep;
if (wireRep.snippet !== null) {
this.snippet = wireRep.snippet;
}
this.isRead = wireRep.flags.indexOf('\\Seen') !== -1;
this.isStarred = wireRep.flags.indexOf('\\Flagged') !== -1;
this.isRepliedTo = wireRep.flags.indexOf('\\Answered') !== -1;
this.isForwarded = wireRep.flags.indexOf('$Forwarded') !== -1;
this.isJunk = wireRep.flags.indexOf('$Junk') !== -1;
this.tags = filterOutBuiltinFlags(wireRep.flags);
this.sendStatus = wireRep.sendStatus || {};
},
__die: function () {
ContactCache.forgetPeepInstances([this.author], this.to, this.cc, this.bcc);
},
deleteMessage: function () {
return this._slice._api.deleteMessages([this]);
},
moveMessage: function (targetFolder) {
return this._slice._api.moveMessages([this], targetFolder);
},
setRead: function (beRead) {
return this._slice._api.markMessagesRead([this], beRead);
},
setStarred: function (beStarred) {
return this._slice._api.markMessagesStarred([this], beStarred);
},
modifyTags: function (addTags, removeTags) {
return this._slice._api.modifyMessageTags([this], addTags, removeTags);
},
getBody: function (options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
this._slice._api._getBodyForMessage(this, options, callback);
},
get bytesToDownloadForBodyDisplay() {
return this._wireRep.bytesToDownloadForBodyDisplay || 0;
},
editAsDraft: function (callback) {
var composer = this._slice._api.resumeMessageComposition(this, callback);
composer.hasDraft = true;
return composer;
},
replyToMessage: function (replyMode, callback) {
return this._slice._api.beginMessageComposition(this, null, {
replyTo: this,
replyMode: replyMode
}, callback);
},
forwardMessage: function (forwardMode, callback) {
return this._slice._api.beginMessageComposition(this, null, {
forwardOf: this,
forwardMode: forwardMode
}, callback);
}
};
function MailMatchedHeader(slice, wireRep) {
this.header = new MailHeader(slice, wireRep.header);
this.matches = wireRep.matches;
this.element = null;
this.data = null;
}
MailMatchedHeader.prototype = {
toString: function () {
return '[MailMatchedHeader: ' + this.header.id + ']';
},
toJSON: function () {
return {
type: 'MailMatchedHeader',
id: this.header.id
};
},
__update: function (wireRep) {
this.matches = wireRep.matches;
this.header.__update(wireRep.header);
},
__die: function () {
this.header.__die();
}
};
function MailBody(api, suid, wireRep, handle) {
this._api = api;
this.id = suid;
this._date = wireRep.date;
this._handle = handle;
this.attachments = null;
if (wireRep.attachments) {
this.attachments = [];
for (var iAtt = 0; iAtt < wireRep.attachments.length; iAtt++) {
this.attachments.push(new MailAttachment(this, wireRep.attachments[iAtt]));
}
}
this._relatedParts = wireRep.relatedParts;
this.bodyReps = wireRep.bodyReps;
this._references = wireRep.references;
this.onchange = null;
this.ondead = null;
}
MailBody.prototype = {
toString: function () {
return '[MailBody: ' + this.id + ']';
},
toJSON: function () {
return {
type: 'MailBody',
id: this.id
};
},
__update: function (wireRep, detail) {
this._relatedParts = wireRep.relatedParts;
this.bodyReps = wireRep.bodyReps;
if (detail && detail.changeDetails && detail.changeDetails.detachedAttachments) {
var indices = detail.changeDetails.detachedAttachments;
for (var iSplice = 0; iSplice < indices.length; iSplice++) {
this.attachments.splice(indices[iSplice], 1);
}
}
if (wireRep.attachments) {
var i, attachment;
for (i = 0; i < this.attachments.length; i++) {
attachment = this.attachments[i];
attachment.__update(wireRep.attachments[i]);
}
for (i = this.attachments.length; i < wireRep.attachments.length; i++) {
this.attachments.push(new MailAttachment(this, wireRep.attachments[i]));
}
}
},
get embeddedImageCount() {
if (!this._relatedParts)
return 0;
return this._relatedParts.length;
},
get bodyRepsDownloaded() {
var i = 0;
var len = this.bodyReps.length;
for (; i < len; i++) {
if (!this.bodyReps[i].isDownloaded) {
return false;
}
}
return true;
},
get embeddedImagesDownloaded() {
for (var i = 0; i < this._relatedParts.length; i++) {
var relatedPart = this._relatedParts[i];
if (!relatedPart.file)
return false;
}
return true;
},
downloadEmbeddedImages: function (callWhenDone, callOnProgress) {
var relPartIndices = [];
for (var i = 0; i < this._relatedParts.length; i++) {
var relatedPart = this._relatedParts[i];
if (relatedPart.file)
continue;
relPartIndices.push(i);
}
if (!relPartIndices.length) {
if (callWhenDone)
callWhenDone();
return;
}
this._api._downloadAttachments(this, relPartIndices, [], [], callWhenDone, callOnProgress);
},
showEmbeddedImages: function (htmlNode, loadCallback) {
var i, cidToBlob = {};
for (i = 0; i < this._relatedParts.length; i++) {
var relPart = this._relatedParts[i];
if (relPart.file && !Array.isArray(relPart.file))
cidToBlob[relPart.contentId] = relPart.file;
}
var nodes = htmlNode.querySelectorAll('.moz-embedded-image');
for (i = 0; i < nodes.length; i++) {
var node = nodes[i], cid = node.getAttribute('cid-src');
if (!cidToBlob.hasOwnProperty(cid))
continue;
showBlobInImg(node, cidToBlob[cid]);
if (loadCallback)
node.addEventListener('load', loadCallback, false);
node.removeAttribute('cid-src');
node.classList.remove('moz-embedded-image');
}
},
checkForExternalImages: function (htmlNode) {
var someNode = htmlNode.querySelector('.moz-external-image');
return someNode !== null;
},
showExternalImages: function (htmlNode, loadCallback) {
var nodes = htmlNode.querySelectorAll('.moz-external-image');
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (loadCallback) {
node.addEventListener('load', loadCallback, false);
}
node.setAttribute('src', node.getAttribute('ext-src'));
node.removeAttribute('ext-src');
node.classList.remove('moz-external-image');
}
},
die: function () {
this.onchange = null;
this._api.__bridgeSend({
type: 'killBody',
id: this.id,
handle: this._handle
});
}
};
function MailAttachment(_body, wireRep) {
this._body = _body;
this.partId = wireRep.part;
this.filename = wireRep.name;
this.mimetype = wireRep.type;
this.sizeEstimateInBytes = wireRep.sizeEstimate;
this._file = wireRep.file;
this.element = null;
this.data = null;
}
MailAttachment.prototype = {
toString: function () {
return '[MailAttachment: "' + this.filename + '"]';
},
toJSON: function () {
return {
type: 'MailAttachment',
filename: this.filename
};
},
__update: function (wireRep) {
this.mimetype = wireRep.type;
this.sizeEstimateInBytes = wireRep.sizeEstimate;
this._file = wireRep.file;
},
get isDownloaded() {
return !!this._file;
},
get isDownloadable() {
return this.mimetype !== 'application/x-gelam-no-download';
},
download: function (callWhenDone, callOnProgress, registerWithDownloadManager) {
if (this.isDownloaded) {
callWhenDone();
return;
}
this._body._api._downloadAttachments(this._body, [], [this._body.attachments.indexOf(this)], [registerWithDownloadManager || false], callWhenDone, callOnProgress);
}
};
function UndoableOperation(_api, operation, affectedCount, _tempHandle, _longtermIds) {
this._api = _api;
this.operation = operation;
this.affectedCount = affectedCount;
this._tempHandle = _tempHandle;
this._longtermIds = null;
this._undoRequested = false;
}
UndoableOperation.prototype = {
toString: function () {
return '[UndoableOperation]';
},
toJSON: function () {
return {
type: 'UndoableOperation',
handle: this._tempHandle,
longtermIds: this._longtermIds
};
},
undo: function () {
if (!this._longtermIds) {
this._undoRequested = true;
return;
}
this._api.__undo(this);
}
};
function BridgedViewSlice(api, ns, handle) {
this._api = api;
this._ns = ns;
this._handle = handle;
this.items = [];
this.status = 'new';
this.syncProgress = 0;
this.atTop = false;
this.atBottom = false;
this.userCanGrowUpwards = false;
this.userCanGrowDownwards = false;
this.pendingRequestCount = 0;
this._growing = 0;
this.onadd = null;
this.onchange = null;
this.onsplice = null;
this.onremove = null;
this.onstatus = null;
this.oncomplete = null;
this.ondead = null;
}
BridgedViewSlice.prototype = {
toString: function () {
return '[BridgedViewSlice: ' + this._ns + ' ' + this._handle + ']';
},
toJSON: function () {
return {
type: 'BridgedViewSlice',
namespace: this._ns,
handle: this._handle
};
},
requestShrinkage: function (firstUsedIndex, lastUsedIndex) {
this.pendingRequestCount++;
if (lastUsedIndex >= this.items.length)
lastUsedIndex = this.items.length - 1;
this._api.__bridgeSend({
type: 'shrinkSlice',
handle: this._handle,
firstIndex: firstUsedIndex,
firstSuid: this.items[firstUsedIndex].id,
lastIndex: lastUsedIndex,
lastSuid: this.items[lastUsedIndex].id
});
},
requestGrowth: function (dirMagnitude, userRequestsGrowth) {
if (this._growing) {
reportError('Already growing in ' + this._growing + ' dir.');
return;
}
this._growing = dirMagnitude;
this.pendingRequestCount++;
this._api.__bridgeSend({
type: 'growSlice',
dirMagnitude: dirMagnitude,
userRequestsGrowth: userRequestsGrowth,
handle: this._handle
});
},
die: function () {
this.onadd = null;
this.onchange = null;
this.onsplice = null;
this.onremove = null;
this.onstatus = null;
this.oncomplete = null;
this._api.__bridgeSend({
type: 'killSlice',
handle: this._handle
});
for (var i = 0; i < this.items.length; i++) {
var item = this.items[i];
item.__die();
}
}
};
function AccountsViewSlice(api, handle) {
BridgedViewSlice.call(this, api, 'accounts', handle);
}
AccountsViewSlice.prototype = Object.create(BridgedViewSlice.prototype);
AccountsViewSlice.prototype.getAccountById = function (id) {
for (var i = 0; i < this.items.length; i++) {
if (this.items[i]._wireRep.id === id) {
return this.items[i];
}
}
return null;
};
Object.defineProperty(AccountsViewSlice.prototype, 'defaultAccount', {
get: function () {
var defaultAccount = this.items[0];
for (var i = 1; i < this.items.length; i++) {
if ((this.items[i]._wireRep.defaultPriority || 0) > (defaultAccount._wireRep.defaultPriority || 0))
defaultAccount = this.items[i];
}
return defaultAccount;
}
});
function FoldersViewSlice(api, handle) {
BridgedViewSlice.call(this, api, 'folders', handle);
}
FoldersViewSlice.prototype = Object.create(BridgedViewSlice.prototype);
FoldersViewSlice.prototype.getFirstFolderWithType = function (type, items) {
if (!items)
items = this.items;
for (var i = 0; i < items.length; i++) {
var folder = items[i];
if (folder.type === type)
return folder;
}
return null;
};
FoldersViewSlice.prototype.getFirstFolderWithName = function (name, items) {
if (!items)
items = this.items;
for (var i = 0; i < items.length; i++) {
var folder = items[i];
if (folder.name === name)
return folder;
}
return null;
};
FoldersViewSlice.prototype.getFirstFolderWithPath = function (path, items) {
if (!items)
items = this.items;
for (var i = 0; i < items.length; i++) {
var folder = items[i];
if (folder.path === path)
return folder;
}
return null;
};
function HeadersViewSlice(api, handle, ns) {
BridgedViewSlice.call(this, api, ns || 'headers', handle);
this._bodiesRequestId = 1;
this._bodiesRequest = {};
}
HeadersViewSlice.prototype = Object.create(BridgedViewSlice.prototype);
HeadersViewSlice.prototype.refresh = function () {
this._api.__bridgeSend({
type: 'refreshHeaders',
handle: this._handle
});
};
HeadersViewSlice.prototype._notifyRequestBodiesComplete = function (reqId) {
var callback = this._bodiesRequest[reqId];
if (reqId && callback) {
callback(true);
delete this._bodiesRequest[reqId];
}
};
HeadersViewSlice.prototype.maybeRequestBodies = function (idxStart, idxEnd, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
var messages = [];
idxEnd = Math.min(idxEnd, this.items.length - 1);
for (; idxStart <= idxEnd; idxStart++) {
var item = this.items[idxStart];
if (this._ns === 'matchedHeaders') {
item = item.header;
}
if (item && item.snippet === null) {
messages.push({
suid: item.id,
date: item.date.valueOf()
});
}
}
if (!messages.length)
return callback && window.setZeroTimeout(callback, false);
var reqId = this._bodiesRequestId++;
this._bodiesRequest[reqId] = callback;
this._api.__bridgeSend({
type: 'requestBodies',
handle: this._handle,
requestId: reqId,
messages: messages,
options: options
});
};
function MessageComposition(api, handle) {
this._api = api;
this._handle = handle;
this.senderIdentity = null;
this.to = null;
this.cc = null;
this.bcc = null;
this.subject = null;
this.body = null;
this._references = null;
this.attachments = null;
this.hasDraft = false;
}
MessageComposition.prototype = {
toString: function () {
return '[MessageComposition: ' + this._handle + ']';
},
toJSON: function () {
return {
type: 'MessageComposition',
handle: this._handle
};
},
die: function () {
if (this._handle) {
this._api._composeDone(this._handle, 'die', null, null);
this._handle = null;
}
},
addAttachment: function (attachmentDef, callback) {
if (!this.hasDraft)
this.saveDraft();
this._api._composeAttach(this._handle, attachmentDef, callback);
var placeholderAttachment = {
name: attachmentDef.name,
blob: {
size: attachmentDef.blob.size,
type: attachmentDef.blob.type
}
};
this.attachments.push(placeholderAttachment);
return placeholderAttachment;
},
removeAttachment: function (attachmentDef, callback) {
var idx = this.attachments.indexOf(attachmentDef);
if (idx !== -1) {
this.attachments.splice(idx, 1);
this._api._composeDetach(this._handle, idx, callback);
}
},
_buildWireRep: function () {
return {
senderId: this.senderIdentity.id,
to: this.to,
cc: this.cc,
bcc: this.bcc,
subject: this.subject,
body: this.body,
referencesStr: this._references,
attachments: this.attachments
};
},
finishCompositionSendMessage: function (callback) {
this._api._composeDone(this._handle, 'send', this._buildWireRep(), callback);
},
saveDraft: function (callback) {
this.hasDraft = true;
this._api._composeDone(this._handle, 'save', this._buildWireRep(), callback);
},
abortCompositionDeleteDraft: function (callback) {
this._api._composeDone(this._handle, 'delete', null, callback);
}
};
var LEGAL_CONFIG_KEYS = [];
var RE_URL = /(^|[\s(,;])((?:https?:\/\/|www\d{0,3}[.][a-z0-9.\-]{2,249}|[a-z0-9.\-]{2,250}[.][a-z]{2,4}\/)[-\w.!~*'();,/?:@&=+$#%]*)/im;
var RE_UNEAT_LAST_URL_CHARS = /(?:[),;.!?]|[.!?]\)|\)[.!?])$/;
var RE_HTTP = /^https?:/i;
var RE_MAIL = /(^|[\s(,;<>])([^(,;<>@\s]+@[a-z0-9.\-]{2,250}[.][a-z0-9\-]{2,32})/im;
var RE_MAILTO = /^mailto:/i;
var MailUtils = {
linkifyPlain: function (body, doc) {
var nodes = [];
var match = true, contentStart;
while (true) {
var url = RE_URL.exec(body);
var email = RE_MAIL.exec(body);
if (url && (!email || url.index < email.index)) {
contentStart = url.index + url[1].length;
if (contentStart > 0)
nodes.push(doc.createTextNode(body.substring(0, contentStart)));
var useUrl = url[2];
var uneat = RE_UNEAT_LAST_URL_CHARS.exec(useUrl);
if (uneat) {
useUrl = useUrl.substring(0, uneat.index);
}
var link = doc.createElement('a');
link.className = 'moz-external-link';
if (RE_HTTP.test(url[2]))
link.setAttribute('ext-href', useUrl);
else
link.setAttribute('ext-href', 'http://' + useUrl);
var text = doc.createTextNode(useUrl);
link.appendChild(text);
nodes.push(link);
body = body.substring(url.index + url[1].length + useUrl.length);
} else if (email) {
contentStart = email.index + email[1].length;
if (contentStart > 0)
nodes.push(doc.createTextNode(body.substring(0, contentStart)));
link = doc.createElement('a');
link.className = 'moz-external-link';
if (RE_MAILTO.test(email[2]))
link.setAttribute('ext-href', email[2]);
else
link.setAttribute('ext-href', 'mailto:' + email[2]);
text = doc.createTextNode(email[2]);
link.appendChild(text);
nodes.push(link);
body = body.substring(email.index + email[0].length);
} else {
break;
}
}
if (body.length > 0)
nodes.push(doc.createTextNode(body));
return nodes;
},
linkifyHTML: function (doc) {
function linkElem(elem) {
var children = elem.childNodes;
for (var i in children) {
var sub = children[i];
if (sub.nodeName == '#text') {
var nodes = MailUtils.linkifyPlain(sub.nodeValue, doc);
elem.replaceChild(nodes[nodes.length - 1], sub);
for (var iNode = nodes.length - 2; iNode >= 0; --iNode) {
elem.insertBefore(nodes[iNode], nodes[iNode + 1]);
}
} else if (sub.nodeName != 'A') {
linkElem(sub);
}
}
}
linkElem(doc.body);
}
};
function MailAPI() {
this._nextHandle = 1;
this._slices = {};
this._pendingRequests = {};
this._liveBodies = {};
this._spliceFireFuncs = [];
this._storedSends = [];
this._processingMessage = null;
this._deferredMessages = [];
this.config = {};
this.onbadlogin = null;
ContactCache.init();
}
exports.MailAPI = MailAPI;
MailAPI.prototype = {
toString: function () {
return '[MailAPI]';
},
toJSON: function () {
return { type: 'MailAPI' };
},
utils: MailUtils,
__bridgeSend: function (msg) {
this._storedSends.push(msg);
},
__bridgeReceive: function ma___bridgeReceive(msg) {
if (this._processingMessage && msg.type !== 'pong') {
this._deferredMessages.push(msg);
} else {
this._processMessage(msg);
}
},
_processMessage: function ma__processMessage(msg) {
var methodName = '_recv_' + msg.type;
if (!(methodName in this)) {
unexpectedBridgeDataError('Unsupported message type:', msg.type);
return;
}
try {
var done = this[methodName](msg);
if (!done) {
this._processingMessage = msg;
}
} catch (ex) {
internalError('Problem handling message type:', msg.type, ex, '\n', ex.stack);
return;
}
},
_doneProcessingMessage: function (msg) {
if (this._processingMessage && this._processingMessage !== msg)
throw new Error('Mismatched message completion!');
this._processingMessage = null;
while (this._processingMessage === null && this._deferredMessages.length) {
this._processMessage(this._deferredMessages.shift());
}
},
_recv_badLogin: function ma__recv_badLogin(msg) {
if (this.onbadlogin)
this.onbadlogin(new MailAccount(this, msg.account, null), msg.problem, msg.whichSide);
return true;
},
_fireAllSplices: function () {
for (var i = 0; i < this._spliceFireFuncs.length; i++) {
var fireSpliceData = this._spliceFireFuncs[i];
fireSpliceData();
}
this._spliceFireFuncs.length = 0;
},
_recv_batchSlice: function receiveBatchSlice(msg) {
var slice = this._slices[msg.handle];
if (!slice) {
unexpectedBridgeDataError('Received message about nonexistent slice:', msg.handle);
return true;
}
var updateStatus = this._updateSliceStatus(msg, slice);
for (var i = 0; i < msg.sliceUpdates.length; i++) {
var update = msg.sliceUpdates[i];
if (update.type === 'update') {
this._spliceFireFuncs.push(this._processSliceUpdate.bind(this, msg, update.updates, slice));
} else {
this._transformAndEnqueueSingleSplice(msg, update, slice);
}
}
if (ContactCache.pendingLookupCount) {
ContactCache.callbacks.push(function contactsResolved() {
this._fireAllSplices();
this._fireStatusNotifications(updateStatus, slice);
this._doneProcessingMessage(msg);
}.bind(this));
return false;
}
this._fireAllSplices();
this._fireStatusNotifications(updateStatus, slice);
return true;
},
_fireStatusNotifications: function (updateStatus, slice) {
if (updateStatus && slice.onstatus) {
slice.onstatus(slice.status);
}
},
_updateSliceStatus: function (msg, slice) {
slice.atTop = msg.atTop;
slice.atBottom = msg.atBottom;
slice.userCanGrowUpwards = msg.userCanGrowUpwards;
slice.userCanGrowDownwards = msg.userCanGrowDownwards;
var generatedStatusChange = msg.status && (slice.status !== msg.status || slice.syncProgress !== msg.progress);
if (msg.status) {
slice.status = msg.status;
slice.syncProgress = msg.syncProgress;
}
return generatedStatusChange;
},
_processSliceUpdate: function (msg, splice, slice) {
try {
for (var i = 0; i < splice.length; i += 2) {
var idx = splice[i], wireRep = splice[i + 1], itemObj = slice.items[idx];
itemObj.__update(wireRep);
if (slice.onchange) {
slice.onchange(itemObj, idx);
}
if (itemObj.onchange) {
itemObj.onchange(itemObj, idx);
}
}
} catch (ex) {
reportClientCodeError('onchange notification error', ex, '\n', ex.stack);
}
},
_transformAndEnqueueSingleSplice: function (msg, splice, slice) {
var transformedItems = this._transform_sliceSplice(splice, slice);
var fake = false;
this._spliceFireFuncs.push(function singleSpliceUpdate() {
this._fireSplice(splice, slice, transformedItems, fake);
}.bind(this));
},
_fireSplice: function (splice, slice, transformedItems, fake) {
var i, stopIndex, items, tempMsg;
if (splice.headerCount !== undefined) {
slice.headerCount = splice.headerCount;
}
if (slice.onsplice) {
try {
slice.onsplice(splice.index, splice.howMany, transformedItems, splice.requested, splice.moreExpected, fake);
} catch (ex) {
reportClientCodeError('onsplice notification error', ex, '\n', ex.stack);
}
}
if (splice.howMany) {
try {
stopIndex = splice.index + splice.howMany;
for (i = splice.index; i < stopIndex; i++) {
var item = slice.items[i];
if (slice.onremove)
slice.onremove(item, i);
if (item.onremove)
item.onremove(item, i);
item.__die();
}
} catch (ex) {
reportClientCodeError('onremove notification error', ex, '\n', ex.stack);
}
}
slice.items.splice.apply(slice.items, [
splice.index,
splice.howMany
].concat(transformedItems));
if (slice.onadd) {
try {
stopIndex = splice.index + transformedItems.length;
for (i = splice.index; i < stopIndex; i++) {
slice.onadd(slice.items[i], i);
}
} catch (ex) {
reportClientCodeError('onadd notification error', ex, '\n', ex.stack);
}
}
if (splice.requested && !splice.moreExpected) {
slice._growing = 0;
if (slice.pendingRequestCount)
slice.pendingRequestCount--;
if (slice.oncomplete) {
var completeFunc = slice.oncomplete;
slice.oncomplete = null;
try {
completeFunc(splice.newEmailCount);
} catch (ex) {
reportClientCodeError('oncomplete notification error', ex, '\n', ex.stack);
}
}
}
},
_transform_sliceSplice: function ma__transform_sliceSplice(splice, slice) {
var addItems = splice.addItems, transformedItems = [], i;
switch (slice._ns) {
case 'accounts':
for (i = 0; i < addItems.length; i++) {
transformedItems.push(new MailAccount(this, addItems[i], slice));
}
break;
case 'identities':
for (i = 0; i < addItems.length; i++) {
transformedItems.push(new MailSenderIdentity(this, addItems[i]));
}
break;
case 'folders':
for (i = 0; i < addItems.length; i++) {
transformedItems.push(new MailFolder(this, addItems[i]));
}
break;
case 'headers':
for (i = 0; i < addItems.length; i++) {
transformedItems.push(new MailHeader(slice, addItems[i]));
}
break;
case 'matchedHeaders':
for (i = 0; i < addItems.length; i++) {
transformedItems.push(new MailMatchedHeader(slice, addItems[i]));
}
break;
default:
console.error('Slice notification for unknown type:', slice._ns);
break;
}
return transformedItems;
},
_recv_sliceDead: function (msg) {
var slice = this._slices[msg.handle];
delete this._slices[msg.handle];
if (slice.ondead)
slice.ondead(slice);
slice.ondead = null;
return true;
},
_getBodyForMessage: function (header, options, callback) {
var downloadBodyReps = false, withBodyReps = false;
if (options && options.downloadBodyReps) {
downloadBodyReps = options.downloadBodyReps;
}
if (options && options.withBodyReps) {
withBodyReps = options.withBodyReps;
}
var handle = this._nextHandle++;
this._pendingRequests[handle] = {
type: 'getBody',
suid: header.id,
callback: callback
};
this.__bridgeSend({
type: 'getBody',
handle: handle,
suid: header.id,
date: header.date.valueOf(),
downloadBodyReps: downloadBodyReps,
withBodyReps: withBodyReps
});
},
_recv_gotBody: function (msg) {
var req = this._pendingRequests[msg.handle];
if (!req) {
unexpectedBridgeDataError('Bad handle for got body:', msg.handle);
return true;
}
delete this._pendingRequests[msg.handle];
var body = msg.bodyInfo ? new MailBody(this, req.suid, msg.bodyInfo, msg.handle) : null;
if (body) {
this._liveBodies[msg.handle] = body;
}
req.callback.call(null, body, req.suid);
return true;
},
_recv_requestBodiesComplete: function (msg) {
var slice = this._slices[msg.handle];
if (slice)
slice._notifyRequestBodiesComplete(msg.requestId);
return true;
},
_recv_bodyModified: function (msg) {
var body = this._liveBodies[msg.handle];
if (!body) {
unexpectedBridgeDataError('body modified for dead handle', msg.handle);
return true;
}
var wireRep = msg.bodyInfo;
body.__update(wireRep, msg.detail);
if (body.onchange) {
body.onchange(msg.detail, body);
}
return true;
},
_recv_bodyDead: function (msg) {
var body = this._liveBodies[msg.handle];
if (body && body.ondead) {
body.ondead();
}
delete this._liveBodies[msg.handle];
return true;
},
_downloadAttachments: function (body, relPartIndices, attachmentIndices, registerAttachments, callWhenDone, callOnProgress) {
var handle = this._nextHandle++;
this._pendingRequests[handle] = {
type: 'downloadAttachments',
body: body,
relParts: relPartIndices.length > 0,
attachments: attachmentIndices.length > 0,
callback: callWhenDone,
progress: callOnProgress
};
this.__bridgeSend({
type: 'downloadAttachments',
handle: handle,
suid: body.id,
date: body._date,
relPartIndices: relPartIndices,
attachmentIndices: attachmentIndices,
registerAttachments: registerAttachments
});
},
_recv_downloadedAttachments: function (msg) {
var req = this._pendingRequests[msg.handle];
if (!req) {
unexpectedBridgeDataError('Bad handle for got body:', msg.handle);
return true;
}
delete this._pendingRequests[msg.handle];
if (req.callback)
req.callback.call(null, req.body);
return true;
},
learnAboutAccount: function (details, callback) {
var handle = this._nextHandle++;
this._pendingRequests[handle] = {
type: 'learnAboutAccount',
details: details,
callback: callback
};
this.__bridgeSend({
type: 'learnAboutAccount',
handle: handle,
details: details
});
},
_recv_learnAboutAccountResults: function (msg) {
var req = this._pendingRequests[msg.handle];
if (!req) {
unexpectedBridgeDataError('Bad handle:', msg.handle);
return true;
}
delete this._pendingRequests[msg.handle];
req.callback.call(null, msg.data);
return true;
},
tryToCreateAccount: function ma_tryToCreateAccount(details, domainInfo, callback) {
var handle = this._nextHandle++;
this._pendingRequests[handle] = {
type: 'tryToCreateAccount',
details: details,
domainInfo: domainInfo,
callback: callback
};
this.__bridgeSend({
type: 'tryToCreateAccount',
handle: handle,
details: details,
domainInfo: domainInfo
});
},
_recv_tryToCreateAccountResults: function ma__recv_tryToCreateAccountResults(msg) {
var req = this._pendingRequests[msg.handle];
if (!req) {
unexpectedBridgeDataError('Bad handle for create account:', msg.handle);
return true;
}
delete this._pendingRequests[msg.handle];
var account;
if (msg.account) {
account = new MailAccount(this, msg.account, null);
}
req.callback.call(null, msg.error, msg.errorDetails, account);
return true;
},
_clearAccountProblems: function ma__clearAccountProblems(account, callback) {
var handle = this._nextHandle++;
this._pendingRequests[handle] = {
type: 'clearAccountProblems',
callback: callback
};
this.__bridgeSend({
type: 'clearAccountProblems',
accountId: account.id,
handle: handle
});
},
_recv_clearAccountProblems: function ma__recv_clearAccountProblems(msg) {
var req = this._pendingRequests[msg.handle];
delete this._pendingRequests[msg.handle];
req.callback && req.callback();
return true;
},
_modifyAccount: function ma__modifyAccount(account, mods, callback) {
var handle = this._nextHandle++;
this._pendingRequests[handle] = {
type: 'modifyAccount',
callback: callback
};
this.__bridgeSend({
type: 'modifyAccount',
accountId: account.id,
mods: mods,
handle: handle
});
},
_recv_modifyAccount: function (msg) {
var req = this._pendingRequests[msg.handle];
delete this._pendingRequests[msg.handle];
req.callback && req.callback();
return true;
},
_deleteAccount: function ma__deleteAccount(account) {
this.__bridgeSend({
type: 'deleteAccount',
accountId: account.id
});
},
_modifyIdentity: function ma__modifyIdentity(identity, mods, callback) {
var handle = this._nextHandle++;
this._pendingRequests[handle] = {
type: 'modifyIdentity',
callback: callback
};
this.__bridgeSend({
type: 'modifyIdentity',
identityId: identity.id,
mods: mods,
handle: handle
});
},
_recv_modifyIdentity: function (msg) {
var req = this._pendingRequests[msg.handle];
delete this._pendingRequests[msg.handle];
req.callback && req.callback();
return true;
},
viewAccounts: function ma_viewAccounts(realAccountsOnly) {
var handle = this._nextHandle++, slice = new AccountsViewSlice(this, handle);
this._slices[handle] = slice;
this.__bridgeSend({
type: 'viewAccounts',
handle: handle
});
return slice;
},
viewSenderIdentities: function ma_viewSenderIdentities() {
var handle = this._nextHandle++, slice = new BridgedViewSlice(this, 'identities', handle);
this._slices[handle] = slice;
this.__bridgeSend({
type: 'viewSenderIdentities',
handle: handle
});
return slice;
},
viewFolders: function ma_viewFolders(mode, argument) {
var handle = this._nextHandle++, slice = new FoldersViewSlice(this, handle);
this._slices[handle] = slice;
this.__bridgeSend({
type: 'viewFolders',
mode: mode,
handle: handle,
argument: argument ? argument.id : null
});
return slice;
},
viewFolderMessages: function ma_viewFolderMessages(folder) {
var handle = this._nextHandle++, slice = new HeadersViewSlice(this, handle);
slice.folderId = folder.id;
slice.pendingRequestCount++;
this._slices[handle] = slice;
this.__bridgeSend({
type: 'viewFolderMessages',
folderId: folder.id,
handle: handle
});
return slice;
},
searchFolderMessages: function ma_searchFolderMessages(folder, text, whatToSearch) {
var handle = this._nextHandle++, slice = new HeadersViewSlice(this, handle, 'matchedHeaders');
slice.pendingRequestCount++;
this._slices[handle] = slice;
this.__bridgeSend({
type: 'searchFolderMessages',
folderId: folder.id,
handle: handle,
phrase: text,
whatToSearch: whatToSearch
});
return slice;
},
deleteMessages: function ma_deleteMessages(messages) {
var handle = this._nextHandle++;
var undoableOp = new UndoableOperation(this, 'delete', messages.length, handle), msgSuids = messages.map(serializeMessageName);
this._pendingRequests[handle] = {
type: 'mutation',
handle: handle,
undoableOp: undoableOp
};
this.__bridgeSend({
type: 'deleteMessages',
handle: handle,
messages: msgSuids
});
return undoableOp;
},
moveMessages: function ma_moveMessages(messages, targetFolder, callback) {
var handle = this._nextHandle++;
var undoableOp = new UndoableOperation(this, 'move', messages.length, handle), msgSuids = messages.map(serializeMessageName);
this._pendingRequests[handle] = {
type: 'mutation',
handle: handle,
undoableOp: undoableOp,
callback: callback
};
this.__bridgeSend({
type: 'moveMessages',
handle: handle,
messages: msgSuids,
targetFolder: targetFolder.id
});
return undoableOp;
},
markMessagesRead: function ma_markMessagesRead(messages, beRead) {
return this.modifyMessageTags(messages, beRead ? ['\\Seen'] : null, beRead ? null : ['\\Seen'], beRead ? 'read' : 'unread');
},
markMessagesStarred: function ma_markMessagesStarred(messages, beStarred) {
return this.modifyMessageTags(messages, beStarred ? ['\\Flagged'] : null, beStarred ? null : ['\\Flagged'], beStarred ? 'star' : 'unstar');
},
modifyMessageTags: function ma_modifyMessageTags(messages, addTags, removeTags, _opcode) {
var handle = this._nextHandle++;
if (!_opcode) {
if (addTags && addTags.length)
_opcode = 'addtag';
else if (removeTags && removeTags.length)
_opcode = 'removetag';
}
var undoableOp = new UndoableOperation(this, _opcode, messages.length, handle), msgSuids = messages.map(serializeMessageName);
this._pendingRequests[handle] = {
type: 'mutation',
handle: handle,
undoableOp: undoableOp
};
this.__bridgeSend({
type: 'modifyMessageTags',
handle: handle,
opcode: _opcode,
addTags: addTags,
removeTags: removeTags,
messages: msgSuids
});
return undoableOp;
},
sendOutboxMessages: function (account, callback) {
var handle = this._nextHandle++;
this._pendingRequests[handle] = {
type: 'sendOutboxMessages',
callback: callback
};
this.__bridgeSend({
type: 'sendOutboxMessages',
accountId: account.id,
handle: handle
});
},
_recv_sendOutboxMessages: function (msg) {
var req = this._pendingRequests[msg.handle];
delete this._pendingRequests[msg.handle];
req.callback && req.callback();
return true;
},
setOutboxSyncEnabled: function (account, enabled, callback) {
var handle = this._nextHandle++;
this._pendingRequests[handle] = {
type: 'setOutboxSyncEnabled',
callback: callback
};
this.__bridgeSend({
type: 'setOutboxSyncEnabled',
accountId: account.id,
outboxSyncEnabled: enabled,
handle: handle
});
},
_recv_setOutboxSyncEnabled: function (msg) {
var req = this._pendingRequests[msg.handle];
delete this._pendingRequests[msg.handle];
req.callback && req.callback();
return true;
},
parseMailbox: function (email) {
try {
var mailbox = addressparser.parse(email);
return mailbox.length >= 1 ? mailbox[0] : null;
} catch (ex) {
reportClientCodeError('parse mailbox error', ex, '\n', ex.stack);
return null;
}
},
_recv_mutationConfirmed: function (msg) {
var req = this._pendingRequests[msg.handle];
if (!req) {
unexpectedBridgeDataError('Bad handle for mutation:', msg.handle);
return true;
}
req.undoableOp._tempHandle = null;
req.undoableOp._longtermIds = msg.longtermIds;
if (req.undoableOp._undoRequested)
req.undoableOp.undo();
if (req.callback) {
req.callback(msg.result);
}
return true;
},
__undo: function undo(undoableOp) {
this.__bridgeSend({
type: 'undo',
longtermIds: undoableOp._longtermIds
});
},
resolveEmailAddressToPeep: function (emailAddress, callback) {
var peep = ContactCache.resolvePeep({
name: null,
address: emailAddress
});
if (ContactCache.pendingLookupCount)
ContactCache.callbacks.push(callback.bind(null, peep));
else
callback(peep);
},
beginMessageComposition: function (message, folder, options, callback) {
if (!callback)
throw new Error('A callback must be provided; you are using the API ' + 'wrong if you do not.');
if (!options)
options = {};
var handle = this._nextHandle++, composer = new MessageComposition(this, handle);
this._pendingRequests[handle] = {
type: 'compose',
composer: composer,
callback: callback
};
var msg = {
type: 'beginCompose',
handle: handle,
mode: null,
submode: null,
refSuid: null,
refDate: null,
refGuid: null,
refAuthor: null,
refSubject: null
};
if (options.hasOwnProperty('replyTo') && options.replyTo) {
msg.mode = 'reply';
msg.submode = options.replyMode;
msg.refSuid = options.replyTo.id;
msg.refDate = options.replyTo.date.valueOf();
msg.refGuid = options.replyTo.guid;
msg.refAuthor = options.replyTo.author.toWireRep();
msg.refSubject = options.replyTo.subject;
} else if (options.hasOwnProperty('forwardOf') && options.forwardOf) {
msg.mode = 'forward';
msg.submode = options.forwardMode;
msg.refSuid = options.forwardOf.id;
msg.refDate = options.forwardOf.date.valueOf();
msg.refGuid = options.forwardOf.guid;
msg.refAuthor = options.forwardOf.author.toWireRep();
msg.refSubject = options.forwardOf.subject;
} else {
msg.mode = 'new';
if (message) {
msg.submode = 'message';
msg.refSuid = message.id;
} else if (folder) {
msg.submode = 'folder';
msg.refSuid = folder.id;
}
}
this.__bridgeSend(msg);
return composer;
},
resumeMessageComposition: function (message, callback) {
if (!callback)
throw new Error('A callback must be provided; you are using the API ' + 'wrong if you do not.');
var handle = this._nextHandle++, composer = new MessageComposition(this, handle);
this._pendingRequests[handle] = {
type: 'compose',
composer: composer,
callback: callback
};
this.__bridgeSend({
type: 'resumeCompose',
handle: handle,
messageNamer: serializeMessageName(message)
});
return composer;
},
_recv_composeBegun: function (msg) {
var req = this._pendingRequests[msg.handle];
if (!req) {
unexpectedBridgeDataError('Bad handle for compose begun:', msg.handle);
return true;
}
req.composer.senderIdentity = new MailSenderIdentity(this, msg.identity);
req.composer.subject = msg.subject;
req.composer.body = msg.body;
req.composer.to = msg.to;
req.composer.cc = msg.cc;
req.composer.bcc = msg.bcc;
req.composer._references = msg.referencesStr;
req.composer.attachments = msg.attachments;
req.composer.sendStatus = msg.sendStatus;
if (req.callback) {
var callback = req.callback;
req.callback = null;
callback.call(null, req.composer);
}
return true;
},
_composeAttach: function (draftHandle, attachmentDef, callback) {
if (!draftHandle) {
return;
}
var draftReq = this._pendingRequests[draftHandle];
if (!draftReq) {
return;
}
var callbackHandle = this._nextHandle++;
this._pendingRequests[callbackHandle] = {
type: 'attachBlobToDraft',
callback: callback
};
this.__bridgeSend({
type: 'attachBlobToDraft',
handle: callbackHandle,
draftHandle: draftHandle,
attachmentDef: attachmentDef
});
},
_recv_attachedBlobToDraft: function (msg) {
var callbackReq = this._pendingRequests[msg.handle];
var draftReq = this._pendingRequests[msg.draftHandle];
if (!callbackReq) {
return true;
}
delete this._pendingRequests[msg.handle];
if (callbackReq.callback && draftReq && draftReq.composer) {
callbackReq.callback(msg.err, draftReq.composer);
}
return true;
},
_composeDetach: function (draftHandle, attachmentIndex, callback) {
if (!draftHandle) {
return;
}
var draftReq = this._pendingRequests[draftHandle];
if (!draftReq) {
return;
}
var callbackHandle = this._nextHandle++;
this._pendingRequests[callbackHandle] = {
type: 'detachAttachmentFromDraft',
callback: callback
};
this.__bridgeSend({
type: 'detachAttachmentFromDraft',
handle: callbackHandle,
draftHandle: draftHandle,
attachmentIndex: attachmentIndex
});
},
_recv_detachedAttachmentFromDraft: function (msg) {
var callbackReq = this._pendingRequests[msg.handle];
var draftReq = this._pendingRequests[msg.draftHandle];
if (!callbackReq) {
return true;
}
delete this._pendingRequests[msg.handle];
if (callbackReq.callback && draftReq && draftReq.composer) {
callbackReq.callback(msg.err, draftReq.composer);
}
return true;
},
_composeDone: function (handle, command, state, callback) {
if (!handle)
return;
var req = this._pendingRequests[handle];
if (!req) {
return;
}
req.type = command;
if (callback)
req.callback = callback;
this.__bridgeSend({
type: 'doneCompose',
handle: handle,
command: command,
state: state
});
},
_recv_doneCompose: function (msg) {
var req = this._pendingRequests[msg.handle];
if (!req) {
unexpectedBridgeDataError('Bad handle for doneCompose:', msg.handle);
return true;
}
req.active = null;
if (req.type === 'die' || !msg.err && req.type !== 'save')
delete this._pendingRequests[msg.handle];
if (req.callback) {
req.callback.call(null, {
sentDate: msg.sentDate,
messageId: msg.messageId,
sendStatus: msg.sendStatus
});
req.callback = null;
}
return true;
},
setInteractive: function () {
this.__bridgeSend({ type: 'setInteractive' });
},
_recv_cronSyncStart: function ma__recv_cronSyncStart(msg) {
if (this.oncronsyncstart)
this.oncronsyncstart(msg.accountIds);
return true;
},
_recv_cronSyncStop: function ma__recv_cronSyncStop(msg) {
if (this.oncronsyncstop)
this.oncronsyncstop(msg.accountsResults);
return true;
},
_recv_backgroundSendStatus: function (msg) {
if (this.onbackgroundsendstatus) {
this.onbackgroundsendstatus(msg.data);
}
return true;
},
useLocalizedStrings: function (strings) {
this.__bridgeSend({
type: 'localizedStrings',
strings: strings
});
if (strings.folderNames)
this.l10n_folder_names = strings.folderNames;
},
l10n_folder_names: {},
l10n_folder_name: function (name, type) {
if (this.l10n_folder_names.hasOwnProperty(type)) {
var lowerName = name.toLowerCase();
if (type === lowerName || type === 'drafts' || type === 'junk' || type === 'queue')
return this.l10n_folder_names[type];
}
return name;
},
modifyConfig: function (mods) {
for (var key in mods) {
if (LEGAL_CONFIG_KEYS.indexOf(key) === -1)
throw new Error(key + ' is not a legal config key!');
}
this.__bridgeSend({
type: 'modifyConfig',
mods: mods
});
},
_recv_config: function (msg) {
this.config = msg.config;
return true;
},
ping: function (callback) {
var handle = this._nextHandle++;
this._pendingRequests[handle] = {
type: 'ping',
callback: callback
};
window.setZeroTimeout(function () {
this.__bridgeSend({
type: 'ping',
handle: handle
});
}.bind(this));
},
_recv_pong: function (msg) {
var req = this._pendingRequests[msg.handle];
delete this._pendingRequests[msg.handle];
req.callback();
return true;
},
debugSupport: function (command, argument) {
if (command === 'setLogging')
this.config.debugLogging = argument;
this.__bridgeSend({
type: 'debugSupport',
cmd: command,
arg: argument
});
}
};
});
;
define('ext/worker-support/main-router', [], function () {
'use strict';
var listeners = {};
var modules = [];
var worker = null;
function register(module) {
var action, name = module.name;
modules.push(module);
if (module.process) {
action = function (msg) {
module.process(msg.uid, msg.cmd, msg.args);
};
} else if (module.dispatch) {
action = function (msg) {
if (module.dispatch[msg.cmd]) {
module.dispatch[msg.cmd].apply(module.dispatch, msg.args);
}
};
}
listeners[name] = action;
module.sendMessage = function (uid, cmd, args, transferArgs) {
worker.postMessage({
type: name,
uid: uid,
cmd: cmd,
args: args
}, transferArgs);
};
}
function unregister(module) {
delete listeners['on' + module.name];
}
function shutdown() {
modules.forEach(function (module) {
if (module.shutdown)
module.shutdown();
});
}
function useWorker(_worker) {
worker = _worker;
worker.onmessage = function dispatchToListener(evt) {
var data = evt.data;
var listener = listeners[data.type];
if (listener)
listener(data);
};
}
return {
register: register,
unregister: unregister,
useWorker: useWorker,
shutdown: shutdown
};
});
;
define('ext/worker-support/configparser-main', [], function () {
'use strict';
function debug(str) {
}
function nsResolver(prefix) {
var baseUrl = 'http://schemas.microsoft.com/exchange/autodiscover/';
var ns = {
rq: baseUrl + 'mobilesync/requestschema/2006',
ad: baseUrl + 'responseschema/2006',
ms: baseUrl + 'mobilesync/responseschema/2006'
};
return ns[prefix] || null;
}
function parseAccountCommon(uid, cmd, text) {
var doc = new DOMParser().parseFromString(text, 'text/xml');
var getNode = function (xpath, rel) {
return doc.evaluate(xpath, rel || doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
};
var dictifyChildNodes = function (node) {
if (!node) {
return null;
}
var dict = {};
for (var kid = node.firstElementChild; kid; kid = kid.nextElementSibling) {
dict[kid.tagName] = kid.textContent;
}
return dict;
};
var provider = getNode('/clientConfig/emailProvider');
var incoming = getNode('incomingServer[@type="imap"] | ' + 'incomingServer[@type="activesync"] | ' + 'incomingServer[@type="pop3"]', provider);
var outgoing = getNode('outgoingServer[@type="smtp"]', provider);
var oauth2Settings = dictifyChildNodes(getNode('oauth2Settings', provider));
var config = null;
var status = null;
if (incoming) {
config = {
type: null,
incoming: {},
outgoing: {},
oauth2Settings: oauth2Settings
};
for (var iter in Iterator(incoming.children)) {
var child = iter[1];
config.incoming[child.tagName] = child.textContent;
}
if (incoming.getAttribute('type') === 'activesync') {
config.type = 'activesync';
} else if (outgoing) {
var isImap = incoming.getAttribute('type') === 'imap';
config.type = isImap ? 'imap+smtp' : 'pop3+smtp';
for (var iter in Iterator(outgoing.children)) {
var child = iter[1];
config.outgoing[child.tagName] = child.textContent;
}
var ALLOWED_SOCKET_TYPES = [
'SSL',
'STARTTLS'
];
if (ALLOWED_SOCKET_TYPES.indexOf(config.incoming.socketType) === -1 || ALLOWED_SOCKET_TYPES.indexOf(config.outgoing.socketType) === -1) {
config = null;
status = 'unsafe';
}
} else {
config = null;
status = 'no-outgoing';
}
} else {
status = 'no-incoming';
}
self.sendMessage(uid, cmd, [
config,
status
]);
}
function parseActiveSyncAccount(uid, cmd, text, aNoRedirect) {
var doc = new DOMParser().parseFromString(text, 'text/xml');
var getNode = function (xpath, rel) {
return doc.evaluate(xpath, rel, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
};
var getNodes = function (xpath, rel) {
return doc.evaluate(xpath, rel, nsResolver, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
};
var getString = function (xpath, rel) {
return doc.evaluate(xpath, rel, nsResolver, XPathResult.STRING_TYPE, null).stringValue;
};
var postResponse = function (error, config, redirectedEmail) {
self.sendMessage(uid, cmd, [
config,
error,
redirectedEmail
]);
};
var error = null;
if (doc.documentElement.tagName === 'parsererror') {
error = 'Error parsing autodiscover response';
return postResponse(error);
}
var responseNode = getNode('/ad:Autodiscover/ms:Response', doc) || getNode('/ms:Autodiscover/ms:Response', doc);
if (!responseNode) {
error = 'Missing Autodiscover Response node';
return postResponse(error);
}
var error = getNode('ms:Error', responseNode) || getNode('ms:Action/ms:Error', responseNode);
if (error) {
error = getString('ms:Message/text()', error);
return postResponse(error);
}
var redirect = getNode('ms:Action/ms:Redirect', responseNode);
if (redirect) {
if (aNoRedirect) {
error = 'Multiple redirects occurred during autodiscovery';
return postResponse(error);
}
var redirectedEmail = getString('text()', redirect);
return postResponse(null, null, redirectedEmail);
}
var user = getNode('ms:User', responseNode);
var config = {
culture: getString('ms:Culture/text()', responseNode),
user: {
name: getString('ms:DisplayName/text()', user),
email: getString('ms:EMailAddress/text()', user)
},
servers: []
};
var servers = getNodes('ms:Action/ms:Settings/ms:Server', responseNode);
var server;
while (server = servers.iterateNext()) {
config.servers.push({
type: getString('ms:Type/text()', server),
url: getString('ms:Url/text()', server),
name: getString('ms:Name/text()', server),
serverData: getString('ms:ServerData/text()', server)
});
}
for (var iter in Iterator(config.servers)) {
var server = iter[1];
if (server.type === 'MobileSync') {
config.mobileSyncServer = server;
break;
}
}
if (!config.mobileSyncServer) {
error = 'No MobileSync server found';
return postResponse(error, config);
}
postResponse(null, config);
}
;
var self = {
name: 'configparser',
sendMessage: null,
process: function (uid, cmd, args) {
debug('process ' + cmd);
switch (cmd) {
case 'accountcommon':
parseAccountCommon(uid, cmd, args[0]);
break;
case 'accountactivesync':
parseActiveSyncAccount(uid, cmd, args[0], args[1]);
break;
}
}
};
return self;
});
define('ext/worker-support/cronsync-main', [
'require',
'evt'
], function (require) {
'use strict';
var evt = require('evt');
function debug(str) {
console.log('cronsync-main: ' + str);
}
function makeData(accountIds, interval, date) {
return {
type: 'sync',
accountIds: accountIds,
interval: interval,
timestamp: date.getTime()
};
}
function makeAccountKey(accountIds) {
return 'id' + accountIds.join(' ');
}
var prefixLength = 'interval'.length;
function toInterval(intervalKey) {
return parseInt(intervalKey.substring(prefixLength), 10);
}
function hasSameValues(ary1, ary2) {
if (ary1.length !== ary2.length) {
return false;
}
var hasMismatch = ary1.some(function (item, i) {
return item !== ary2[i];
});
return !hasMismatch;
}
var dispatcher = {
_routeReady: false,
_routeQueue: [],
_sendMessage: function (type, args) {
if (this._routeReady) {
routeRegistration.sendMessage(null, type, args);
} else {
this._routeQueue.push([
type,
args
]);
}
},
hello: function () {
this._routeReady = true;
if (this._routeQueue.length) {
var queue = this._routeQueue;
this._routeQueue = [];
queue.forEach(function (args) {
this._sendMessage(args[0], args[1]);
}.bind(this));
}
},
clearAll: function () {
var mozAlarms = navigator.mozAlarms;
if (!mozAlarms) {
return;
}
var r = mozAlarms.getAll();
r.onsuccess = function (event) {
var alarms = event.target.result;
if (!alarms) {
return;
}
alarms.forEach(function (alarm) {
if (alarm.data && alarm.data.type === 'sync') {
mozAlarms.remove(alarm.id);
}
});
}.bind(this);
r.onerror = function (err) {
console.error('cronsync-main clearAll mozAlarms.getAll: error: ' + err);
}.bind(this);
},
ensureSync: function (syncData) {
var mozAlarms = navigator.mozAlarms;
if (!mozAlarms) {
console.warn('no mozAlarms support!');
return;
}
debug('ensureSync called');
var request = mozAlarms.getAll();
request.onsuccess = function (event) {
debug('success!');
var alarms = event.target.result;
if (!alarms) {
alarms = [];
}
var expiredAlarmIds = [], okAlarmIntervals = {}, uniqueAlarms = {};
alarms.forEach(function (alarm) {
if (!alarm.data || !alarm.data.type || alarm.data.type !== 'sync') {
return;
}
var intervalKey = 'interval' + alarm.data.interval, wantedAccountIds = syncData[intervalKey];
if (!wantedAccountIds || !hasSameValues(wantedAccountIds, alarm.data.accountIds)) {
debug('account array mismatch, canceling existing alarm');
expiredAlarmIds.push(alarm.id);
} else {
var interval = toInterval(intervalKey), now = Date.now(), alarmTime = alarm.data.timestamp, accountKey = makeAccountKey(wantedAccountIds);
if (interval && !uniqueAlarms.hasOwnProperty(accountKey) && alarmTime > now && alarmTime < now + interval) {
debug('existing alarm is OK: ' + accountKey);
uniqueAlarms[accountKey] = true;
okAlarmIntervals[intervalKey] = true;
} else {
debug('existing alarm is out of interval range, canceling');
expiredAlarmIds.push(alarm.id);
}
}
});
expiredAlarmIds.forEach(function (alarmId) {
mozAlarms.remove(alarmId);
});
var alarmMax = 0, alarmCount = 0, self = this;
function done() {
alarmCount += 1;
if (alarmCount < alarmMax) {
return;
}
debug('ensureSync completed');
self._sendMessage('syncEnsured');
}
Object.keys(syncData).forEach(function (intervalKey) {
if (okAlarmIntervals.hasOwnProperty(intervalKey)) {
return;
}
var interval = toInterval(intervalKey), accountIds = syncData[intervalKey], date = new Date(Date.now() + interval);
if (!interval) {
return;
}
alarmMax += 1;
var alarmRequest = mozAlarms.add(date, 'ignoreTimezone', makeData(accountIds, interval, date));
alarmRequest.onsuccess = function () {
debug('success: mozAlarms.add for ' + 'IDs: ' + accountIds + ' at ' + interval + 'ms');
done();
};
alarmRequest.onerror = function (err) {
console.error('cronsync-main mozAlarms.add for IDs: ' + accountIds + ' failed: ' + err);
};
});
if (!alarmMax) {
done();
}
}.bind(this);
request.onerror = function (err) {
console.error('cronsync-main ensureSync mozAlarms.getAll: error: ' + err);
};
}
};
if (navigator.mozSetMessageHandler) {
navigator.mozSetMessageHandler('alarm', function onAlarm(alarm) {
console.log('mozSetMessageHandler: received an alarm');
if (window.hasOwnProperty('appDispatchedMessage')) {
window.appDispatchedMessage = true;
}
var data = alarm.data;
if (!data || data.type !== 'sync') {
return;
}
if (navigator.requestWakeLock) {
var locks = [navigator.requestWakeLock('cpu')];
debug('wake locks acquired: ' + locks + ' for account IDs: ' + data.accountIds);
evt.emitWhenListener('cronSyncWakeLocks', makeAccountKey(data.accountIds), locks);
}
debug('alarm dispatch started at ' + new Date());
dispatcher._sendMessage('alarm', [
data.accountIds,
data.interval
]);
});
}
var routeRegistration = {
name: 'cronsync',
sendMessage: null,
dispatch: dispatcher
};
return routeRegistration;
});
define('ext/worker-support/devicestorage-main', [], function () {
'use strict';
function debug(str) {
dump('DeviceStorage: ' + str + '\n');
}
function save(uid, cmd, storage, blob, filename, registerDownload) {
var deviceStorage = navigator.getDeviceStorage(storage);
if (!deviceStorage) {
self.sendMessage(uid, cmd, [
false,
'no-device-storage',
null,
false
]);
return;
}
var req = deviceStorage.addNamed(blob, filename);
req.onerror = function () {
self.sendMessage(uid, cmd, [
false,
req.error.name,
null,
false
]);
};
req.onsuccess = function (e) {
var prefix = '';
if (typeof window.IS_GELAM_TEST !== 'undefined') {
prefix = 'TEST_PREFIX/';
}
var savedPath = prefix + e.target.result;
var registering = false;
if (registerDownload) {
var downloadManager = navigator.mozDownloadManager;
console.warn('have downloadManager?', !!downloadManager, 'have adoptDownload?', downloadManager && !!downloadManager.adoptDownload);
if (downloadManager && downloadManager.adoptDownload) {
try {
var fullPath = e.target.result;
var firstSlash = fullPath.indexOf('/', 2);
var storageName = fullPath.substring(1, firstSlash);
var storagePath = fullPath.substring(firstSlash + 1);
console.log('adopting download', deviceStorage.storageName, e.target.result);
registering = true;
downloadManager.adoptDownload({
totalBytes: blob.size,
url: '',
storageName: storageName,
storagePath: storagePath,
contentType: blob.type,
startTime: new Date(Date.now())
}).then(function () {
console.log('registered download with download manager');
self.sendMessage(uid, cmd, [
true,
null,
savedPath,
true
]);
}, function () {
console.warn('failed to register download with download manager');
self.sendMessage(uid, cmd, [
true,
null,
savedPath,
false
]);
});
} catch (ex) {
console.error('Problem adopting download!:', ex, '\n', ex.stack);
}
} else {
console.log('download manager not available, not registering.');
}
} else {
console.log('do not want to register download');
}
if (!registering) {
self.sendMessage(uid, cmd, [
true,
null,
savedPath,
false
]);
}
};
}
var self = {
name: 'devicestorage',
sendMessage: null,
process: function (uid, cmd, args) {
debug('process ' + cmd);
switch (cmd) {
case 'save':
save(uid, cmd, args[0], args[1], args[2], args[3]);
break;
}
}
};
return self;
});
define('ext/worker-support/maildb-main', [], function () {
'use strict';
function debug(str) {
dump('MailDB: ' + str + '\n');
}
var db = null;
function open(uid, cmd, args) {
db = self._debugDB = new MailDB(args[0], function () {
self.sendMessage(uid, cmd, Array.prototype.slice.call(arguments));
});
}
function others(uid, cmd, args) {
if (!Array.isArray(args))
args = [];
args.push(function () {
self.sendMessage(uid, cmd, Array.prototype.slice.call(arguments));
});
if (!db._db)
console.warn('trying to call', cmd, 'on apparently dead db. skipping.');
else
db[cmd].apply(db, args);
}
var self = {
name: 'maildb',
sendMessage: null,
process: function (uid, cmd, args) {
switch (cmd) {
case 'open':
open(uid, cmd, args);
break;
default:
others(uid, cmd, args);
break;
}
},
_debugDB: null
};
var IndexedDB;
if ('indexedDB' in window && window.indexedDB) {
IndexedDB = window.indexedDB;
} else if ('mozIndexedDB' in window && window.mozIndexedDB) {
IndexedDB = window.mozIndexedDB;
} else if ('webkitIndexedDB' in window && window.webkitIndexedDB) {
IndexedDB = window.webkitIndexedDB;
} else {
console.error('No IndexedDB!');
throw new Error('I need IndexedDB; load me in a content page universe!');
}
var CUR_VERSION = 22;
var FRIENDLY_LAZY_DB_UPGRADE_VERSION = 5;
var TBL_CONFIG = 'config', CONFIG_KEY_ROOT = 'config', CONFIG_KEYPREFIX_ACCOUNT_DEF = 'accountDef:';
var TBL_FOLDER_INFO = 'folderInfo';
var TBL_HEADER_BLOCKS = 'headerBlocks';
var TBL_BODY_BLOCKS = 'bodyBlocks';
function MailDB(testOptions, successCb, errorCb, upgradeCb) {
this._db = null;
this._lazyConfigCarryover = null;
this._fatalError = function (event) {
function explainSource(source) {
if (!source)
return 'unknown source';
if (source instanceof IDBObjectStore)
return 'object store "' + source.name + '"';
if (source instanceof IDBIndex)
return 'index "' + source.name + '" on object store "' + source.objectStore.name + '"';
if (source instanceof IDBCursor)
return 'cursor on ' + explainSource(source.source);
return 'unexpected source';
}
var explainedSource, target = event.target;
if (target instanceof IDBTransaction) {
explainedSource = 'transaction (' + target.mode + ')';
} else if (target instanceof IDBRequest) {
explainedSource = 'request as part of ' + (target.transaction ? target.transaction.mode : 'NO') + ' transaction on ' + explainSource(target.source);
} else {
explainedSource = target.toString();
}
console.error('indexedDB error:', target.error.name, 'from', explainedSource);
};
var dbVersion = CUR_VERSION;
if (testOptions && testOptions.dbDelta)
dbVersion += testOptions.dbDelta;
if (testOptions && testOptions.dbVersion)
dbVersion = testOptions.dbVersion;
var openRequest = IndexedDB.open('b2g-email', dbVersion), self = this;
openRequest.onsuccess = function (event) {
self._db = openRequest.result;
successCb();
};
openRequest.onupgradeneeded = function (event) {
console.log('MailDB in onupgradeneeded');
var db = openRequest.result;
if (event.oldVersion < FRIENDLY_LAZY_DB_UPGRADE_VERSION || testOptions && testOptions.nukeDb) {
self._nukeDB(db);
} else {
var trans = openRequest.transaction;
self.getConfig(function (configObj, accountInfos) {
if (configObj)
self._lazyConfigCarryover = {
oldVersion: event.oldVersion,
config: configObj,
accountInfos: accountInfos
};
self._nukeDB(db);
}, trans);
}
};
openRequest.onerror = this._fatalError;
}
MailDB.prototype = {
_nukeDB: function (db) {
var existingNames = db.objectStoreNames;
for (var i = 0; i < existingNames.length; i++) {
db.deleteObjectStore(existingNames[i]);
}
db.createObjectStore(TBL_CONFIG);
db.createObjectStore(TBL_FOLDER_INFO);
db.createObjectStore(TBL_HEADER_BLOCKS);
db.createObjectStore(TBL_BODY_BLOCKS);
},
close: function () {
if (this._db) {
this._db.close();
this._db = null;
}
},
getConfig: function (callback, trans) {
var transaction = trans || this._db.transaction([
TBL_CONFIG,
TBL_FOLDER_INFO
], 'readonly');
var configStore = transaction.objectStore(TBL_CONFIG), folderInfoStore = transaction.objectStore(TBL_FOLDER_INFO);
var configReq = configStore.mozGetAll(), folderInfoReq = folderInfoStore.mozGetAll();
configReq.onerror = this._fatalError;
folderInfoReq.onerror = this._fatalError;
var self = this;
folderInfoReq.onsuccess = function (event) {
var configObj = null, accounts = [], i, obj;
if (self._lazyConfigCarryover) {
var lazyCarryover = self._lazyConfigCarryover;
self._lazyConfigCarryover = null;
callback(configObj, accounts, lazyCarryover);
return;
}
for (i = 0; i < configReq.result.length; i++) {
obj = configReq.result[i];
if (obj.id === 'config')
configObj = obj;
else
accounts.push({
def: obj,
folderInfo: null
});
}
for (i = 0; i < folderInfoReq.result.length; i++) {
accounts[i].folderInfo = folderInfoReq.result[i];
}
try {
callback(configObj, accounts);
} catch (ex) {
console.error('Problem in configCallback', ex, '\n', ex.stack);
}
};
},
saveConfig: function (config) {
var req = this._db.transaction(TBL_CONFIG, 'readwrite').objectStore(TBL_CONFIG).put(config, 'config');
req.onerror = this._fatalError;
},
saveAccountDef: function (config, accountDef, folderInfo, callback) {
var trans = this._db.transaction([
TBL_CONFIG,
TBL_FOLDER_INFO
], 'readwrite');
var configStore = trans.objectStore(TBL_CONFIG);
configStore.put(config, 'config');
configStore.put(accountDef, CONFIG_KEYPREFIX_ACCOUNT_DEF + accountDef.id);
if (folderInfo) {
trans.objectStore(TBL_FOLDER_INFO).put(folderInfo, accountDef.id);
}
trans.onerror = this._fatalError;
if (callback) {
trans.oncomplete = function () {
callback();
};
}
},
loadHeaderBlock: function (folderId, blockId, callback) {
var req = this._db.transaction(TBL_HEADER_BLOCKS, 'readonly').objectStore(TBL_HEADER_BLOCKS).get(folderId + ':' + blockId);
req.onerror = this._fatalError;
req.onsuccess = function () {
callback(req.result);
};
},
loadBodyBlock: function (folderId, blockId, callback) {
var req = this._db.transaction(TBL_BODY_BLOCKS, 'readonly').objectStore(TBL_BODY_BLOCKS).get(folderId + ':' + blockId);
req.onerror = this._fatalError;
req.onsuccess = function () {
callback(req.result);
};
},
saveAccountFolderStates: function (accountId, folderInfo, perFolderStuff, deletedFolderIds, callback) {
var trans = this._db.transaction([
TBL_FOLDER_INFO,
TBL_HEADER_BLOCKS,
TBL_BODY_BLOCKS
], 'readwrite');
trans.onerror = this._fatalError;
trans.objectStore(TBL_FOLDER_INFO).put(folderInfo, accountId);
var headerStore = trans.objectStore(TBL_HEADER_BLOCKS), bodyStore = trans.objectStore(TBL_BODY_BLOCKS), i;
var operationQueue = [];
function addToQueue() {
var args = Array.slice(arguments);
var store = args.shift();
var type = args.shift();
operationQueue.push({
store: store,
type: type,
args: args
});
}
function workQueue() {
var pendingRequest = operationQueue.shift();
if (!pendingRequest)
return;
var store = pendingRequest.store;
var type = pendingRequest.type;
var request = store[type].apply(store, pendingRequest.args);
request.onsuccess = request.onerror = workQueue;
}
for (i = 0; i < perFolderStuff.length; i++) {
var pfs = perFolderStuff[i], block;
for (var headerBlockId in pfs.headerBlocks) {
block = pfs.headerBlocks[headerBlockId];
if (block)
addToQueue(headerStore, 'put', block, pfs.id + ':' + headerBlockId);
else
addToQueue(headerStore, 'delete', pfs.id + ':' + headerBlockId);
}
for (var bodyBlockId in pfs.bodyBlocks) {
block = pfs.bodyBlocks[bodyBlockId];
if (block)
addToQueue(bodyStore, 'put', block, pfs.id + ':' + bodyBlockId);
else
addToQueue(bodyStore, 'delete', pfs.id + ':' + bodyBlockId);
}
}
if (deletedFolderIds) {
for (i = 0; i < deletedFolderIds.length; i++) {
var folderId = deletedFolderIds[i], range = IDBKeyRange.bound(folderId + ':', folderId + ':\uFFF0', false, false);
addToQueue(headerStore, 'delete', range);
addToQueue(bodyStore, 'delete', range);
}
}
if (callback) {
trans.addEventListener('complete', function () {
callback();
});
}
workQueue();
return trans;
},
deleteAccount: function (accountId) {
var trans = this._db.transaction([
TBL_CONFIG,
TBL_FOLDER_INFO,
TBL_HEADER_BLOCKS,
TBL_BODY_BLOCKS
], 'readwrite');
trans.onerror = this._fatalError;
trans.objectStore(TBL_CONFIG).delete('accountDef:' + accountId);
trans.objectStore(TBL_FOLDER_INFO).delete(accountId);
var range = IDBKeyRange.bound(accountId + '/', accountId + '/\uFFF0', false, false);
trans.objectStore(TBL_HEADER_BLOCKS).delete(range);
trans.objectStore(TBL_BODY_BLOCKS).delete(range);
}
};
return self;
});
define('ext/async_blob_fetcher', ['exports'], function (exports) {
function asyncFetchBlobAsUint8Array(blob, callback) {
var blobUrl = URL.createObjectURL(blob);
var xhr = new XMLHttpRequest();
xhr.open('GET', blobUrl, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) {
callback(xhr.status);
return;
}
callback(null, new Uint8Array(xhr.response));
};
xhr.onerror = function () {
callback('error');
};
try {
xhr.send();
} catch (ex) {
console.error('XHR send() failure on blob');
callback('error');
}
URL.revokeObjectURL(blobUrl);
}
return { asyncFetchBlobAsUint8Array: asyncFetchBlobAsUint8Array };
});
;
define('ext/worker-support/net-main', [
'require',
'../async_blob_fetcher'
], function (require) {
'use strict';
var asyncFetchBlobAsUint8Array = require('../async_blob_fetcher').asyncFetchBlobAsUint8Array;
var sockInfoByUID = {};
function open(uid, host, port, options) {
var socket = navigator.mozTCPSocket;
var sock = socket.open(host, port, options);
var sockInfo = sockInfoByUID[uid] = {
uid: uid,
sock: sock,
activeBlob: null,
blobOffset: 0,
queuedData: null,
backlog: []
};
sock.onopen = function (evt) {
self.sendMessage(uid, 'onopen');
};
sock.onerror = function (evt) {
var err = evt.data;
var wrappedErr;
if (err && typeof err === 'object') {
wrappedErr = {
name: err.name,
type: err.type,
message: err.message
};
} else {
wrappedErr = err;
}
self.sendMessage(uid, 'onerror', wrappedErr);
};
sock.ondata = function (evt) {
var buf = evt.data;
self.sendMessage(uid, 'ondata', buf, [buf]);
};
sock.ondrain = function (evt) {
if (sockInfo.activeBlob && sockInfo.queuedData) {
console.log('net-main(' + sockInfo.uid + '): Socket drained, sending.');
sock.send(sockInfo.queuedData.buffer, 0, sockInfo.queuedData.byteLength);
sockInfo.queuedData = null;
fetchNextBlobChunk(sockInfo);
} else {
self.sendMessage(uid, 'ondrain');
}
};
sock.onclose = function (evt) {
self.sendMessage(uid, 'onclose');
delete sockInfoByUID[uid];
};
}
function beginBlobSend(sockInfo, blob) {
console.log('net-main(' + sockInfo.uid + '): Blob send of', blob.size, 'bytes');
sockInfo.activeBlob = blob;
sockInfo.blobOffset = 0;
sockInfo.queuedData = null;
fetchNextBlobChunk(sockInfo);
}
function fetchNextBlobChunk(sockInfo) {
if (sockInfo.blobOffset >= sockInfo.activeBlob.size) {
console.log('net-main(' + sockInfo.uid + '): Blob send completed.', 'backlog length:', sockInfo.backlog.length);
sockInfo.activeBlob = null;
var backlog = sockInfo.backlog;
while (backlog.length) {
var sendArgs = backlog.shift();
var data = sendArgs[0];
if (data instanceof Blob) {
beginBlobSend(sockInfo, data);
return;
}
sockInfo.sock.send(data, sendArgs[1], sendArgs[2]);
}
return;
}
var nextOffset = Math.min(sockInfo.blobOffset + self.BLOB_BLOCK_READ_SIZE, sockInfo.activeBlob.size);
console.log('net-main(' + sockInfo.uid + '): Fetching bytes', sockInfo.blobOffset, 'through', nextOffset, 'of', sockInfo.activeBlob.size);
var blobSlice = sockInfo.activeBlob.slice(sockInfo.blobOffset, nextOffset);
sockInfo.blobOffset = nextOffset;
function gotChunk(err, binaryDataU8) {
console.log('net-main(' + sockInfo.uid + '): Retrieved chunk');
if (err) {
sockInfo.sock.close();
return;
}
if (sockInfo.sock.bufferedAmount === 0) {
console.log('net-main(' + sockInfo.uid + '): Sending chunk immediately.');
sockInfo.sock.send(binaryDataU8.buffer, 0, binaryDataU8.byteLength);
fetchNextBlobChunk(sockInfo);
return;
}
sockInfo.queuedData = binaryDataU8;
}
;
asyncFetchBlobAsUint8Array(blobSlice, gotChunk);
}
function close(uid) {
var sockInfo = sockInfoByUID[uid];
if (!sockInfo)
return;
var sock = sockInfo.sock;
sock.close();
sock.onopen = null;
sock.onerror = null;
sock.ondata = null;
sock.ondrain = null;
sock.onclose = null;
self.sendMessage(uid, 'onclose');
delete sockInfoByUID[uid];
}
function write(uid, data, offset, length) {
var sockInfo = sockInfoByUID[uid];
if (!sockInfo) {
return;
}
if (sockInfo.activeBlob) {
sockInfo.backlog.push([
data,
offset,
length
]);
return;
}
self.sendMessage(uid, 'onprogress', []);
if (data instanceof Blob) {
beginBlobSend(sockInfo, data);
} else {
sockInfo.sock.send(data, offset, length);
}
}
function upgradeToSecure(uid) {
var sockInfo = sockInfoByUID[uid];
if (!sockInfo)
return;
sockInfo.sock.upgradeToSecure();
}
var self = {
name: 'netsocket',
sendMessage: null,
BLOB_BLOCK_READ_SIZE: 96 * 1024,
process: function (uid, cmd, args) {
switch (cmd) {
case 'open':
open(uid, args[0], args[1], args[2]);
break;
case 'close':
close(uid);
break;
case 'write':
write(uid, args[0], args[1], args[2]);
break;
case 'upgradeToSecure':
upgradeToSecure(uid);
break;
default:
console.error('Unhandled net-main command:', cmd);
break;
}
}
};
return self;
});
define('ext/worker-support/wakelocks-main', [], function () {
'use strict';
function debug(str) {
dump('WakeLocks: ' + str + '\n');
}
var nextId = 1;
var locks = {};
function requestWakeLock(type) {
var lock = navigator.requestWakeLock(type);
var id = nextId++;
locks[id] = lock;
return id;
}
var self = {
name: 'wakelocks',
sendMessage: null,
process: function (uid, cmd, args) {
debug('process ' + cmd + ' ' + JSON.stringify(args));
switch (cmd) {
case 'requestWakeLock':
var type = args[0];
self.sendMessage(uid, cmd, [requestWakeLock(type)]);
break;
case 'unlock':
var id = args[0];
var lock = locks[id];
if (lock) {
lock.unlock();
delete locks[id];
}
self.sendMessage(uid, cmd, []);
}
}
};
return self;
});
window.setZeroTimeout = function (fn) {
setTimeout(function () {
fn();
}, 0);
};
define('ext/main-frame-setup', [
'./mailapi',
'./worker-support/main-router',
'./worker-support/configparser-main',
'./worker-support/cronsync-main',
'./worker-support/devicestorage-main',
'./worker-support/maildb-main',
'./worker-support/net-main',
'./worker-support/wakelocks-main',
'require'
], function ($mailapi, $router, $configparser, $cronsync, $devicestorage, $maildb, $net, $wakelocks, require) {
var control = {
name: 'control',
sendMessage: null,
process: function (uid, cmd, args) {
var online = navigator.onLine;
control.sendMessage(uid, 'hello', [online]);
window.addEventListener('online', function (evt) {
control.sendMessage(uid, evt.type, [true]);
});
window.addEventListener('offline', function (evt) {
control.sendMessage(uid, evt.type, [false]);
});
$router.unregister(control);
}
};
var MailAPI = new $mailapi.MailAPI();
var bridge = {
name: 'bridge',
sendMessage: null,
process: function (uid, cmd, args) {
var msg = args;
if (msg.type === 'hello') {
delete MailAPI._fake;
MailAPI.__bridgeSend = function (msg) {
worker.postMessage({
uid: uid,
type: 'bridge',
msg: msg
});
};
MailAPI.config = msg.config;
MailAPI._storedSends.forEach(function (msg) {
MailAPI.__bridgeSend(msg);
});
MailAPI._storedSends = [];
} else {
MailAPI.__bridgeReceive(msg);
}
}
};
var worker = new Worker(require.toUrl('./worker-bootstrap.js'));
$router.useWorker(worker);
$router.register(control);
$router.register(bridge);
$router.register($configparser);
$router.register($cronsync);
$router.register($devicestorage);
$router.register($maildb);
$router.register($net);
$router.register($wakelocks);
return MailAPI;
});
;