From 1e1ffd2b4100c5a475bb5397807032cdd0b91031 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Tue, 7 Feb 2017 13:04:03 +0000 Subject: [PATCH] Adding in longpress for new tab menu --- index.js | 4 + shortcuts.js | 391 +++++++++++++++++++++++++++++++++++++ webextension/background.js | 4 +- 3 files changed, 397 insertions(+), 2 deletions(-) create mode 100644 shortcuts.js diff --git a/index.js b/index.js index 7c01829..9fecb92 100644 --- a/index.js +++ b/index.js @@ -37,6 +37,7 @@ const { viewFor } = require("sdk/view/core"); const webExtension = require("sdk/webextension"); const windows = require("sdk/windows"); const windowUtils = require("sdk/window/utils"); +const shortcuts = require("shortcuts"); // ---------------------------------------------------------------------------- // ContainerService @@ -605,6 +606,7 @@ ContainerWindow.prototype = { _init(window) { this._window = window; + this._newTabShortcut = new shortcuts.NewTabShortcut(window); const style = Style({ uri: self.data.url("usercontext.css") }); attachTo(style, this._window); }, @@ -663,6 +665,8 @@ ContainerWindow.prototype = { } }; + this._window.showPopup = showPopup; + [button, overflowButton].forEach((buttonElement) => { buttonElement.addEventListener("mouseover", () => { showPopup(buttonElement); diff --git a/shortcuts.js b/shortcuts.js new file mode 100644 index 0000000..942d2cb --- /dev/null +++ b/shortcuts.js @@ -0,0 +1,391 @@ +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; diff --git a/webextension/background.js b/webextension/background.js index 9b707c8..805dd0e 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -13,6 +13,6 @@ browser.tabs.query({}).then(tabs => { }).catch(() => {}); function disableAddon(tabId) { - browser.browserAction.disable(tabId); - browser.browserAction.setTitle({ tabId, title: "Containers disabled in Private Browsing Mode" }); + browser.browserAction.disable(tabId); + browser.browserAction.setTitle({ tabId, title: "Containers disabled in Private Browsing Mode" }); }