fune/browser/base/content/content.js
Kris Maglione da381d04b7 Bug 1474155: Part 5 - Null out frame script singleton init methods after they're called. r=mconley
Believe it or not, the memory these references hold alive is significant.
Nulling them out saves 5-10K per process.

MozReview-Commit-ID: JONjE48yE8I

--HG--
extra : rebase_source : 49adc538070eecb9183e6e052e6e43db9c4c7a99
extra : histedit_source : 699f49fad1bfa69b8c511bf96187096e751c8606
2018-07-07 20:49:30 -07:00

396 lines
12 KiB
JavaScript

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* This content script should work in any browser or iframe and should not
* depend on the frame being contained in tabbrowser. */
/* eslint-env mozilla/frame-script */
/* eslint no-unused-vars: ["error", {args: "none"}] */
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
// TabChildGlobal
var global = this;
XPCOMUtils.defineLazyModuleGetters(this, {
BlockedSiteContent: "resource:///modules/BlockedSiteContent.jsm",
ContentLinkHandler: "resource:///modules/ContentLinkHandler.jsm",
ContentMetaHandler: "resource:///modules/ContentMetaHandler.jsm",
ContentWebRTC: "resource:///modules/ContentWebRTC.jsm",
LoginFormFactory: "resource://gre/modules/LoginManagerContent.jsm",
InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.jsm",
PluginContent: "resource:///modules/PluginContent.jsm",
FormSubmitObserver: "resource:///modules/FormSubmitObserver.jsm",
NetErrorContent: "resource:///modules/NetErrorContent.jsm",
PageMetadata: "resource://gre/modules/PageMetadata.jsm",
WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
ContextMenu: "resource:///modules/ContextMenu.jsm",
});
XPCOMUtils.defineLazyProxy(this, "contextMenu", () => {
return new ContextMenu(global);
});
XPCOMUtils.defineLazyProxy(this, "ClickEventHandler", () => {
let tmp = {};
ChromeUtils.import("resource:///modules/ClickEventHandler.jsm", tmp);
return new tmp.ClickEventHandler(global);
});
XPCOMUtils.defineLazyGetter(this, "LoginManagerContent", () => {
let tmp = {};
ChromeUtils.import("resource://gre/modules/LoginManagerContent.jsm", tmp);
tmp.LoginManagerContent.setupEventListeners(global);
return tmp.LoginManagerContent;
});
XPCOMUtils.defineLazyProxy(this, "formSubmitObserver", () => {
return new FormSubmitObserver(content, this);
}, {
// stub QI
QueryInterface: ChromeUtils.generateQI([Ci.nsIFormSubmitObserver, Ci.nsISupportsWeakReference])
});
XPCOMUtils.defineLazyProxy(this, "PageInfoListener",
"resource:///modules/PageInfoListener.jsm");
XPCOMUtils.defineLazyProxy(this, "LightWeightThemeWebInstallListener",
"resource:///modules/LightWeightThemeWebInstallListener.jsm");
Services.els.addSystemEventListener(global, "contextmenu", contextMenu, false);
Services.obs.addObserver(formSubmitObserver, "invalidformsubmit", true);
addMessageListener("PageInfo:getData", PageInfoListener);
// NOTE: Much of this logic is duplicated in BrowserCLH.js for Android.
addMessageListener("RemoteLogins:fillForm", function(message) {
// intercept if ContextMenu.jsm had sent a plain object for remote targets
message.objects.inputElement = contextMenu.getTarget(message, "inputElement");
LoginManagerContent.receiveMessage(message, content);
});
addEventListener("DOMFormHasPassword", function(event) {
LoginManagerContent.onDOMFormHasPassword(event, content);
let formLike = LoginFormFactory.createFromForm(event.originalTarget);
InsecurePasswordUtils.reportInsecurePasswords(formLike);
});
addEventListener("DOMInputPasswordAdded", function(event) {
LoginManagerContent.onDOMInputPasswordAdded(event, content);
let formLike = LoginFormFactory.createFromField(event.originalTarget);
InsecurePasswordUtils.reportInsecurePasswords(formLike);
});
addEventListener("DOMAutoComplete", function(event) {
LoginManagerContent.onUsernameInput(event);
});
var AboutBlockedSiteListener = {
init(chromeGlobal) {
addMessageListener("DeceptiveBlockedDetails", this);
chromeGlobal.addEventListener("AboutBlockedLoaded", this, false, true);
this.init = null;
},
get isBlockedSite() {
return content.document.documentURI.startsWith("about:blocked");
},
receiveMessage(msg) {
if (!this.isBlockedSite) {
return;
}
BlockedSiteContent.receiveMessage(global, msg);
},
handleEvent(aEvent) {
if (!this.isBlockedSite) {
return;
}
if (aEvent.type != "AboutBlockedLoaded") {
return;
}
BlockedSiteContent.handleEvent(global, aEvent);
},
};
AboutBlockedSiteListener.init(this);
this.AboutNetAndCertErrorListener = {
init(chromeGlobal) {
addMessageListener("CertErrorDetails", this);
addMessageListener("Browser:CaptivePortalFreed", this);
chromeGlobal.addEventListener("AboutNetErrorLoad", this, false, true);
chromeGlobal.addEventListener("AboutNetErrorOpenCaptivePortal", this, false, true);
chromeGlobal.addEventListener("AboutNetErrorSetAutomatic", this, false, true);
chromeGlobal.addEventListener("AboutNetErrorResetPreferences", this, false, true);
this.init = null;
},
isAboutNetError(doc) {
return doc.documentURI.startsWith("about:neterror");
},
isAboutCertError(doc) {
return doc.documentURI.startsWith("about:certerror");
},
receiveMessage(msg) {
if (msg.name == "CertErrorDetails") {
let frameDocShell = WebNavigationFrames.findDocShell(msg.data.frameId, docShell);
// We need nsIWebNavigation to access docShell.document.
frameDocShell && frameDocShell.QueryInterface(Ci.nsIWebNavigation);
if (!frameDocShell || !this.isAboutCertError(frameDocShell.document)) {
return;
}
NetErrorContent.onCertErrorDetails(global, msg, frameDocShell);
} else if (msg.name == "Browser:CaptivePortalFreed") {
// TODO: This check is not correct for frames.
if (!this.isAboutCertError(content.document)) {
return;
}
this.onCaptivePortalFreed(msg);
}
},
onCaptivePortalFreed(msg) {
content.dispatchEvent(new content.CustomEvent("AboutNetErrorCaptivePortalFreed"));
},
handleEvent(aEvent) {
// Documents have a null ownerDocument.
let doc = aEvent.originalTarget.ownerDocument || aEvent.originalTarget;
if (!this.isAboutNetError(doc) && !this.isAboutCertError(doc)) {
return;
}
NetErrorContent.handleEvent(global, aEvent);
},
};
AboutNetAndCertErrorListener.init(this);
Services.els.addSystemEventListener(global, "click", ClickEventHandler, true);
new ContentLinkHandler(this);
ContentMetaHandler.init(this);
var PluginContentStub = {
EVENTS: [
"PluginCrashed",
"PluginOutdated",
"PluginInstantiated",
"PluginRemoved",
"HiddenPlugin",
],
MESSAGES: [
"BrowserPlugins:ActivatePlugins",
"BrowserPlugins:NotificationShown",
"BrowserPlugins:ContextMenuCommand",
"BrowserPlugins:NPAPIPluginProcessCrashed",
"BrowserPlugins:CrashReportSubmitted",
"BrowserPlugins:Test:ClearCrashData",
],
_pluginContent: null,
get pluginContent() {
if (!this._pluginContent) {
this._pluginContent = new PluginContent(global);
}
return this._pluginContent;
},
init() {
addEventListener("unload", this);
addEventListener("PluginBindingAttached", this, true, true);
for (let event of this.EVENTS) {
addEventListener(event, this, true);
}
for (let msg of this.MESSAGES) {
addMessageListener(msg, this);
}
Services.obs.addObserver(this, "decoder-doctor-notification");
this.init = null;
},
uninit() {
Services.obs.removeObserver(this, "decoder-doctor-notification");
},
observe(subject, topic, data) {
return this.pluginContent.observe(subject, topic, data);
},
handleEvent(event) {
if (event.type === "unload") {
return this.uninit();
}
return this.pluginContent.handleEvent(event);
},
receiveMessage(msg) {
return this.pluginContent.receiveMessage(msg);
},
};
PluginContentStub.init();
// This is a temporary hack to prevent regressions (bug 1471327).
void content;
addEventListener("DOMWindowFocus", function(event) {
sendAsyncMessage("DOMWindowFocus", {});
}, false);
// We use this shim so that ContentWebRTC.jsm will not be loaded until
// it is actually needed.
var ContentWebRTCShim = message => ContentWebRTC.receiveMessage(message);
addMessageListener("rtcpeer:Allow", ContentWebRTCShim);
addMessageListener("rtcpeer:Deny", ContentWebRTCShim);
addMessageListener("webrtc:Allow", ContentWebRTCShim);
addMessageListener("webrtc:Deny", ContentWebRTCShim);
addMessageListener("webrtc:StopSharing", ContentWebRTCShim);
var PageMetadataMessenger = {
init() {
addMessageListener("PageMetadata:GetPageData", this);
addMessageListener("PageMetadata:GetMicroformats", this);
this.init = null;
},
receiveMessage(message) {
switch (message.name) {
case "PageMetadata:GetPageData": {
let target = contextMenu.getTarget(message);
let result = PageMetadata.getData(content.document, target);
sendAsyncMessage("PageMetadata:PageDataResult", result);
break;
}
case "PageMetadata:GetMicroformats": {
let target = contextMenu.getTarget(message);
let result = PageMetadata.getMicroformats(content.document, target);
sendAsyncMessage("PageMetadata:MicroformatsResult", result);
break;
}
}
}
};
PageMetadataMessenger.init();
addEventListener("InstallBrowserTheme", LightWeightThemeWebInstallListener, false, true);
addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstallListener, false, true);
addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstallListener, false, true);
let OfflineApps = {
_docId: 0,
_docIdMap: new Map(),
_docManifestSet: new Set(),
_observerAdded: false,
registerWindow(aWindow) {
if (!this._observerAdded) {
this._observerAdded = true;
Services.obs.addObserver(this, "offline-cache-update-completed", true);
}
let manifestURI = this._getManifestURI(aWindow);
this._docManifestSet.add(manifestURI.spec);
},
handleEvent(event) {
if (event.type == "MozApplicationManifest") {
this.offlineAppRequested(event.originalTarget.defaultView);
}
},
_getManifestURI(aWindow) {
if (!aWindow.document.documentElement)
return null;
var attr = aWindow.document.documentElement.getAttribute("manifest");
if (!attr)
return null;
try {
return Services.io.newURI(attr, aWindow.document.characterSet,
Services.io.newURI(aWindow.location.href));
} catch (e) {
return null;
}
},
offlineAppRequested(aContentWindow) {
this.registerWindow(aContentWindow);
if (!Services.prefs.getBoolPref("browser.offline-apps.notify")) {
return;
}
let currentURI = aContentWindow.document.documentURIObject;
// don't bother showing UI if the user has already made a decision
if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION)
return;
try {
if (Services.prefs.getBoolPref("offline-apps.allow_by_default")) {
// all pages can use offline capabilities, no need to ask the user
return;
}
} catch (e) {
// this pref isn't set by default, ignore failures
}
let docId = ++this._docId;
this._docIdMap.set(docId, Cu.getWeakReference(aContentWindow.document));
sendAsyncMessage("OfflineApps:RequestPermission", {
uri: currentURI.spec,
docId,
});
},
_startFetching(aDocument) {
if (!aDocument.documentElement)
return;
let manifestURI = this._getManifestURI(aDocument.defaultView);
if (!manifestURI)
return;
var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject,
aDocument.nodePrincipal, aDocument.defaultView);
},
receiveMessage(aMessage) {
if (aMessage.name == "OfflineApps:StartFetching") {
let doc = this._docIdMap.get(aMessage.data.docId);
doc = doc && doc.get();
if (doc) {
this._startFetching(doc);
}
this._docIdMap.delete(aMessage.data.docId);
}
},
observe(aSubject, aTopic, aState) {
if (aTopic == "offline-cache-update-completed") {
let cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
let uri = cacheUpdate.manifestURI;
if (uri && this._docManifestSet.has(uri.spec)) {
sendAsyncMessage("OfflineApps:CheckUsage", {uri: uri.spec});
}
}
},
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
};
addEventListener("MozApplicationManifest", OfflineApps, false);
addMessageListener("OfflineApps:StartFetching", OfflineApps);