Bug 332195 - part 2: allow the user to control this behaviour with a checkbox, r=mconley
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Tue, 13 Oct 2015 11:06:01 +0100
changeset 304753 96bb0f79639d34553ded9e9be9359b468577dfb6
parent 304752 96d3a95d7dc6e88b14c7dc14768056ec9ba263a4
child 304754 e9d1a369d21762e16c0af0608bc9b1aff53c4428
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs332195
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 332195 - part 2: allow the user to control this behaviour with a checkbox, r=mconley
browser/base/content/browser.js
browser/base/content/tabbrowser.xml
browser/locales/en-US/chrome/browser/tabbrowser.properties
browser/modules/RemotePrompt.jsm
toolkit/components/prompts/content/tabprompts.xml
toolkit/components/prompts/src/nsPrompter.js
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -8176,27 +8176,53 @@ var AboutPrivateBrowsingListener = {
   }
 };
 
 function TabModalPromptBox(browser) {
   this._weakBrowserRef = Cu.getWeakReference(browser);
 }
 
 TabModalPromptBox.prototype = {
+  _promptCloseCallback(onCloseCallback, principalToAllowFocusFor, allowFocusCheckbox, ...args) {
+    if (principalToAllowFocusFor && allowFocusCheckbox.checked) {
+      Services.perms.addFromPrincipal(principalToAllowFocusFor, "focus-tab-by-prompt",
+                                      Services.perms.ALLOW_ACTION);
+    }
+    onCloseCallback.apply(this, args);
+  },
+
   appendPrompt(args, onCloseCallback) {
     const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
     let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
     let browser = this.browser;
     browser.parentNode.appendChild(newPrompt);
     browser.setAttribute("tabmodalPromptShowing", true);
 
     newPrompt.clientTop; // style flush to assure binding is attached
 
+    let principalToAllowFocusFor = this._allowTabFocusByPromptPrincipal;
+    delete this._allowTabFocusByPromptPrincipal;
+
+    let allowFocusCheckbox; // Define outside the if block so we can bind it into the callback.
+    if (principalToAllowFocusFor) {
+      let allowFocusRow = document.createElementNS(XUL_NS, "row");
+      allowFocusCheckbox = document.createElementNS(XUL_NS, "checkbox");
+      let spacer = document.createElementNS(XUL_NS, "spacer");
+      allowFocusRow.appendChild(spacer);
+      let label = gBrowser.mStringBundle.getFormattedString("tabs.allowTabFocusByPromptForSite",
+                                                            [principalToAllowFocusFor.URI.host]);
+      allowFocusCheckbox.setAttribute("label", label);
+      allowFocusRow.appendChild(allowFocusCheckbox);
+      newPrompt.appendChild(allowFocusRow);
+    }
+
     let tab = gBrowser.getTabForBrowser(browser);
-    newPrompt.init(args, tab, onCloseCallback);
+    let closeCB = this._promptCloseCallback.bind(null, onCloseCallback, principalToAllowFocusFor,
+                                                 allowFocusCheckbox);
+    newPrompt.init(args, tab, closeCB);
     return newPrompt;
   },
 
   removePrompt(aPrompt) {
     let browser = this.browser;
     browser.parentNode.removeChild(aPrompt);
 
     let prompts = this.listPrompts();
@@ -8211,16 +8237,20 @@ TabModalPromptBox.prototype = {
 
   listPrompts(aPrompt) {
     // Get the nodelist, then return as an array
     const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
     let els = this.browser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
     return Array.from(els);
   },
 
+  onNextPromptShowAllowFocusCheckboxFor(principal) {
+    this._allowTabFocusByPromptPrincipal = principal;
+  },
+
   get browser() {
     let browser = this._weakBrowserRef.get();
     if (!browser) {
       throw "Stale promptbox! The associated browser is gone.";
     }
     return browser;
   },
 };
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -4343,21 +4343,45 @@
                             this.getTabForBrowser(event.originalTarget);
 
           // Don't need to act if the tab is already selected:
           if (tabForEvent.selected)
             return;
 
           if (event.detail && event.detail.tabPrompt &&
               Services.prefs.getBoolPref("browser.tabs.dontfocusfordialogs")) {
-            tabForEvent.setAttribute("attention", "true");
-          } else {
-            // bring tab to the front:
-            this.selectedTab = tabForEvent;
+            let docPrincipal = targetIsWindow ? event.target.document.nodePrincipal : null;
+            // At least one of these should/will be non-null:
+            let promptPrincipal = event.detail.promptPrincipal || docPrincipal ||
+                                  tabForEvent.linkedBrowser.contentPrincipal;
+            // For null principals, we bail immediately and don't show the checkbox:
+            if (!promptPrincipal || promptPrincipal.isNullPrincipal) {
+              tabForEvent.setAttribute("attention", "true");
+              return;
+            }
+
+            // For non-system/expanded principals, we bail and show the checkbox
+            if (promptPrincipal.URI &&
+                !Services.scriptSecurityManager.isSystemPrincipal(promptPrincipal)) {
+              let permission = Services.perms.testPermissionFromPrincipal(promptPrincipal,
+                                                                          "focus-tab-by-prompt");
+              if (permission != Services.perms.ALLOW_ACTION) {
+                // Tell the prompt box we want to show the user a checkbox:
+                let tabPrompt = this.getTabModalPromptBox(tabForEvent.linkedBrowser);
+                tabPrompt.onNextPromptShowAllowFocusCheckboxFor(promptPrincipal);
+                tabForEvent.setAttribute("attention", "true");
+                return;
+              }
+            }
+            // ... so system and expanded principals, as well as permitted "normal"
+            // URI-based principals, always get to steal focus for the tab when prompting.
           }
+
+          // if prefs/permissions/origins so dictate, bring tab to the front:
+          this.selectedTab = tabForEvent;
         ]]>
       </handler>
       <handler event="DOMTitleChanged">
         <![CDATA[
           if (!event.isTrusted)
             return;
 
           var contentWin = event.target.defaultView;
--- a/browser/locales/en-US/chrome/browser/tabbrowser.properties
+++ b/browser/locales/en-US/chrome/browser/tabbrowser.properties
@@ -37,8 +37,12 @@ tabs.closeSelectedTab.tooltip=Close tab 
 # LOCALIZATION NOTE (tabs.muteAudio.tooltip):
 # %S is the keyboard shortcut for "Mute tab"
 tabs.muteAudio.tooltip=Mute tab (%S)
 # LOCALIZATION NOTE (tabs.unmuteAudio.tooltip):
 # %S is the keyboard shortcut for "Unmute tab"
 tabs.unmuteAudio.tooltip=Unmute tab (%S)
 tabs.muteAudio.background.tooltip=Mute tab
 tabs.unmuteAudio.background.tooltip=Unmute tab
+
+# LOCALIZATION NOTE (tabs.allowTabFocusByPromptForSite):
+# %S is the hostname of the site where dialogs are allowed to switch tabs
+tabs.allowTabFocusByPromptForSite=Allow dialogs from %S to take you to their tab
--- a/browser/modules/RemotePrompt.jsm
+++ b/browser/modules/RemotePrompt.jsm
@@ -58,17 +58,17 @@ var RemotePrompt = {
       browser.messageManager.removeMessageListener("Prompt:ForceClose", listener);
 
       if (newPrompt) {
         newPrompt.abortPrompt();
       }
     });
 
     try {
-      let eventDetail = {tabPrompt: true};
+      let eventDetail = {tabPrompt: true, promptPrincipal: args.promptPrincipal};
       PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser, eventDetail);
 
       args.promptActive = true;
 
       newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
 
       // TODO since we don't actually open a window, need to check if
       // there's other stuff in nsWindowWatcher::OpenWindowInternal
--- a/toolkit/components/prompts/content/tabprompts.xml
+++ b/toolkit/components/prompts/content/tabprompts.xml
@@ -51,16 +51,18 @@
                             <label anonid="password1Label" value="&editfield1.label;" control="password1Textbox"/>
                             <textbox anonid="password1Textbox" type="password"/>
                         </row>
 
                         <row anonid="checkboxContainer" hidden="true">
                             <spacer/>
                             <checkbox anonid="checkbox"/>
                         </row>
+
+                        <xbl:children includes="row"/>
                     </rows>
                 </grid>
                 <xbl:children/>
                 <hbox class="buttonContainer">
 #ifdef XP_UNIX
                     <button anonid="button3" hidden="true"/>
                     <button anonid="button2" hidden="true"/>
                     <spacer anonid="buttonSpacer" flex="1"/>
--- a/toolkit/components/prompts/src/nsPrompter.js
+++ b/toolkit/components/prompts/src/nsPrompter.js
@@ -480,16 +480,17 @@ function openRemotePrompt(domWin, args, 
     domWin.addEventListener("pagehide", pagehide);
     function pagehide() {
         domWin.removeEventListener("pagehide", pagehide);
         messageManager.sendAsyncMessage("Prompt:ForceClose", { _remoteId: id });
     }
 
     let topPrincipal = domWin.top.document.nodePrincipal;
     let promptPrincipal = domWin.document.nodePrincipal;
+    args.promptPrincipal = promptPrincipal;
     args.showAlertOrigin = topPrincipal.equals(promptPrincipal);
 
     args._remoteId = id;
 
     messageManager.sendAsyncMessage("Prompt:Open", args, {});
 
     let thread = Services.tm.currentThread;
     while (!closed) {