Source code
Revision control
Copy as Markdown
Other Tools
// -*- 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
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});
// This object implements the JS parts of nsIWebNavigation.
export class RemoteWebNavigation {
constructor(aBrowser) {
this._browser = aBrowser;
this._cancelContentJSEpoch = 1;
this._currentURI = null;
this._canGoBack = false;
this._canGoBackIgnoringUserInteraction = false;
this._canGoForward = false;
this.referringURI = null;
}
swapBrowser(aBrowser) {
this._browser = aBrowser;
}
maybeCancelContentJSExecution(aNavigationType, aOptions = {}) {
const epoch = this._cancelContentJSEpoch++;
this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
aNavigationType,
{ ...aOptions, epoch }
);
return epoch;
}
get canGoBack() {
if (Services.appinfo.sessionHistoryInParent) {
const sessionHistory = this._browser.browsingContext.sessionHistory;
return sessionHistory?.canGoBackFromEntryAtIndex(sessionHistory?.index);
}
return this._canGoBack;
}
get canGoBackIgnoringUserInteraction() {
if (Services.appinfo.sessionHistoryInParent) {
const sessionHistory = this._browser.browsingContext.sessionHistory;
return sessionHistory?.index > 0;
}
return this._canGoBackIgnoringUserInteraction;
}
get canGoForward() {
if (Services.appinfo.sessionHistoryInParent) {
let sessionHistory = this._browser.browsingContext.sessionHistory;
return sessionHistory?.index < sessionHistory?.count - 1;
}
return this._canGoForward;
}
goBack(requireUserInteraction = false) {
let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
Ci.nsIRemoteTab.NAVIGATE_BACK
);
this._browser.browsingContext.goBack(
cancelContentJSEpoch,
requireUserInteraction,
true
);
}
goForward(requireUserInteraction = false) {
let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
Ci.nsIRemoteTab.NAVIGATE_FORWARD
);
this._browser.browsingContext.goForward(
cancelContentJSEpoch,
requireUserInteraction,
true
);
}
gotoIndex(aIndex) {
let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
Ci.nsIRemoteTab.NAVIGATE_INDEX,
{ index: aIndex }
);
this._browser.browsingContext.goToIndex(aIndex, cancelContentJSEpoch, true);
}
_speculativeConnect(uri, loadURIOptions) {
try {
// Let's start a network connection before the content process asks.
// Note that we might have already set up the speculative connection in
// some cases, especially when the url is from location bar or its popup
// menu.
if (uri.schemeIs("http") || uri.schemeIs("https")) {
let isBrowserPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate(
this._browser
);
let principal = loadURIOptions.triggeringPrincipal;
// We usually have a triggeringPrincipal assigned, but in case we
// don't have one or if it's a SystemPrincipal, let's create it with OA
// inferred from the current context.
if (!principal || principal.isSystemPrincipal) {
let attrs = {
userContextId: this._browser.getAttribute("usercontextid") || 0,
privateBrowsingId: isBrowserPrivate ? 1 : 0,
};
principal = Services.scriptSecurityManager.createContentPrincipal(
uri,
attrs
);
}
Services.io.speculativeConnect(uri, principal, null, false);
}
} catch (ex) {
// Can't setup speculative connection for this uri for some
// reason, just ignore it.
}
}
loadURI(uri, loadURIOptions) {
this._speculativeConnect(uri, loadURIOptions);
let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
Ci.nsIRemoteTab.NAVIGATE_URL,
{ uri }
);
this._browser.browsingContext.loadURI(uri, {
...loadURIOptions,
cancelContentJSEpoch,
});
}
fixupAndLoadURIString(uriString, loadURIOptions) {
let uri;
try {
let fixupFlags = Services.uriFixup.webNavigationFlagsToFixupFlags(
uriString,
loadURIOptions.loadFlags
);
let isBrowserPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate(
this._browser
);
if (isBrowserPrivate) {
fixupFlags |= Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT;
}
uri = Services.uriFixup.getFixupURIInfo(
uriString,
fixupFlags
).preferredURI;
} catch (ex) {
// In rare cases `uriFixup` can throw. We ignore this here, but it's
// likely that the fixupAndLoadURIString call below will still throw,
// hopefully with more details.
}
if (uri) {
this._speculativeConnect(uri, loadURIOptions);
}
let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
Ci.nsIRemoteTab.NAVIGATE_URL,
{ uri }
);
// For now, continue to use fixup here, but note that ideally we should be
// doing fixup only once and reusing the URI we created above. Addressing
this._browser.browsingContext.fixupAndLoadURIString(uriString, {
...loadURIOptions,
cancelContentJSEpoch,
});
}
reload(aReloadFlags) {
this._browser.browsingContext.reload(aReloadFlags);
}
stop(aStopFlags) {
this._browser.browsingContext.stop(aStopFlags);
}
get document() {
return this._browser.contentDocument;
}
get currentURI() {
if (!this._currentURI) {
this._currentURI = Services.io.newURI("about:blank");
}
return this._currentURI;
}
set currentURI(aURI) {
let loadURIOptions = {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
};
this.loadURI(aURI.spec, loadURIOptions);
}
// done in content scripts.
get sessionHistory() {
throw new Components.Exception(
"Not implemented",
Cr.NS_ERROR_NOT_IMPLEMENTED
);
}
set sessionHistory(aValue) {
throw new Components.Exception(
"Not implemented",
Cr.NS_ERROR_NOT_IMPLEMENTED
);
}
_sendMessage(aMessage, aData) {
try {
this._browser.sendMessageToActor(aMessage, aData, "WebNavigation");
} catch (e) {
console.error(e);
}
}
}