Commit cc92dd7e authored by Andrea Marchesini's avatar Andrea Marchesini
Browse files

Bug 675574 - Allow just 1 window.open() per event, r=smaug

parent 0efb7300
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -5478,6 +5478,14 @@ nsGlobalWindowOuter::RevisePopupAbuseLevel(PopupControlState aControl)
      abuse = openOverridden;
  }

  // If this popup is allowed, let's block any other for this event, forcing
  // openBlocked state.
  if ((abuse == openAllowed || abuse == openControlled) &&
      StaticPrefs::dom_block_multiple_popups() &&
      !PopupWhitelisted()) {
    nsContentUtils::PushPopupControlState(openBlocked, true);
  }

  return abuse;
}

@@ -5514,7 +5522,7 @@ nsGlobalWindowOuter::FireAbuseEvents(const nsAString &aPopupURL,
    ios->NewURI(NS_ConvertUTF16toUTF8(aPopupURL), nullptr, baseURL,
                getter_AddRefs(popupURI));

  // fire an event chock full of informative URIs
  // fire an event block full of informative URIs
  FirePopupBlockedEvent(topDoc, popupURI, aPopupWindowName,
                        aPopupWindowFeatures);
}
+2 −0
Original line number Diff line number Diff line
@@ -57,3 +57,5 @@ skip-if = verify
[browser_timeout_throttling_with_audio_playback.js]
[browser_bug1303838.js]
[browser_inputStream_structuredClone.js]
[browser_multiple_popups.js]
support-files = browser_multiple_popups.html
+54 −0
Original line number Diff line number Diff line
<!DOCTYPE html>
<html>
<body>
  <button onclick="openPopups();" id="openPopups">open popups</button>
  <button onclick="openNestedPopups();" id="openNestedPopups">open tested popups</button>
  <button onclick="openPopupAndClick();" id="openPopupAndClick">open popups and click</button>
  <button onclick="closeAllWindows();" id="closeAllWindows">close all windows</button>
  <input type="text" id="input" />
  <script>
let windows = [];

function openPopups() {
  windows.push(window.open('empty.html', '_blank', 'width=100,height=100'));
  windows.push(window.open('empty.html', '_blank', 'width=100,height=100'));
}

function openNestedPopups() {
  var w = window.open('empty.html', '_blank', 'width=100,height=100');
  windows.push(w);
  windows.push(w.open('empty.html', '_blank', 'width=100,height=100'));
}

var recursion = false;
function openPopupAndClick() {
  windows.push(window.open('empty.html', '_blank', 'width=100,height=100'));
  if (!recursion) {
    recursion = true;
    document.getElementById("openPopupAndClick").click();
  }
}

function closeAllWindows() {
  windows.forEach(w => {
    try {
      w.close();
    } catch(e) {}
  });
}

if (location.search.includes("openPopups")) {
  let id = setInterval(() => {
    if (document.getElementById('start')) {
      clearInterval(id);
      openPopups();
    }
  }, 500);
}

document.getElementById("input").onmouseup = _ => {
  openPopups();
}
  </script>
</body>
</html>
+362 −0
Original line number Diff line number Diff line
/**
 * In this test, we check that the content can't open more than one popup at a
 * time (depending on "dom.allow_mulitple_popups" preference value).
 */

const TEST_DOMAIN = "http://example.net";
const TEST_PATH = "/browser/dom/base/test/";
const CHROME_DOMAIN = "chrome://mochitests/content";

requestLongerTimeout(2);

function WindowObserver(count) {
  return new Promise(resolve => {
    let windowObserver = function(aSubject, aTopic, aData) {
      if (aTopic != "domwindowopened") {
        return;
      }

      if (--count == 0) {
        Services.ww.unregisterNotification(windowObserver);
        resolve();
      }
    }
    Services.ww.registerNotification(windowObserver);
  });
}

add_task(async _ => {
  info("All opened if the pref is off");

  await SpecialPowers.pushPrefEnv({"set": [
    ["dom.block_multiple_popups", false],
    ["dom.disable_open_during_load", true],
  ]});

  let tab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "browser_multiple_popups.html")
  gBrowser.selectedTab = tab;

  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  let obs = new WindowObserver(2);

  await BrowserTestUtils.synthesizeMouseAtCenter("#openPopups", {}, tab.linkedBrowser);

  await obs;
  ok(true, "We had 2 windows.");

  await BrowserTestUtils.synthesizeMouseAtCenter("#closeAllWindows", {}, tab.linkedBrowser);

  BrowserTestUtils.removeTab(tab);
});

