Skip to content
This repository has been archived by the owner on Nov 3, 2021. It is now read-only.

Commit

Permalink
Bug 814257 - Import vingtetun's gelam patch
Browse files Browse the repository at this point in the history
Attachment from:
https://bugzilla.mozilla.org/attachment.cgi?id=722927&action=edit

One conflict on data/lib/mailapi/jobmixins.js, easily resolved.
  • Loading branch information
vingtetun authored and asutherland committed Mar 24, 2013
1 parent dd2bdfe commit 2189c38
Show file tree
Hide file tree
Showing 12 changed files with 444 additions and 705 deletions.
58 changes: 14 additions & 44 deletions data/lib/mailapi/accountcommon.js
Expand Up @@ -247,53 +247,23 @@ Autoconfigurator.prototype = {
// issue), trying to use responseXML results in a SecurityError when
// running XPath queries. So let's just do an end-run around the
// "security".
var doc = new DOMParser().parseFromString(xhr.responseText, 'text/xml');
function getNode(xpath, rel) {
return doc.evaluate(xpath, rel || doc, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null)
.singleNodeValue;
}

var provider = getNode('/clientConfig/emailProvider');
// Get the first incomingServer we can use (we assume first == best).
var incoming = getNode('incomingServer[@type="imap"] | ' +
'incomingServer[@type="activesync"]', provider);
var outgoing = getNode('outgoingServer[@type="smtp"]', provider);

if (incoming) {
var config = { type: null, incoming: {}, outgoing: {} };
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) {
config.type = 'imap+smtp';
for (var iter in Iterator(outgoing.children)) {
var child = iter[1];
config.outgoing[child.tagName] = child.textContent;
}
self.postMessage({
uid: 0,
type: 'configparser',
cmd: 'accountcommon',
args: [xhr.responseText]
});

// We do not support unencrypted connections outside of unit tests.
if (config.incoming.socketType !== 'SSL' ||
config.outgoing.socketType !== 'SSL') {
callback('no-config-info', null, { status: 'unsafe' });
return;
}
}
else {
callback('no-config-info', null, { status: 'no-outgoing' });
self.addEventListener('message', function onworkerresponse(evt) {
var data = evt.data;
if (data.type != 'configparser' || data.cmd != 'accountcommon') {
return;
}

callback(null, config, null);
}
else {
callback('no-config-info', null, { status: 'no-incoming' });
}
self.removeEventListener(evt.type, onworkerresponse);
var args = data.args;
var config = args[0], status = args[1];
callback(config ? null : 'no-config-info', config, { status: status });
});
};

