perf(autolinker): Use requestIdleCallback to fix hanging on large bodies

This commit is contained in:
Ben Gotow 2016-03-23 19:02:51 -07:00
parent 75c9b116bb
commit 75ff8282a8
4 changed files with 28 additions and 8 deletions

View file

@ -28,7 +28,7 @@ function _runOnTextNode(node, matchers) {
} }
} }
export function autolink(doc) { export function autolink(doc, {async} = {}) {
// Traverse the new DOM tree and make things that look like links clickable, // Traverse the new DOM tree and make things that look like links clickable,
// and ensure anything with an href has a title attribute. // and ensure anything with an href has a title attribute.
const textWalker = document.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT); const textWalker = document.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT);
@ -38,8 +38,21 @@ export function autolink(doc) {
['', RegExpUtils.urlRegex({matchEntireString: false})], ['', RegExpUtils.urlRegex({matchEntireString: false})],
]; ];
while (textWalker.nextNode()) { if (async) {
_runOnTextNode(textWalker.currentNode, matchers); const fn = (deadline) => {
while (textWalker.nextNode()) {
_runOnTextNode(textWalker.currentNode, matchers);
if (deadline.timeRemaining() <= 0) {
window.requestIdleCallback(fn, {timeout: 500});
return;
}
}
};
window.requestIdleCallback(fn, {timeout: 500});
} else {
while (textWalker.nextNode()) {
_runOnTextNode(textWalker.currentNode, matchers);
}
} }
// Traverse the new DOM tree and make sure everything with an href has a title. // Traverse the new DOM tree and make sure everything with an href has a title.

View file

@ -65,7 +65,7 @@ export default class EmailFrame extends React.Component {
doc.write(`<div id='inbox-html-wrapper'>${this._emailContent()}</div>`); doc.write(`<div id='inbox-html-wrapper'>${this._emailContent()}</div>`);
doc.close(); doc.close();
autolink(doc); autolink(doc, {async: true});
// Notify the EventedIFrame that we've replaced it's document (with `open`) // Notify the EventedIFrame that we've replaced it's document (with `open`)
// so it can attach event listeners again. // so it can attach event listeners again.

View file

@ -55,7 +55,7 @@ describe "AccountStore", ->
(new Account).fromJSON(@configAccounts[1]) (new Account).fromJSON(@configAccounts[1])
]) ])
it "should initialize tokens from config, if present, save them to keytar, and remove them from config", -> it "should initialize tokens from config, if present, and save them to keytar", ->
@configTokens = {'A': 'A-TOKEN'} @configTokens = {'A': 'A-TOKEN'}
@instance = new @constructor @instance = new @constructor
expect(@instance.tokenForAccountId('A')).toEqual('A-TOKEN') expect(@instance.tokenForAccountId('A')).toEqual('A-TOKEN')

View file

@ -35,6 +35,9 @@ class MessageBodyProcessor {
return; return;
} }
// grab the old value
const oldOutput = this._recentlyProcessedD[changedKey].body;
// remove the message from the cache // remove the message from the cache
delete this._recentlyProcessedD[changedKey]; delete this._recentlyProcessedD[changedKey];
this._recentlyProcessedA = this._recentlyProcessedA.filter(({key}) => this._recentlyProcessedA = this._recentlyProcessedA.filter(({key}) =>
@ -52,9 +55,13 @@ class MessageBodyProcessor {
const updatedMessage = changedMessage.clone(); const updatedMessage = changedMessage.clone();
updatedMessage.body = updatedMessage.body || subscriptions[0].message.body; updatedMessage.body = updatedMessage.body || subscriptions[0].message.body;
const output = this.retrieve(updatedMessage); const output = this.retrieve(updatedMessage);
for (const subscription of subscriptions) {
subscription.callback(output); // only trigger if the output has really changed
subscription.message = updatedMessage; if (output !== oldOutput) {
for (const subscription of subscriptions) {
subscription.callback(output);
subscription.message = updatedMessage;
}
} }
} }
} }