add_task(async _ => {
  info("2 window.open()s in a click event allowed because whitelisted domain.");

  await SpecialPowers.pushPrefEnv({"set": [
    ["dom.block_multiple_popups", true],
    ["dom.disable_open_during_load", true],
  ]});

  const uri = Services.io.newURI(TEST_DOMAIN);
  const principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});

  Services.perms.addFromPrincipal(principal, "popup", Services.perms.ALLOW_ACTION);

  let tab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "browser_multiple_popups.html")
  gBrowser.selectedTab = tab;

  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  let obs = new WindowObserver(2);

  await BrowserTestUtils.synthesizeMouseAtCenter("#openPopups", {}, tab.linkedBrowser);

  await obs;
  ok(true, "We had 2 windows.");

  await BrowserTestUtils.synthesizeMouseAtCenter("#closeAllWindows", {}, tab.linkedBrowser);

  BrowserTestUtils.removeTab(tab);

  await new Promise(aResolve => {
    Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_PERMISSIONS, value => {
      Assert.equal(value, 0);
      aResolve();
    });
  });
});

add_task(async _ => {
  info("2 window.open()s in a mouseup event allowed because whitelisted domain.");

  await SpecialPowers.pushPrefEnv({"set": [
    ["dom.block_multiple_popups", true],
    ["dom.disable_open_during_load", true],
  ]});

  const uri = Services.io.newURI(TEST_DOMAIN);
  const principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});

  Services.perms.addFromPrincipal(principal, "popup", Services.perms.ALLOW_ACTION);

  let tab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "browser_multiple_popups.html")
  gBrowser.selectedTab = tab;

  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  let obs = new WindowObserver(2);

  await BrowserTestUtils.synthesizeMouseAtCenter("#input", { type: "mouseup" }, tab.linkedBrowser);

  await obs;
  ok(true, "We had 2 windows.");

  await BrowserTestUtils.synthesizeMouseAtCenter("#closeAllWindows", {}, tab.linkedBrowser);

  BrowserTestUtils.removeTab(tab);

  await new Promise(aResolve => {
    Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_PERMISSIONS, value => {
      Assert.equal(value, 0);
      aResolve();
    });
  });
});

add_task(async _ => {
  info("2 window.open()s in a single click event: only the first one is allowed.");

  await SpecialPowers.pushPrefEnv({"set": [
    ["dom.block_multiple_popups", true],
    ["dom.disable_open_during_load", true],
  ]});

  let tab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "browser_multiple_popups.html")
  gBrowser.selectedTab = tab;

  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  let p = ContentTask.spawn(browser, null, () => {
    return new content.Promise(resolve => {
      content.addEventListener("DOMPopupBlocked", () => {
        ok(true, "The popup has been blocked");
        resolve();
      }, {once:true});
    });
  });

  let obs = new WindowObserver(1);

  await BrowserTestUtils.synthesizeMouseAtCenter("#openPopups", {}, tab.linkedBrowser);

  await p;
  await obs;
  ok(true, "We had only 1 window.");

  await BrowserTestUtils.synthesizeMouseAtCenter("#closeAllWindows", {}, tab.linkedBrowser);

  BrowserTestUtils.removeTab(tab);
});

add_task(async _ => {
  info("2 window.open()s in a single mouseup event: only the first one is allowed.");

  await SpecialPowers.pushPrefEnv({"set": [
    ["dom.block_multiple_popups", true],
    ["dom.disable_open_during_load", true],
  ]});

  let tab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "browser_multiple_popups.html")
  gBrowser.selectedTab = tab;

  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  let p = ContentTask.spawn(browser, null, () => {
    return new content.Promise(resolve => {
      content.addEventListener("DOMPopupBlocked", () => {
        ok(true, "The popup has been blocked");
        resolve();
      }, {once:true});
    });
  });

  let obs = new WindowObserver(1);

  await BrowserTestUtils.synthesizeMouseAtCenter("#input", { type: "mouseup" }, tab.linkedBrowser);

  await p;
  await obs;
  ok(true, "We had only 1 window.");

  await BrowserTestUtils.synthesizeMouseAtCenter("#closeAllWindows", {}, tab.linkedBrowser);

  BrowserTestUtils.removeTab(tab);
});

add_task(async _ => {
  info("2 window.open()s by non-event code: no windows allowed.");

  await SpecialPowers.pushPrefEnv({"set": [
    ["dom.block_multiple_popups", true],
    ["dom.disable_open_during_load", true],
  ]});

  let tab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "browser_multiple_popups.html?openPopups")
  gBrowser.selectedTab = tab;

  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  await ContentTask.spawn(browser, null, () => {
    return new content.Promise(resolve => {
      let count = 0;
      content.addEventListener("DOMPopupBlocked", function cb() {
        if (++count == 2) {
          content.removeEventListener("DOMPopupBlocked", cb);
          ok(true, "The popup has been blocked");
          resolve();
        }
      });

      let p = content.document.createElement("p");
      p.setAttribute('id', 'start');
      content.document.body.appendChild(p);
    });
  });

  ok(true, "We had 0 windows.");

  await BrowserTestUtils.synthesizeMouseAtCenter("#closeAllWindows", {}, tab.linkedBrowser);

  BrowserTestUtils.removeTab(tab);
});

