Bug 1374382 - [WIP] Defer DOMLinkAdded and DOMLinkChanged reactions until an idlecallback.
MozReview-Commit-ID: FqI6PsUrGsB
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -663,17 +663,22 @@ var ClickEventHandler = {
// callers expect <a>-like elements.
// Note: makeURI() will throw if aUri is not a valid URI.
return [href ? Services.io.newURI(href, null, baseURI).spec : null, null,
node && node.ownerDocument.nodePrincipal];
}
};
ClickEventHandler.init();
-ContentLinkHandler.init(this);
+var contentLinkHandler;
+
+if (content === content.top) {
+ contentLinkHandler = new ContentLinkHandler(this);
+}
+//ContentLinkHandler.init(this);
ContentMetaHandler.init(this);
// TODO: Load this lazily so the JSM is run only if a relevant event/message fires.
var pluginContent = new PluginContent(global);
addEventListener("DOMWindowFocus", function(event) {
sendAsyncMessage("DOMWindowFocus", {});
}, false);
--- a/browser/modules/ContentLinkHandler.jsm
+++ b/browser/modules/ContentLinkHandler.jsm
@@ -263,36 +263,139 @@ function handleFaviconLink(aLink, aIsRic
let timer = setTimeout(() => faviconTimeoutCallback(aFaviconLoads, pageUrl, aChromeGlobal),
FAVICON_PARSING_TIMEOUT);
let load = { timer, iconInfos: [iconInfo] };
aFaviconLoads.set(pageUrl, load);
}
return true;
}
-this.ContentLinkHandler = {
- init(chromeGlobal) {
- const faviconLoads = new Map();
- chromeGlobal.addEventListener("DOMLinkAdded", event => {
- this.onLinkEvent(event, chromeGlobal, faviconLoads);
- });
- chromeGlobal.addEventListener("DOMLinkChanged", event => {
- this.onLinkEvent(event, chromeGlobal, faviconLoads);
- });
- chromeGlobal.addEventListener("unload", event => {
- for (const [pageUrl, load] of faviconLoads) {
+this.ContentLinkHandler = function(global) {
+ this.init(global);
+};
+
+ContentLinkHandler.prototype = {
+ init(global) {
+ this.global = global;
+ this.content = this.global.content;
+ this.reset();
+
+ global.addEventListener("DOMLinkAdded", this);
+ global.addEventListener("DOMLinkChanged", this);
+ global.addEventListener("pagehide", this);
+ global.addEventListener("unload", this);
+ global.addEventListener("load", this, true);
+ },
+
+ uninit() {
+ let global = this.global;
+
+ global.removeEventListener("DOMLinkAdded", this);
+ global.removeEventListener("DOMLinkChanged", this);
+ global.removeEventListener("pagehide", this);
+ global.removeEventListener("unload", this);
+ global.removeEventListener("load", this, true);
+
+ delete this.eventQueue;
+ delete this.faviconLoads;
+ delete this.idleTimerId;
+ delete this.content;
+ delete this.global;
+ },
+
+ reset() {
+ this.cancelDeferredFlush();
+
+ if (this.faviconLoads) {
+ for (const [pageUrl, load] of this.faviconLoads) {
load.timer.cancel();
load.timer = null;
- faviconLoads.delete(pageUrl);
+ this.faviconLoads.delete(pageUrl);
+ }
+ }
+
+ this.waitingForLoad = true;
+ this.eventQueue = [];
+ this.faviconLoads = new Map();
+ this.idleTimerId = null;
+ },
+
+ handleEvent(event) {
+ let window = event.originalTarget.ownerGlobal;
+ if (window && window.top !== window) {
+ return;
+ }
+
+ switch (event.type) {
+ case "DOMLinkChanged":
+ // fall-through
+ case "DOMLinkAdded": {
+ this.queueEvent(event);
+ break;
+ }
+
+ case "load": {
+ if (this.waitingForLoad) {
+ this.waitingForLoad = false;
+ this.setDeferredFlush();
+ }
+ break;
+ }
+
+ case "pagehide": {
+ this.reset();
+ break;
}
+ case "unload": {
+ this.uninit();
+ break;
+ }
+ }
+ },
+
+ cancelDeferredFlush() {
+ if (this.idleTimerId) {
+ this.content.cancelIdleCallback(this.idleTimerId);
+ this.idleTimerId = null;
+ }
+ },
+
+ setDeferredFlush() {
+ if (this.waitingForLoad) {
+ return;
+ }
+
+ if (this.idleTimerId) {
+ this.cancelDeferredFlush();
+ }
+
+ this.idleTimerId = this.content.requestIdleCallback(idleDeadline => {
+ this.flush(idleDeadline);
});
},
+ queueEvent(event) {
+ this.eventQueue.push(event);
+ this.setDeferredFlush();
+ },
+
+ flush(idleDeadline) {
+ while (idleDeadline.timeRemaining() && this.eventQueue.length > 0) {
+ let event = this.eventQueue.shift();
+ this.onLinkEvent(event, this.global, this.faviconLoads);
+ }
+
+ if (this.eventQueue.length > 0) {
+ this.setDeferredFlush();
+ }
+ },
+
onLinkEvent(event, chromeGlobal, faviconLoads) {
var link = event.originalTarget;
+
var rel = link.rel && link.rel.toLowerCase();
if (!link || !link.ownerDocument || !rel || !link.href)
return;
// Ignore sub-frames (bugs 305472, 479408).
let window = link.ownerGlobal;
if (window != window.top)
return;