const AppConstants = require("resource://gre/modules/AppConstants.jsm"); const { Cc, Ci } = require("chrome"); const {Services} = require("resource://gre/modules/Services.jsm"); const NEW_TAB_TIMEOUT = 300; function getBrowserURL() { return "chrome://browser/content/browser.xul"; } function whereToOpenLink(e, ignoreButton, ignoreAlt) { // This method must treat a null event like a left click without modifier keys (i.e. // e = { shiftKey:false, ctrlKey:false, metaKey:false, altKey:false, button:0 }) // for compatibility purposes. if (!e) return "current"; const shift = e.shiftKey; const ctrl = e.ctrlKey; const meta = e.metaKey; const alt = e.altKey && !ignoreAlt; // ignoreButton allows "middle-click paste" to use function without always opening in a new window. const middle = !ignoreButton && e.button === 1; const middleUsesTabs = Services.prefs.getBoolPref("browser.tabs.opentabfor.middleclick", true); // Don't do anything special with right-mouse clicks. They're probably clicks on context menu items. const metaKey = AppConstants.platform === "macosx" ? meta : ctrl; if (metaKey || (middle && middleUsesTabs)) return shift ? "tabshifted" : "tab"; if (alt && Services.prefs.getBoolPref("browser.altClickSave", false)) return "save"; if (shift || (middle && !middleUsesTabs)) return "window"; return "current"; } function BrowserOpenTab(event, win) { let where = "tab"; let relatedToCurrent = false; //let doc = event.target.ownerDocument; //let win = doc.defaultView; if (event) { where = whereToOpenLink(event, false, true); switch (where) { case "tab": case "tabshifted": // When accel-click or middle-click are used, open the new tab as // related to the current tab. relatedToCurrent = true; break; case "current": where = "tab"; break; } } openUILinkIn(win.BROWSER_NEW_TAB_URL, where, { relatedToCurrent }, undefined, undefined, win); } function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI, win) { let params; if (arguments.length === 3 && typeof arguments[2] === "object") { params = aAllowThirdPartyFixup; } else { params = { allowThirdPartyFixup: aAllowThirdPartyFixup, postData: aPostData, referrerURI: aReferrerURI, referrerPolicy: Ci.nsIHttpChannel.REFERRER_POLICY_UNSET }; } params.fromChrome = true; openLinkIn(url, where, params, win); } function openLinkIn(url, where, params, win) { if (!where || !url) return; const aAllowThirdPartyFixup = params.allowThirdPartyFixup; const aPostData = params.postData; const aCharset = params.charset; const aReferrerURI = params.referrerURI; const aReferrerPolicy = ("referrerPolicy" in params ? params.referrerPolicy : Ci.nsIHttpChannel.REFERRER_POLICY_UNSET); let aRelatedToCurrent = params.relatedToCurrent; const aAllowMixedContent = params.allowMixedContent; const aInBackground = params.inBackground; const aDisallowInheritPrincipal = params.disallowInheritPrincipal; const aIsPrivate = params.private; const aSkipTabAnimation = params.skipTabAnimation; const aAllowPinnedTabHostChange = !!params.allowPinnedTabHostChange; const aNoReferrer = params.noReferrer; const aAllowPopups = !!params.allowPopups; const aUserContextId = params.userContextId; const aIndicateErrorPageLoad = params.indicateErrorPageLoad; const aPrincipal = params.originPrincipal; const aForceAboutBlankViewerInCurrent = params.forceAboutBlankViewerInCurrent; //if (where === "save") { // // TODO(1073187): propagate referrerPolicy. // // ContentClick.jsm passes isContentWindowPrivate for saveURL instead of passing a CPOW initiatingDoc // if ("isContentWindowPrivate" in params) { // saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, null, params.isContentWindowPrivate); // } else { // if (!aInitiatingDoc) { // Cu.reportError("openUILink/openLinkIn was called with " + // "where === 'save' but without initiatingDoc. See bug 814264."); // return; // } // saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, aInitiatingDoc); // } // return; //} // Establish which window we'll load the link in. let w; if (where === "current" && params.targetBrowser) { w = params.targetBrowser.ownerGlobal; } else { w = win.top; } // 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 = win.top; aRelatedToCurrent = false; } if (!w || where === "window") { // This propagates to window.arguments. const sa = Cc["@mozilla.org/array;1"]. createInstance(Ci.nsIMutableArray); const wuri = Cc["@mozilla.org/supports-string;1"]. createInstance(Ci.nsISupportsString); wuri.data = url; let charset = null; if (aCharset) { charset = Cc["@mozilla.org/supports-string;1"] .createInstance(Ci.nsISupportsString); charset.data = "charset=" + aCharset; } const allowThirdPartyFixupSupports = Cc["@mozilla.org/supports-PRBool;1"]. createInstance(Ci.nsISupportsPRBool); allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup; let referrerURISupports = null; if (aReferrerURI && !aNoReferrer) { referrerURISupports = Cc["@mozilla.org/supports-string;1"]. createInstance(Ci.nsISupportsString); referrerURISupports.data = aReferrerURI.spec; } const referrerPolicySupports = Cc["@mozilla.org/supports-PRUint32;1"]. createInstance(Ci.nsISupportsPRUint32); referrerPolicySupports.data = aReferrerPolicy; const userContextIdSupports = Cc["@mozilla.org/supports-PRUint32;1"]. createInstance(Ci.nsISupportsPRUint32); userContextIdSupports.data = aUserContextId; sa.appendElement(wuri, /* weak =*/ false); sa.appendElement(charset, /* weak =*/ false); sa.appendElement(referrerURISupports, /* weak =*/ false); sa.appendElement(aPostData, /* weak =*/ false); sa.appendElement(allowThirdPartyFixupSupports, /* weak =*/ false); sa.appendElement(referrerPolicySupports, /* weak =*/ false); sa.appendElement(userContextIdSupports, /* weak =*/ false); sa.appendElement(aPrincipal, /* weak =*/ false); let features = "chrome,dialog=no,all"; if (aIsPrivate) { features += ",private"; } Services.ww.openWindow(w || win, getBrowserURL(), 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) { //blank } if (w.gBrowser.getTabForBrowser(targetBrowser).pinned && !aAllowPinnedTabHostChange) { try { // nsIURI.host can throw for non-nsStandardURL nsIURIs. if (!uriObj || (!uriObj.schemeIs("javascript") && targetBrowser.currentURI.host !== uriObj.host)) { where = "tab"; loadInBackground = false; } } catch (err) { where = "tab"; loadInBackground = false; } } } else { // 'where' is "tab" or "tabshifted", so we'll load the link in a new tab. loadInBackground = aInBackground; if (loadInBackground === null) { loadInBackground = true; } } let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; let tabUsedForLoad; switch (where) { case "current": 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 protect users from malicious URIs // (see stripUnsafeProtocolOnPaste). if (aDisallowInheritPrincipal && !(uriObj && uriObj.schemeIs("javascript"))) { 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 (aForceAboutBlankViewerInCurrent) { targetBrowser.createAboutBlankContentViewer(aPrincipal); } targetBrowser.loadURIWithFlags(url, { triggeringPrincipal: aPrincipal, flags, referrerURI: aNoReferrer ? null : aReferrerURI, referrerPolicy: aReferrerPolicy, postData: aPostData, userContextId: aUserContextId }); break; case "tabshifted": loadInBackground = !loadInBackground; // fall through case "tab": tabUsedForLoad = w.gBrowser.loadOneTab(url, { referrerURI: aReferrerURI, referrerPolicy: aReferrerPolicy, charset: aCharset, postData: aPostData, inBackground: loadInBackground, allowThirdPartyFixup: aAllowThirdPartyFixup, relatedToCurrent: aRelatedToCurrent, skipAnimation: aSkipTabAnimation, allowMixedContent: aAllowMixedContent, noReferrer: aNoReferrer, userContextId: aUserContextId, originPrincipal: aPrincipal, triggeringPrincipal: aPrincipal }); targetBrowser = tabUsedForLoad.linkedBrowser; break; } // Focus the content, but only if the browser used for the load is selected. if (targetBrowser === w.gBrowser.selectedBrowser) { targetBrowser.focus(); } if (!loadInBackground && w.isBlankPageURL(url)) { w.focusAndSelectUrlBar(); } } const NewTabShortcut = function (window) { this.init(window); }; NewTabShortcut.prototype = { init(window) { this._window = window; const elm = this._window.document.getElementById("key_newNavigatorTab"); this._key = elm.getAttribute("key"); this._timeout = NEW_TAB_TIMEOUT; this._menupopup = this._window.document.getElementById("alltabs-popup"); this._window.addEventListener("keydown", this); this._window.addEventListener("keyup", this); }, uninint() { this._window.removeEventListener("keydown", this); this._window.removeEventListener("keyup", this); }, handleEvent(event) { const accelKey = AppConstants.platform === "macosx" ? "metaKey" : "ctrlKey"; if (event.key !== this._key || !event[accelKey]) { this._clearTimer(); return; } // Lets return early if the userContext is disabled if (!Services.prefs.getBoolPref("privacy.userContext.enabled")) { return false; } // Let's see if this is a long press. if (event.type === "keydown" && !this._timer) { if (event.shiftKey) { return; } this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); this._timer.initWithCallback(this, this._timeout, this._timer.TYPE_ONE_SHOT); } else if (event.type === "keyup") { // Timeout has not expired yet if (this._timer) { this._clearTimer(); BrowserOpenTab(event, this._window); return false; } } // We suppress the default behavior of accel+T. event.preventDefault(); }, _clearTimer() { if (this._timer) { this._timer.cancel(); this._timer = null; } }, // Timer expired notify() { this._clearTimer(); this._openContainerMenu(); }, _openContainerMenu() { const tabbrowser = this._window.document.getElementById("tabbrowser-tabs"); const newTabOverflowButton = this._window.document.getElementById("new-tab-button"); const newTabButton = this._window.document.getAnonymousElementByAttribute(tabbrowser, "anonid", "tabs-newtab-button"); if (tabbrowser.getAttribute("overflow") === "true") { this._window.showPopup(newTabOverflowButton); } else { this._window.showPopup(newTabButton); } } }; exports.NewTabShortcut = NewTabShortcut;