From 63ad68e7987a7c93cbfba1276b1c5040b276a12c Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Thu, 17 Dec 2009 16:10:50 -0500 Subject: [PATCH] Bug 461625 - Hide the UI for saving permission manager entries in Private Browsing mode (test); r=mconnor --- .../privatebrowsing/test/browser/Makefile.in | 4 + ..._privatebrowsing_contextmenu_blockimage.js | 89 +++++++++++++ ...wser_privatebrowsing_cookieacceptdialog.js | 117 ++++++++++++++++++ .../browser_privatebrowsing_popupblocker.js | 97 +++++++++++++++ .../browser/browser_privatebrowsing_ui.js | 6 + .../test/browser/ctxmenu-image.png | Bin 0 -> 5401 bytes .../privatebrowsing/test/browser/ctxmenu.html | 9 ++ .../privatebrowsing/test/browser/popup.html | 11 ++ 8 files changed, 333 insertions(+) create mode 100644 browser/components/privatebrowsing/test/browser/browser_privatebrowsing_contextmenu_blockimage.js create mode 100644 browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cookieacceptdialog.js create mode 100644 browser/components/privatebrowsing/test/browser/browser_privatebrowsing_popupblocker.js create mode 100644 browser/components/privatebrowsing/test/browser/ctxmenu-image.png create mode 100644 browser/components/privatebrowsing/test/browser/ctxmenu.html create mode 100644 browser/components/privatebrowsing/test/browser/popup.html diff --git a/browser/components/privatebrowsing/test/browser/Makefile.in b/browser/components/privatebrowsing/test/browser/Makefile.in index 6bad3e07598b..90852e061f94 100644 --- a/browser/components/privatebrowsing/test/browser/Makefile.in +++ b/browser/components/privatebrowsing/test/browser/Makefile.in @@ -62,6 +62,7 @@ _BROWSER_TEST_FILES = \ browser_privatebrowsing_openlocation.js \ browser_privatebrowsing_pageinfo.js \ browser_privatebrowsing_placestitle.js \ + browser_privatebrowsing_popupblocker.js \ browser_privatebrowsing_popupmode.js \ browser_privatebrowsing_searchbar.js \ browser_privatebrowsing_sslsite_transition.js \ @@ -74,6 +75,9 @@ _BROWSER_TEST_FILES = \ browser_privatebrowsing_windowtitle_page.html \ browser_privatebrowsing_zoom.js \ browser_privatebrowsing_zoomrestore.js \ + ctxmenu.html \ + ctxmenu-image.png \ + popup.html \ staller.sjs \ title.sjs \ $(NULL) diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_contextmenu_blockimage.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_contextmenu_blockimage.js new file mode 100644 index 000000000000..2ff592850a14 --- /dev/null +++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_contextmenu_blockimage.js @@ -0,0 +1,89 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Private Browsing Tests. + * + * The Initial Developer of the Original Code is + * Ehsan Akhgari. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ehsan Akhgari (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// This test makes sure that private browsing mode disables the Block Image +// context menu item. + +function test() { + // initialization + let pb = Cc["@mozilla.org/privatebrowsing;1"]. + getService(Ci.nsIPrivateBrowsingService); + + const TEST_URI = "http://localhost:8888/browser/browser/components/privatebrowsing/test/browser/ctxmenu.html"; + + waitForExplicitFinish(); + + function checkBlockImageMenuItem(expectedHidden, callback) { + let tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + let browser = gBrowser.getBrowserForTab(tab); + browser.addEventListener("load", function() { + browser.removeEventListener("load", arguments.callee, true); + + executeSoon(function() { + let contextMenu = document.getElementById("contentAreaContextMenu"); + let blockImage = document.getElementById("context-blockimage"); + let image = browser.contentDocument.getElementsByTagName("img")[0]; + ok(image, "The content image should be accessible"); + + contextMenu.addEventListener("popupshown", function() { + contextMenu.removeEventListener("popupshown", arguments.callee, false); + + is(blockImage.hidden, expectedHidden, + "The Block Image menu item should " + (expectedHidden ? "" : "not ") + "be hidden"); + contextMenu.hidePopup(); + gBrowser.removeTab(tab); + callback(); + }, false); + + document.popupNode = image; + EventUtils.synthesizeMouse(image, 2, 2, + {type: "contextmenu", button: 2}, + browser.contentWindow); + }); + }, true); + browser.loadURI(TEST_URI); + } + + checkBlockImageMenuItem(false, function() { + pb.privateBrowsingEnabled = true; + checkBlockImageMenuItem(true, function() { + pb.privateBrowsingEnabled = false; + checkBlockImageMenuItem(false, finish); + }); + }); +} diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cookieacceptdialog.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cookieacceptdialog.js new file mode 100644 index 000000000000..a3d5ccd84746 --- /dev/null +++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cookieacceptdialog.js @@ -0,0 +1,117 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Private Browsing Tests. + * + * The Initial Developer of the Original Code is + * Ehsan Akhgari. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ehsan Akhgari (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// This test makes sure that private browsing mode disables the "remember" +// option in the cookie accept dialog. + +function test() { + // initialization + let pb = Cc["@mozilla.org/privatebrowsing;1"]. + getService(Ci.nsIPrivateBrowsingService); + let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Ci.nsIWindowWatcher); + let cp = Cc["@mozilla.org/embedcomp/cookieprompt-service;1"]. + getService(Ci.nsICookiePromptService); + + waitForExplicitFinish(); + + function checkRememberOption(expectedDisabled, callback) { + let observer = { + observe: function(aSubject, aTopic, aData) { + if (aTopic === "domwindowopened") { + ww.unregisterNotification(this); + let win = aSubject.QueryInterface(Ci.nsIDOMWindow); + win.addEventListener("load", function onLoad(event) { + win.removeEventListener("load", onLoad, false); + + executeSoon(function() { + let doc = win.document; + let remember = doc.getElementById("persistDomainAcceptance"); + ok(remember, "The remember checkbox should exist"); + + if (expectedDisabled) + is(remember.getAttribute("disabled"), "true", + "The checkbox should be disabled"); + else + ok(!remember.hasAttribute("disabled"), + "The checkbox should not be disabled"); + + win.close(); + callback(); + }); + }, false); + } + } + }; + ww.registerNotification(observer); + + let remember = {}; + const time = (new Date("Jan 1, 2030")).getTime() / 1000; + let cookie = { + name: "foo", + value: "bar", + isDomain: true, + host: "mozilla.org", + path: "/baz", + isSecure: false, + expires: time, + status: 0, + policy: 0, + isSession: false, + expiry: time, + isHttpOnly: true, + QueryInterface: function(iid) { + const validIIDs = [Components.interfaces.nsISupports, + Components.interfaces.nsICookie, + Components.interfaces.nsICookie2]; + for (var i = 0; i < validIIDs.length; ++i) + if (iid == validIIDs[i]) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + } + }; + cp.cookieDialog(window, cookie, "mozilla.org", 10, false, remember); + } + + checkRememberOption(false, function() { + pb.privateBrowsingEnabled = true; + checkRememberOption(true, function() { + pb.privateBrowsingEnabled = false; + checkRememberOption(false, finish); + }); + }); +} diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_popupblocker.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_popupblocker.js new file mode 100644 index 000000000000..fc2a8ac9a828 --- /dev/null +++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_popupblocker.js @@ -0,0 +1,97 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Private Browsing Tests. + * + * The Initial Developer of the Original Code is + * Ehsan Akhgari. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ehsan Akhgari (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// This test makes sure that private browsing mode disables the remember option +// for the popup blocker menu. + +function test() { + // initialization + let pb = Cc["@mozilla.org/privatebrowsing;1"]. + getService(Ci.nsIPrivateBrowsingService); + + let oldPopupPolicy = gPrefService.getBoolPref("dom.disable_open_during_load"); + gPrefService.setBoolPref("dom.disable_open_during_load", true); + + const TEST_URI = "http://localhost:8888/browser/browser/components/privatebrowsing/test/browser/popup.html"; + + waitForExplicitFinish(); + + function testPopupBlockerMenuItem(expectedDisabled, callback) { + gBrowser.addEventListener("DOMUpdatePageReport", function() { + gBrowser.removeEventListener("DOMUpdatePageReport", arguments.callee, false); + executeSoon(function() { + let pageReportButton = document.getElementById("page-report-button"); + let notification = gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"); + + ok(!pageReportButton.hidden, "The page report button should not be hidden"); + ok(notification, "The notification box should be displayed"); + + function checkMenuItem(callback) { + document.addEventListener("popupshown", function(event) { + document.removeEventListener("popupshown", arguments.callee, false); + + if (expectedDisabled) + is(document.getElementById("blockedPopupAllowSite").getAttribute("disabled"), "true", + "The allow popups menu item should be disabled"); + + event.originalTarget.hidePopup(); + callback(); + }, false); + } + + checkMenuItem(function() { + checkMenuItem(function() { + gBrowser.removeTab(tab); + callback(); + }); + notification.querySelector("button").doCommand(); + }); + EventUtils.synthesizeMouse(document.getElementById("page-report-button"), 1, 1, {}); + }); + }, false); + + let tab = gBrowser.addTab(TEST_URI); + gBrowser.selectedTab = tab; + } + + pb.privateBrowsingEnabled = true; + testPopupBlockerMenuItem(true, function() { + pb.privateBrowsingEnabled = false; + gPrefService.setBoolPref("dom.disable_open_during_load", oldPopupPolicy); + finish(); + }); +} diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js index 380ae6127643..09727aeab966 100644 --- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js +++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js @@ -60,13 +60,19 @@ function test() { // test the gPrivateBrowsingUI object ok(gPrivateBrowsingUI, "The gPrivateBrowsingUI object exists"); + is(pb.privateBrowsingEnabled, false, "The private browsing mode should not be started initially"); + is(gPrivateBrowsingUI.privateBrowsingEnabled, false, "gPrivateBrowsingUI should expose the correct private browsing status"); ok(pbMenuItem, "The Private Browsing menu item exists"); is(pbMenuItem.getAttribute("label"), pbMenuItem.getAttribute("startlabel"), "The Private Browsing menu item should read \"Start Private Browsing\""); gPrivateBrowsingUI.toggleMode(); + is(pb.privateBrowsingEnabled, true, "The private browsing mode should be started"); + is(gPrivateBrowsingUI.privateBrowsingEnabled, true, "gPrivateBrowsingUI should expose the correct private browsing status"); // check to see if the Private Browsing mode was activated successfully is(observer.data, "enter", "Private Browsing mode was activated using the gPrivateBrowsingUI object"); is(pbMenuItem.getAttribute("label"), pbMenuItem.getAttribute("stoplabel"), "The Private Browsing menu item should read \"Stop Private Browsing\""); gPrivateBrowsingUI.toggleMode() + is(pb.privateBrowsingEnabled, false, "The private browsing mode should not be started"); + is(gPrivateBrowsingUI.privateBrowsingEnabled, false, "gPrivateBrowsingUI should expose the correct private browsing status"); // check to see if the Private Browsing mode was deactivated successfully is(observer.data, "exit", "Private Browsing mode was deactivated using the gPrivateBrowsingUI object"); is(pbMenuItem.getAttribute("label"), pbMenuItem.getAttribute("startlabel"), "The Private Browsing menu item should read \"Start Private Browsing\""); diff --git a/browser/components/privatebrowsing/test/browser/ctxmenu-image.png b/browser/components/privatebrowsing/test/browser/ctxmenu-image.png new file mode 100644 index 0000000000000000000000000000000000000000..4c3be508477eb19cd08ddf4d0f568a06a4ec7a45 GIT binary patch literal 5401 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%0AkN;0 zyJ&%i2}6{0DD#K)G49(rgXS=7jscJvI__x|ULfDupElyJ;>x8lzrCH1?L;A&i#7cP z@9sqvU4KIk!4AhhGfrTfVNks*w(=h=d2U%gVy`8jzklHueB*YtM|G-7F zViI{xasv(Il3LkJx>b$5pI36Aa8^g?*ng- + + + Page containing an image + + + + + diff --git a/browser/components/privatebrowsing/test/browser/popup.html b/browser/components/privatebrowsing/test/browser/popup.html new file mode 100644 index 000000000000..333a303469ee --- /dev/null +++ b/browser/components/privatebrowsing/test/browser/popup.html @@ -0,0 +1,11 @@ + + + + Page creating a popup + + + + +