add_task(async _ => {
  info("2 window.open()s by non-event code allowed by permission");

  await SpecialPowers.pushPrefEnv({"set": [
    ["dom.block_multiple_popups", true],
    ["dom.disable_open_during_load", true],
  ]});

  const uri = Services.io.newURI(TEST_DOMAIN);
  const principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});

  Services.perms.addFromPrincipal(principal, "popup", Services.perms.ALLOW_ACTION);

  let obs = new WindowObserver(2);

  let tab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "browser_multiple_popups.html?openPopups")
  gBrowser.selectedTab = tab;

  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  ok(true, "We had 2 windows.");

  await BrowserTestUtils.synthesizeMouseAtCenter("#closeAllWindows", {}, tab.linkedBrowser);

  BrowserTestUtils.removeTab(tab);

  await new Promise(aResolve => {
    Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_PERMISSIONS, value => {
      Assert.equal(value, 0);
      aResolve();
    });
  });
});

add_task(async _ => {
  info("1 window.open() executing another window.open(): only the first one is allowed.");

  await SpecialPowers.pushPrefEnv({"set": [
    ["dom.block_multiple_popups", true],
    ["dom.disable_open_during_load", true],
  ]});

  let tab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "browser_multiple_popups.html")
  gBrowser.selectedTab = tab;

  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  // We don't receive DOMPopupBlocked for nested windows. Let's use just the observer.
  let obs = new WindowObserver(1);

  await BrowserTestUtils.synthesizeMouseAtCenter("#openNestedPopups", {}, tab.linkedBrowser);

  await obs;
  ok(true, "We had 1 window.");

  await BrowserTestUtils.synthesizeMouseAtCenter("#closeAllWindows", {}, tab.linkedBrowser);

  BrowserTestUtils.removeTab(tab);
});

add_task(async _ => {
  info("window.open() and .click() on the element opening the window.");

  await SpecialPowers.pushPrefEnv({"set": [
    ["dom.block_multiple_popups", true],
    ["dom.disable_open_during_load", true],
  ]});

  let tab = BrowserTestUtils.addTab(gBrowser, TEST_DOMAIN + TEST_PATH + "browser_multiple_popups.html")
  gBrowser.selectedTab = tab;

  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  let p = ContentTask.spawn(browser, null, () => {
    return new content.Promise(resolve => {
      content.addEventListener("DOMPopupBlocked", () => {
        ok(true, "The popup has been blocked");
        resolve();
      }, {once:true});
    });
  });

  let obs = new WindowObserver(1);

  await BrowserTestUtils.synthesizeMouseAtCenter("#openPopupAndClick", {}, tab.linkedBrowser);

  await p;
  await obs;
  ok(true, "We had only 1 window.");

  await BrowserTestUtils.synthesizeMouseAtCenter("#closeAllWindows", {}, tab.linkedBrowser);

  BrowserTestUtils.removeTab(tab);
});

add_task(async _ => {
  info("All opened from chrome.");

  await SpecialPowers.pushPrefEnv({"set": [
    ["dom.block_multiple_popups", true],
    ["dom.disable_open_during_load", true],
  ]});

  let tab = BrowserTestUtils.addTab(gBrowser, CHROME_DOMAIN + TEST_PATH + "browser_multiple_popups.html")
  gBrowser.selectedTab = tab;

  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  let obs = new WindowObserver(2);

  await BrowserTestUtils.synthesizeMouseAtCenter("#openPopups", {}, tab.linkedBrowser);

  await obs;
  ok(true, "We had 2 windows.");

  await BrowserTestUtils.synthesizeMouseAtCenter("#closeAllWindows", {}, tab.linkedBrowser);

  BrowserTestUtils.removeTab(tab);
});
+7 −0
Original line number Diff line number Diff line
@@ -423,6 +423,13 @@ VARCACHE_PREF(
  RelaxedAtomicBool, false
)

// Block multiple window.open() per single event.
VARCACHE_PREF(
  "dom.block_multiple_popups",
   dom_block_multiple_popups,
  bool, true
)

//---------------------------------------------------------------------------
// Clear-Site-Data prefs
//---------------------------------------------------------------------------
Loading