Files
tubestation/browser/modules/URILoadingHelper.sys.mjs
Gijs Kruitbosch 2fbd8150f1 Bug 1817443 - remove openUILinkIn entirely and rename fromChrome, r=mossop,extension-reviewers,rpl
'fromChrome' really meant "force tabs to open in the foreground", so let's
rename it accordingly.

This removes the attempt to document arguments for openUILinkIn.
I'll add documentation back on the end of this stack, for openLinkIn, when
various bits are reorganized anyway.

Depends on D170210

Differential Revision: https://phabricator.services.mozilla.com/D170384
2023-02-23 14:49:28 +00:00

593 lines
20 KiB
JavaScript

/* 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/. */
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { BrowserUtils } from "resource://gre/modules/BrowserUtils.sys.mjs";
import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = {};
XPCOMUtils.defineLazyModuleGetters(lazy, {
AboutNewTab: "resource:///modules/AboutNewTab.jsm",
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
});
XPCOMUtils.defineLazyGetter(lazy, "ReferrerInfo", () =>
Components.Constructor(
"@mozilla.org/referrer-info;1",
"nsIReferrerInfo",
"init"
)
);
export const URILoadingHelper = {
/* eslint-disable complexity */
openLinkIn(window, url, where, params) {
if (!where || !url) {
return;
}
var aForceForeground = params.forceForeground;
var aAllowThirdPartyFixup = params.allowThirdPartyFixup;
var aPostData = params.postData;
var aCharset = params.charset;
var aReferrerInfo = params.referrerInfo
? params.referrerInfo
: new lazy.ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, null);
var aRelatedToCurrent = params.relatedToCurrent;
var aAllowInheritPrincipal = !!params.allowInheritPrincipal;
var aForceAllowDataURI = params.forceAllowDataURI;
var aInBackground = params.inBackground;
var aInitiatingDoc = params.initiatingDoc;
var aIsPrivate = params.private;
var aForceNonPrivate = params.forceNonPrivate;
var aSkipTabAnimation = params.skipTabAnimation;
var aAllowPinnedTabHostChange = !!params.allowPinnedTabHostChange;
var aAllowPopups = !!params.allowPopups;
var aUserContextId = params.userContextId;
var aIndicateErrorPageLoad = params.indicateErrorPageLoad;
var aPrincipal = params.originPrincipal;
var aStoragePrincipal = params.originStoragePrincipal;
var aTriggeringPrincipal = params.triggeringPrincipal;
var aTriggeringRemoteType = params.triggeringRemoteType;
var aCsp = params.csp;
var aForceAboutBlankViewerInCurrent = params.forceAboutBlankViewerInCurrent;
var aResolveOnNewTabCreated = params.resolveOnNewTabCreated;
// This callback will be called with the content browser once it's created.
var aResolveOnContentBrowserReady = params.resolveOnContentBrowserCreated;
var aGlobalHistoryOptions = params.globalHistoryOptions;
if (!aTriggeringPrincipal) {
throw new Error("Must load with a triggering Principal");
}
if (where == "save") {
if ("isContentWindowPrivate" in params) {
window.saveURL(
url,
null,
null,
null,
true,
true,
aReferrerInfo,
null,
null,
params.isContentWindowPrivate,
aPrincipal
);
} else {
if (!aInitiatingDoc) {
console.error(
"openUILink/openLinkIn was called with " +
"where == 'save' but without initiatingDoc. See bug 814264."
);
return;
}
window.saveURL(
url,
null,
null,
null,
true,
true,
aReferrerInfo,
null,
aInitiatingDoc
);
}
return;
}
// Establish which window we'll load the link in.
let w;
if (where == "current" && params.targetBrowser) {
w = params.targetBrowser.ownerGlobal;
} else {
w = this.getTargetWindow(window, { forceNonPrivate: aForceNonPrivate });
}
// We don't want to open tabs in popups, so try to find a non-popup window in
// that case.
if ((where == "tab" || where == "tabshifted") && w && !w.toolbar.visible) {
w = this.getTargetWindow(window, {
skipPopups: true,
forceNonPrivate: aForceNonPrivate,
});
aRelatedToCurrent = false;
}
// Teach the principal about the right OA to use, e.g. in case when
// opening a link in a new private window, or in a new container tab.
// Please note we do not have to do that for SystemPrincipals and we
// can not do it for NullPrincipals since NullPrincipals are only
// identical if they actually are the same object (See Bug: 1346759)
function useOAForPrincipal(principal) {
if (principal && principal.isContentPrincipal) {
let attrs = {
userContextId: aUserContextId,
privateBrowsingId:
aIsPrivate || (w && PrivateBrowsingUtils.isWindowPrivate(w)),
firstPartyDomain: principal.originAttributes.firstPartyDomain,
};
return Services.scriptSecurityManager.principalWithOA(principal, attrs);
}
return principal;
}
aPrincipal = useOAForPrincipal(aPrincipal);
aStoragePrincipal = useOAForPrincipal(aStoragePrincipal);
aTriggeringPrincipal = useOAForPrincipal(aTriggeringPrincipal);
if (!w || where == "window") {
let features = "chrome,dialog=no,all";
if (aIsPrivate) {
features += ",private";
// To prevent regular browsing data from leaking to private browsing sites,
// strip the referrer when opening a new private window. (See Bug: 1409226)
aReferrerInfo = new lazy.ReferrerInfo(
aReferrerInfo.referrerPolicy,
false,
aReferrerInfo.originalReferrer
);
} else if (aForceNonPrivate) {
features += ",non-private";
}
// This propagates to window.arguments.
var sa = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
var wuri = Cc["@mozilla.org/supports-string;1"].createInstance(
Ci.nsISupportsString
);
wuri.data = url;
let extraOptions = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
Ci.nsIWritablePropertyBag2
);
if (aTriggeringRemoteType) {
extraOptions.setPropertyAsACString(
"triggeringRemoteType",
aTriggeringRemoteType
);
}
if (params.hasValidUserGestureActivation !== undefined) {
extraOptions.setPropertyAsBool(
"hasValidUserGestureActivation",
params.hasValidUserGestureActivation
);
}
if (aForceAllowDataURI) {
extraOptions.setPropertyAsBool("forceAllowDataURI", true);
}
if (params.fromExternal !== undefined) {
extraOptions.setPropertyAsBool("fromExternal", params.fromExternal);
}
if (aGlobalHistoryOptions?.triggeringSponsoredURL) {
extraOptions.setPropertyAsACString(
"triggeringSponsoredURL",
aGlobalHistoryOptions.triggeringSponsoredURL
);
if (aGlobalHistoryOptions.triggeringSponsoredURLVisitTimeMS) {
extraOptions.setPropertyAsUint64(
"triggeringSponsoredURLVisitTimeMS",
aGlobalHistoryOptions.triggeringSponsoredURLVisitTimeMS
);
}
}
var allowThirdPartyFixupSupports = Cc[
"@mozilla.org/supports-PRBool;1"
].createInstance(Ci.nsISupportsPRBool);
allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup;
var userContextIdSupports = Cc[
"@mozilla.org/supports-PRUint32;1"
].createInstance(Ci.nsISupportsPRUint32);
userContextIdSupports.data = aUserContextId;
sa.appendElement(wuri);
sa.appendElement(extraOptions);
sa.appendElement(aReferrerInfo);
sa.appendElement(aPostData);
sa.appendElement(allowThirdPartyFixupSupports);
sa.appendElement(userContextIdSupports);
sa.appendElement(aPrincipal);
sa.appendElement(aStoragePrincipal);
sa.appendElement(aTriggeringPrincipal);
sa.appendElement(null); // allowInheritPrincipal
sa.appendElement(aCsp);
const sourceWindow = w || window;
let win;
// Returns a promise that will be resolved when the new window's startup is finished.
function waitForWindowStartup() {
return new Promise(resolve => {
const delayedStartupObserver = aSubject => {
if (aSubject == win) {
Services.obs.removeObserver(
delayedStartupObserver,
"browser-delayed-startup-finished"
);
resolve();
}
};
Services.obs.addObserver(
delayedStartupObserver,
"browser-delayed-startup-finished"
);
});
}
if (params.frameID != undefined && sourceWindow) {
// Only notify it as a WebExtensions' webNavigation.onCreatedNavigationTarget
// event if it contains the expected frameID params.
// (e.g. we should not notify it as a onCreatedNavigationTarget if the user is
// opening a new window using the keyboard shortcut).
const sourceTabBrowser = sourceWindow.gBrowser.selectedBrowser;
waitForWindowStartup().then(() => {
Services.obs.notifyObservers(
{
wrappedJSObject: {
url,
createdTabBrowser: win.gBrowser.selectedBrowser,
sourceTabBrowser,
sourceFrameID: params.frameID,
},
},
"webNavigation-createdNavigationTarget"
);
});
}
if (aResolveOnContentBrowserReady) {
waitForWindowStartup().then(() =>
aResolveOnContentBrowserReady(win.gBrowser.selectedBrowser)
);
}
win = Services.ww.openWindow(
sourceWindow,
AppConstants.BROWSER_CHROME_URL,
null,
features,
sa
);
return;
}
// We're now committed to loading the link in an existing browser window.
// Raise the target window before loading the URI, since loading it may
// result in a new frontmost window (e.g. "javascript:window.open('');").
w.focus();
let targetBrowser;
let loadInBackground;
let uriObj;
if (where == "current") {
targetBrowser = params.targetBrowser || w.gBrowser.selectedBrowser;
loadInBackground = false;
try {
uriObj = Services.io.newURI(url);
} catch (e) {}
// In certain tabs, we restrict what if anything may replace the loaded
// page. If a load request bounces off for the currently selected tab,
// we'll open a new tab instead.
let tab = w.gBrowser.getTabForBrowser(targetBrowser);
if (tab == w.FirefoxViewHandler.tab) {
where = "tab";
targetBrowser = null;
} else if (
!aAllowPinnedTabHostChange &&
tab.pinned &&
url != "about:crashcontent"
) {
try {
// nsIURI.host can throw for non-nsStandardURL nsIURIs.
if (
!uriObj ||
(!uriObj.schemeIs("javascript") &&
targetBrowser.currentURI.host != uriObj.host)
) {
where = "tab";
targetBrowser = null;
}
} catch (err) {
where = "tab";
targetBrowser = null;
}
}
} else {
// `where` is "tab" or "tabshifted", so we'll load the link in a new tab.
loadInBackground = aInBackground;
if (loadInBackground == null) {
loadInBackground = aForceForeground
? false
: Services.prefs.getBoolPref("browser.tabs.loadInBackground");
}
}
let focusUrlBar = false;
switch (where) {
case "current":
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
if (aAllowThirdPartyFixup) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
}
// LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL isn't supported for javascript URIs,
// i.e. it causes them not to load at all. Callers should strip
// "javascript:" from pasted strings to prevent blank tabs
if (!aAllowInheritPrincipal) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
}
if (aAllowPopups) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_POPUPS;
}
if (aIndicateErrorPageLoad) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ERROR_LOAD_CHANGES_RV;
}
if (aForceAllowDataURI) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
}
if (params.fromExternal) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
}
let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler;
if (
aForceAboutBlankViewerInCurrent &&
(!uriObj ||
Services.io.getDynamicProtocolFlags(uriObj) &
URI_INHERITS_SECURITY_CONTEXT)
) {
// Unless we know for sure we're not inheriting principals,
// force the about:blank viewer to have the right principal:
targetBrowser.createAboutBlankContentViewer(
aPrincipal,
aStoragePrincipal
);
}
targetBrowser.fixupAndLoadURIString(url, {
triggeringPrincipal: aTriggeringPrincipal,
csp: aCsp,
flags,
referrerInfo: aReferrerInfo,
postData: aPostData,
userContextId: aUserContextId,
hasValidUserGestureActivation: params.hasValidUserGestureActivation,
globalHistoryOptions: aGlobalHistoryOptions,
triggeringRemoteType: aTriggeringRemoteType,
});
if (aResolveOnContentBrowserReady) {
aResolveOnContentBrowserReady(targetBrowser);
}
// Don't focus the content area if focus is in the address bar and we're
// loading the New Tab page.
focusUrlBar =
w.document.activeElement == w.gURLBar.inputField &&
w.isBlankPageURL(url);
break;
case "tabshifted":
loadInBackground = !loadInBackground;
// fall through
case "tab":
focusUrlBar =
!loadInBackground &&
w.isBlankPageURL(url) &&
!lazy.AboutNewTab.willNotifyUser;
let tabUsedForLoad = w.gBrowser.addTab(url, {
referrerInfo: aReferrerInfo,
charset: aCharset,
postData: aPostData,
inBackground: loadInBackground,
allowThirdPartyFixup: aAllowThirdPartyFixup,
relatedToCurrent: aRelatedToCurrent,
skipAnimation: aSkipTabAnimation,
userContextId: aUserContextId,
originPrincipal: aPrincipal,
originStoragePrincipal: aStoragePrincipal,
triggeringPrincipal: aTriggeringPrincipal,
allowInheritPrincipal: aAllowInheritPrincipal,
triggeringRemoteType: aTriggeringRemoteType,
csp: aCsp,
forceAllowDataURI: aForceAllowDataURI,
focusUrlBar,
openerBrowser: params.openerBrowser,
fromExternal: params.fromExternal,
globalHistoryOptions: aGlobalHistoryOptions,
});
targetBrowser = tabUsedForLoad.linkedBrowser;
if (aResolveOnNewTabCreated) {
aResolveOnNewTabCreated(targetBrowser);
}
if (aResolveOnContentBrowserReady) {
aResolveOnContentBrowserReady(targetBrowser);
}
if (params.frameID != undefined && w) {
// Only notify it as a WebExtensions' webNavigation.onCreatedNavigationTarget
// event if it contains the expected frameID params.
// (e.g. we should not notify it as a onCreatedNavigationTarget if the user is
// opening a new tab using the keyboard shortcut).
Services.obs.notifyObservers(
{
wrappedJSObject: {
url,
createdTabBrowser: targetBrowser,
sourceTabBrowser: w.gBrowser.selectedBrowser,
sourceFrameID: params.frameID,
},
},
"webNavigation-createdNavigationTarget"
);
}
break;
}
if (
!params.avoidBrowserFocus &&
!focusUrlBar &&
targetBrowser == w.gBrowser.selectedBrowser
) {
// Focus the content, but only if the browser used for the load is selected.
targetBrowser.focus();
}
},
/**
* Finds a browser window suitable for opening a link matching the
* requirements given in the `params` argument. If the current window matches
* the requirements then it is returned otherwise the top-most window that
* matches will be returned.
*
* @param {Window} window - The current window.
* @param {Object} params - Parameters for selecting the window.
* @param {boolean} params.skipPopups - Require a non-popup window.
* @param {boolean} params.forceNonPrivate - Require a non-private window.
* @returns {Window | null} A matching browser window or null if none matched.
*/
getTargetWindow(window, { skipPopups, forceNonPrivate } = {}) {
let { top } = window;
// If this is called in a browser window, use that window regardless of
// whether it's the frontmost window, since commands can be executed in
// background windows (bug 626148).
if (
top.document.documentElement.getAttribute("windowtype") ==
"navigator:browser" &&
(!skipPopups || top.toolbar.visible) &&
(!forceNonPrivate || !PrivateBrowsingUtils.isWindowPrivate(top))
) {
return top;
}
return lazy.BrowserWindowTracker.getTopWindow({
private: !forceNonPrivate && PrivateBrowsingUtils.isWindowPrivate(window),
allowPopups: !skipPopups,
});
},
/**
* openUILink handles clicks on UI elements that cause URLs to load.
*
* @param {string} url
* @param {Event | Object} event Event or JSON object representing an Event
* @param {Boolean | Object} aIgnoreButton
* Boolean or object with the same properties as
* accepted by openLinkIn, plus "ignoreButton"
* and "ignoreAlt".
* @param {Boolean} aIgnoreAlt
* @param {Boolean} aAllowThirdPartyFixup
* @param {Object} aPostData
* @param {Object} aReferrerInfo
*/
openUILink(
window,
url,
event,
aIgnoreButton,
aIgnoreAlt,
aAllowThirdPartyFixup,
aPostData,
aReferrerInfo
) {
event = BrowserUtils.getRootEvent(event);
let params;
if (aIgnoreButton && typeof aIgnoreButton == "object") {
params = aIgnoreButton;
// don't forward "ignoreButton" and "ignoreAlt" to openLinkIn
aIgnoreButton = params.ignoreButton;
aIgnoreAlt = params.ignoreAlt;
delete params.ignoreButton;
delete params.ignoreAlt;
} else {
params = {
allowThirdPartyFixup: aAllowThirdPartyFixup,
postData: aPostData,
referrerInfo: aReferrerInfo,
initiatingDoc: event ? event.target.ownerDocument : null,
};
}
if (!params.triggeringPrincipal) {
throw new Error(
"Required argument triggeringPrincipal missing within openUILink"
);
}
let where = BrowserUtils.whereToOpenLink(event, aIgnoreButton, aIgnoreAlt);
params.forceForeground ??= true;
this.openLinkIn(window, url, where, params);
},
/* openTrustedLinkIn will attempt to open the given URI using the SystemPrincipal
* as the trigeringPrincipal, unless a more specific Principal is provided.
*
* Otherwise, parameters are the same as openLinkIn, but we will set `forceForeground`
* to true.
*/
openTrustedLinkIn(window, url, where, params = {}) {
if (!params.triggeringPrincipal) {
params.triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
}
params.forceForeground ??= true;
this.openLinkIn(window, url, where, params);
},
/* openWebLinkIn will attempt to open the given URI using the NullPrincipal
* as the triggeringPrincipal, unless a more specific Principal is provided.
*
* Otherwise, parameters are the same as openLinkIn, but we will set `forceForeground`
* to true.
*/
openWebLinkIn(window, url, where, params = {}) {
if (!params.triggeringPrincipal) {
params.triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal(
{}
);
}
if (params.triggeringPrincipal.isSystemPrincipal) {
throw new Error(
"System principal should never be passed into openWebLinkIn()"
);
}
params.forceForeground ??= true;
this.openLinkIn(window, url, where, params);
},
};