xhr.ontimeout = xhr.onerror = function() {
Expand Down
6 changes: 3 additions & 3 deletions data/lib/mailapi/activesync/folder.js
Expand Up @@ -799,10 +799,10 @@ ActiveSyncFolderConn.prototype = {
body.bodyReps = ['plain', bodyRep];
}
else if (bodyType === asbEnum.Type.HTML) {
var htmlNode = $htmlchew.sanitizeAndNormalizeHtml(bodyText);
header.snippet = $htmlchew.generateSnippet(htmlNode,
var html = $htmlchew.sanitizeAndNormalizeHtml(bodyText);
header.snippet = $htmlchew.generateSnippet(html,
DESIRED_SNIPPET_LENGTH);
body.bodyReps = ['html', htmlNode.innerHTML];
body.bodyReps = ['html', html];
}

return { header: header, body: body };
Expand Down
132 changes: 58 additions & 74 deletions data/lib/mailapi/cronsync.js
Expand Up @@ -149,40 +149,54 @@ console.log('sync done!');
};

function generateNotificationForMessage(header, onClick, onClose) {
// NB: We don't need to use NotificationHelper because we end up doing
// something similar ourselves.
console.log('generating notification for:', header.suid, header.subject);
var notif = navigator.mozNotification.createNotification(
header.author.name || header.author.address,
header.subject,
// XXX it makes no sense that the back-end knows the path of the icon,
// but this specific function may need to vary based on host environment
// anyways...
gIconUrl);
notif.onclick = onClick.bind(null, header, notif);
notif.onclose = onClose.bind(null, header, notif);
notif.show();
var notif = header.suid;

sendMessage(
'showNotification',
[header.author.name || header.author.address, header.subject],
function(click) {
click ? onClick(header, notif) : onClose(header, notif);
});
return notif;
}

var gApp, gIconUrl;
navigator.mozApps.getSelf().onsuccess = function(event) {
gApp = event.target.result;
gIconUrl = gApp.installOrigin + '/style/icons/Email.png';
};
/**
* Try and bring up the given header in the front-end.
*
* XXX currently, we just cause the app to display, but we don't do anything
* to cause the actual message to be displayed. Right now, since the back-end
* and the front-end are in the same app, we can easily tell ourselves to do
* things, but in the separated future, we might want to use a webactivity,
* and as such we should consider using that initially too.
*/
function displayHeaderInFrontend(header) {
gApp.launch();

function debug(str) {
dump("CronSyncer: " + str + "\n");
}

var uid = 0;
var callbacks = {};
function sendMessage(cmd, args, callback) {
if (callback) {
callbacks[uid] = callback;
}

if (!Array.isArray(args)) {
args = args ? [args] : [];
}

self.postMessage({ uid: uid++, type: 'cronsyncer', cmd: cmd, args: args });
}

function receiveMessage(evt) {
var data = evt.data;
if (data.type != 'cronsyncer')
return;

var callback = callbacks[data.uid];
if (!callback)
return;
delete callbacks[data.uid];

setTimeout(function() {
callback.apply(callback, data.args);
});
}

window.addEventListener("message", receiveMessage);

/**
* Creates the synchronizer. It is does not do anything until the first call
* to setSyncInterval.
Expand All @@ -208,7 +222,6 @@ function CronSyncer(universe, _logParent) {
this._outstandingNotesPerAccount = {};

this._initialized = false;
this._hackTimeout = null;

this._activeSlices = [];
}
Expand All @@ -218,62 +231,33 @@ CronSyncer.prototype = {
* Remove any/all scheduled alarms.
*/
_clearAlarms: function() {
// mozalarms doesn't work on desktop; comment out and use setTimeout.
if (this._hackTimeout !== null) {
window.clearTimeout(this._hackTimeout);
this._hackTimeout = null;
}
/*
var req = navigator.mozAlarms.getAll();
req.onsuccess = function(event) {
var alarms = event.target.result;
for (var i = 0; i < alarms.length; i++) {
navigator.mozAlarms.remove(alarms[i].id);
}
}.bind(this);
*/
sendMessage('clearAlarms');
},

_scheduleNextSync: function() {
if (!this._syncIntervalMS)
return;
console.log("scheduling sync for " + (this._syncIntervalMS / 1000) +
" seconds in the future.");
this._hackTimeout = window.setTimeout(this.onAlarm.bind(this),
this._syncIntervalMS);
/*
try {
console.log('mpozAlarms', navigator.mozAlarms);
var req = navigator.mozAlarms.add(
new Date(Date.now() + this._syncIntervalMS),
'ignoreTimezone', {});
console.log('req:', req);
req.onsuccess = function() {
console.log('scheduled!');
};
req.onerror = function(event) {
console.warn('alarm scheduling problem!');
console.warn(' err:',
event.target && event.target.error &&
event.target.error.name);
};
}
catch (ex) {
console.error('problem initiating request:', ex);
}
*/
//debug("scheduling sync for " + (this._syncIntervalMS / 1000) +
// " seconds in the future.");

sendMessage('addAlarm', [new Date(Date.now() + this._syncIntervalMS)]);
},

setSyncIntervalMS: function(syncIntervalMS) {
console.log('setSyncIntervalMS:', syncIntervalMS);
var pendingAlarm = false;
if (!this._initialized) {
this._initialized = true;
// mozAlarms doesn't work on b2g-desktop
/*
pendingAlarm = navigator.mozHasPendingMessage('alarm');
navigator.mozSetMessageHandler('alarm', this.onAlarm.bind(this));
*/
pendingAlarm = navigator.hasPendingAlarm;

window.addEventListener('message', (function(evt) {
switch(evt.data.type) {
case 'alarm':
dump("CronSyncer - receive an alarm via a message handler\n");
this.onAlarm(evt.data.args);
break;
}
}).bind(this));
}

// leave zero intact, otherwise round up to the minimum.
Expand Down Expand Up @@ -332,7 +316,7 @@ CronSyncer.prototype = {
console.warn('bad note index!');
outstandingInfo.notes.splice(idx);
// trigger the display of the app!
displayHeaderInFrontend(header);
sendMessage('showApp', [header]);
},
closeHandler: function(header, note, event) {
var idx = outstandingInfo.notes.indexOf(note);
Expand Down
96 changes: 11 additions & 85 deletions data/lib/mailapi/htmlchew.js
Expand Up @@ -447,80 +447,20 @@ var BLEACH_SETTINGS = {
* style tags in the head, etc.
* }
* ]
* @return[HtmlElement]{
* The sanitized HTML content wrapped in a div container.
* @return[HtmlString]{
* The sanitized HTML string wrapped into a div container.
* }
*/
exports.sanitizeAndNormalizeHtml = function sanitizeAndNormalize(htmlString) {
var sanitizedNode = $bleach.clean(htmlString, BLEACH_SETTINGS);
return sanitizedNode;
return $bleach.clean(htmlString, BLEACH_SETTINGS);
};

var ELEMENT_NODE = 1, TEXT_NODE = 3;

var RE_NORMALIZE_WHITESPACE = /\s+/g;

/**
* Derive snippet text from the already-sanitized HTML representation.
*/
exports.generateSnippet = function generateSnippet(sanitizedHtmlNode,
exports.generateSnippet = function generateSnippet(sanitizedHtml,
desiredLength) {
var snippet = '';

// Perform a traversal of the DOM tree skipping over things we don't care
// about. Whenever we see an element we can descend into, we do so.
// Whenever we finish processing a node, we move to our next sibling.
// If there is no next sibling, we move up the tree until there is a next
// sibling or we hit the top.
var node = sanitizedHtmlNode.firstChild, done = false;
if (!node)
return snippet;
while (!done) {
if (node.nodeType === ELEMENT_NODE) {
switch (node.tagName.toLowerCase()) {
// - Things that can't contain useful text.
// Avoid including block-quotes in the snippet.
case 'blockquote':
// The style does not belong in the snippet!
case 'style':
break;

default:
if (node.firstChild) {
node = node.firstChild;
continue;
}
break;
}
}
else if (node.nodeType === TEXT_NODE) {
// these text nodes can be ridiculously full of whitespace. Normalize
// the whitespace down to one whitespace character.
var normalizedText =
node.data.replace(RE_NORMALIZE_WHITESPACE, ' ');
// If the join would create two adjacents spaces, then skip the one
// on the thing we are concatenating.
if (snippet.length && normalizedText[0] === ' ' &&
snippet[snippet.length - 1] === ' ')
normalizedText = normalizedText.substring(1);
snippet += normalizedText;
if (snippet.length >= desiredLength)
break; // (exits the loop)
}

while (!node.nextSibling) {
node = node.parentNode;
if (node === sanitizedHtmlNode) {
// yeah, a goto or embedding this in a function might have been cleaner
done = true;
break;
}
}
if (!done)
node = node.nextSibling;
}

return snippet.substring(0, desiredLength);
return $bleach.generateSnippet(sanitizedHtml, desiredLength);
};

/**
Expand All @@ -532,38 +472,24 @@ exports.generateSnippet = function generateSnippet(sanitizedHtmlNode,
* about recipients having sufficient CSS support and our own desire to have
* things resemble text/plain.
*
* NB: simple escaping should also be fine, but this is unlikely to be a
* performance hotspot.
*/
exports.wrapTextIntoSafeHTMLString = function(text, wrapTag,
transformNewlines, attrs) {
if (transformNewlines === undefined)
transformNewlines = true;

var doc = document.implementation.createHTMLDocument(''),
wrapNode = doc.createElement(wrapTag || 'div');
wrapTag = wrapTag || 'div';

if (transformNewlines) {
var lines = text.split('\n');
for (var i = 0; i < lines.length; i++) {
var lineText = lines[i];
if (i)
wrapNode.appendChild(doc.createElement('br'));
if (lineText.length)
wrapNode.appendChild(doc.createTextNode(lineText));
}
}
else {
wrapNode.textContent = text;
}
text = transformNewLines ? text.replace(/\n/g, '<br/>') : text;

var attributes = '';
if (attrs) {
for (var iAttr = 0; iAttr < attrs.length; iAttr += 2) {
wrapNode.setAttribute(attrs[iAttr], attrs[iAttr + 1]);
for (var i = 0; i < attrs.length; i += 2) {
attributes += ' ' + attrs[i] + '="' + attrs[i + 1] +'"';
}
}

return wrapNode.outerHTML;
return '<' + wrapTag + attributes + '>' + text + '</' + wrapTag + '>';
};

var RE_QUOTE_CHAR = /"/g;
Expand Down

0 comments on commit 2189c38

Please sign in to comment.