Bug 1553265 - allow GeckoView error pages to request a certificate exception with window.postMessage; r?snorp
--- a/mobile/android/chrome/geckoview/GeckoViewContentChild.js
+++ b/mobile/android/chrome/geckoview/GeckoViewContentChild.js
@@ -412,26 +412,78 @@ class GeckoViewContentChild extends Geck
);
if (manifest) {
this.eventDispatcher.sendRequest({
type: "GeckoView:WebAppManifest",
manifest,
});
}
});
+
+ this.maybeAddErrorPageCertOverrideListener(docShell);
break;
}
case "MozFirstContentfulPaint":
this.eventDispatcher.sendRequest({
type: "GeckoView:FirstContentfulPaint",
});
break;
}
}
+ isErrorPageDocShell(docShell) {
+ // JS port of the value from nsDocShellLoadTypes.h
+ const LOAD_FLAGS_ERROR_PAGE = 0x0001;
+ const LOAD_ERROR_PAGE =
+ Ci.nsIDocShell.LOAD_CMD_NORMAL | (LOAD_FLAGS_ERROR_PAGE << 16);
+ return docShell.loadType === LOAD_ERROR_PAGE;
+ }
+
+ maybeAddErrorPageCertOverrideListener(docShell) {
+ // Only add the listener if the page is an error page. Note that we
+ // need to check the docShell's loadType, as by now the current window
+ // has changed to load our data-URL error page, so we can't just check
+ // if the URL is about:certerror/etc, nor test loadInfo.loadErrorPage.
+ if (!this.isErrorPageDocShell(docShell)) {
+ return;
+ }
+
+ // Also don't bother if there is no failed certificate to override.
+ const { failedChannel } = docShell;
+ if (!failedChannel || !failedChannel.securityInfo) {
+ return;
+ }
+
+ const serHelper = Cc[
+ "@mozilla.org/network/serialization-helper;1"
+ ].getService(Ci.nsISerializationHelper);
+ const securityInfo = serHelper.serializeToString(
+ failedChannel.securityInfo
+ );
+
+ const uri = failedChannel.QueryInterface(Ci.nsIHttpChannel).URI;
+
+ const msg = {
+ securityInfo,
+ host: uri.asciiHost,
+ port: uri.port,
+ };
+
+ addEventListener(
+ "message",
+ ({ data: { addCertException } }) => {
+ if (addCertException) {
+ msg.isTemporary = addCertException !== "permanent";
+ sendAsyncMessage("GeckoView:AddCertException", msg);
+ }
+ },
+ { capture: true }
+ );
+ }
+
// WebProgress event handler.
onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) {
debug`onLocationChange`;
if (this._savedState) {
const scrolldata = this._savedState.scrolldata;
if (scrolldata && scrolldata.zoom && scrolldata.zoom.displaySize) {
let utils = content.windowUtils;
--- a/mobile/android/modules/geckoview/GeckoViewContent.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewContent.jsm
@@ -48,16 +48,17 @@ class GeckoViewContent extends GeckoView
);
this.window.addEventListener(
"framefocusrequested",
this,
/* capture */ true,
/* untrusted */ false
);
+ this.messageManager.addMessageListener("GeckoView:AddCertException", this);
this.messageManager.addMessageListener("GeckoView:DOMFullscreenExit", this);
this.messageManager.addMessageListener(
"GeckoView:DOMFullscreenRequest",
this
);
Services.obs.addObserver(this, "oop-frameloader-crashed");
Services.obs.addObserver(this, "ipc:content-shutdown");
@@ -76,16 +77,20 @@ class GeckoViewContent extends GeckoView
);
this.window.removeEventListener(
"framefocusrequested",
this,
/* capture */ true
);
this.messageManager.removeMessageListener(
+ "GeckoView:AddCertException",
+ this
+ );
+ this.messageManager.removeMessageListener(
"GeckoView:DOMFullscreenExit",
this
);
this.messageManager.removeMessageListener(
"GeckoView:DOMFullscreenRequest",
this
);
@@ -180,16 +185,51 @@ class GeckoViewContent extends GeckoView
}
}
// Message manager event handler.
receiveMessage(aMsg) {
debug`receiveMessage: ${aMsg.name}`;
switch (aMsg.name) {
+ case "GeckoView:AddCertException":
+ // based on NetErrorParent.jsm#addCertException()
+ const serHelper = Cc[
+ "@mozilla.org/network/serialization-helper;1"
+ ].getService(Ci.nsISerializationHelper);
+
+ const securityInfo = serHelper.deserializeObject(
+ aMsg.data.securityInfo
+ );
+ securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+
+ const overrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+ ].getService(Ci.nsICertOverrideService);
+ let flags = 0;
+ if (securityInfo.isUntrusted) {
+ flags |= overrideService.ERROR_UNTRUSTED;
+ }
+ if (securityInfo.isDomainMismatch) {
+ flags |= overrideService.ERROR_MISMATCH;
+ }
+ if (securityInfo.isNotValidAtThisTime) {
+ flags |= overrideService.ERROR_TIME;
+ }
+
+ overrideService.rememberValidityOverride(
+ aMsg.data.host,
+ aMsg.data.port,
+ securityInfo.serverCert,
+ flags,
+ aMsg.data.isTemporary
+ );
+
+ aMsg.target.reload();
+ break;
case "GeckoView:DOMFullscreenExit":
this.window.windowUtils.remoteFrameFullscreenReverted();
break;
case "GeckoView:DOMFullscreenRequest":
this.window.windowUtils.remoteFrameFullscreenChanged(aMsg.target);
break;
}
}