diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 9ebb317ac6..c079f974cd 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -34,6 +34,9 @@ pref("browser.tabs.remote.autostart.1", false); // Bug 945235: Prevent all bars to be considered visible: pref("toolkit.defaultChromeFeatures", "chrome,dialog=no,close,resizable,scrollbars,extrachrome"); +// Disable focus rings +pref("browser.display.focus_ring_width", 0); + // Device pixel to CSS px ratio, in percent. Set to -1 to calculate based on display density. pref("browser.viewport.scaleRatio", -1); diff --git a/browser/base/content/aboutTabCrashed.xhtml b/browser/base/content/aboutTabCrashed.xhtml new file mode 100644 index 0000000000..f593b6063b --- /dev/null +++ b/browser/base/content/aboutTabCrashed.xhtml @@ -0,0 +1,56 @@ + + + + + + %htmlDTD; + + %globalDTD; + + %browserDTD; + +]> + + +
+ + + + +&tabCrashed.header;
+&tabCrashed.message;
+
+#ifdef MAKE_E10S_WORK
+
@@ -2979,6 +3085,7 @@
browserStack.removeChild(this.mCurrentBrowser);
this.mCurrentBrowser.setAttribute("remote", true);
browserStack.appendChild(this.mCurrentBrowser);
+ this.tabContainer.firstChild.setAttribute("remote", "true");
}
this.mCurrentTab = this.tabContainer.firstChild;
@@ -3182,6 +3289,22 @@
tab.setAttribute("titlechanged", "true");
]]>
+
"); + }, + run: function (aSnippetsMap) + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element"); + is(snippetsElt.getElementsByTagName("span").length, 1, + "A default snippet is present."); + + aSnippetsMap.delete("snippets"); + } +}, + +{ + desc: "Check that search engine logo has alt text", + setup: function () { }, + run: function () + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + + let searchEngineLogoElt = doc.getElementById("searchEngineLogo"); + ok(searchEngineLogoElt, "Found search engine logo"); + + let altText = searchEngineLogoElt.alt; + ok(typeof altText == "string" && altText.length > 0, + "Search engine logo's alt text is a nonempty string"); + + isnot(altText, "undefined", + "Search engine logo's alt text shouldn't be the string 'undefined'"); + } +}, + +{ + desc: "Check that performing a search fires a search event and records to " + + "Firefox Health Report.", + setup: function () { }, + run: function () { + try { + let cm = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); + cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider"); + } catch (ex) { + // Health Report disabled, or no SearchesProvider. + return Promise.resolve(); + } + + let numSearchesBefore = 0; + let deferred = Promise.defer(); + let doc = gBrowser.contentDocument; + let engineName = doc.documentElement.getAttribute("searchEngineName"); + + doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) { + let data = JSON.parse(e.detail); + is(data.engineName, engineName, "Detail is search engine name"); + + // We use executeSoon() to ensure that this code runs after the + // count has been updated in browser.js, since it uses the same + // event. + executeSoon(function () { + getNumberOfSearches(engineName).then(num => { + is(num, numSearchesBefore + 1, "One more search recorded."); + deferred.resolve(); + }); + }); + }, true, true); + + // Get the current number of recorded searches. + getNumberOfSearches(engineName).then(num => { + numSearchesBefore = num; + + info("Perform a search."); + doc.getElementById("searchText").value = "a search"; + doc.getElementById("searchSubmit").click(); + gBrowser.stop(); + }); + + return deferred.promise; + } +}, + +{ + desc: "Check snippets map is cleared if cached version is old", + setup: function (aSnippetsMap) + { + aSnippetsMap.set("snippets", "test"); + aSnippetsMap.set("snippets-cached-version", 0); + }, + run: function (aSnippetsMap) + { + ok(!aSnippetsMap.has("snippets"), "snippets have been properly cleared"); + ok(!aSnippetsMap.has("snippets-cached-version"), + "cached-version has been properly cleared"); + } +}, + +{ + desc: "Check cached snippets are shown if cached version is current", + setup: function (aSnippetsMap) + { + aSnippetsMap.set("snippets", "test"); + }, + run: function (aSnippetsMap) + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element"); + is(snippetsElt.innerHTML, "test", "Cached snippet is present."); + + is(aSnippetsMap.get("snippets"), "test", "snippets still cached"); + is(aSnippetsMap.get("snippets-cached-version"), + AboutHomeUtils.snippetsVersion, + "cached-version is correct"); + ok(aSnippetsMap.has("snippets-last-update"), "last-update still exists"); + } +}, + +{ + desc: "Check if the 'Know Your Rights default snippet is shown when 'browser.rights.override' pref is set", + beforeRun: function () + { + Services.prefs.setBoolPref("browser.rights.override", false); + }, + setup: function () { }, + run: function (aSnippetsMap) + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let showRights = AboutHomeUtils.showKnowYourRights; + + ok(showRights, "AboutHomeUtils.showKnowYourRights should be TRUE"); + + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element"); + is(snippetsElt.getElementsByTagName("a")[0].href, "about:rights", "Snippet link is present."); + + Services.prefs.clearUserPref("browser.rights.override"); + } +}, + +{ + desc: "Check if the 'Know Your Rights default snippet is NOT shown when 'browser.rights.override' pref is NOT set", + beforeRun: function () + { + Services.prefs.setBoolPref("browser.rights.override", true); + }, + setup: function () { }, + run: function (aSnippetsMap) + { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let rightsData = AboutHomeUtils.knowYourRightsData; + + ok(!rightsData, "AboutHomeUtils.knowYourRightsData should be FALSE"); + + let snippetsElt = doc.getElementById("snippets"); + ok(snippetsElt, "Found snippets element"); + ok(snippetsElt.getElementsByTagName("a")[0].href != "about:rights", "Snippet link should not point to about:rights."); + + Services.prefs.clearUserPref("browser.rights.override"); + } +}, + +{ + desc: "Check that the search UI/ action is updated when the search engine is changed", + setup: function() {}, + run: function() + { + let currEngine = Services.search.currentEngine; + let unusedEngines = [].concat(Services.search.getVisibleEngines()).filter(x => x != currEngine); + let searchbar = document.getElementById("searchbar"); + + function checkSearchUI(engine) { + let doc = gBrowser.selectedTab.linkedBrowser.contentDocument; + let searchText = doc.getElementById("searchText"); + let logoElt = doc.getElementById("searchEngineLogo"); + let engineName = doc.documentElement.getAttribute("searchEngineName"); + + is(engineName, engine.name, "Engine name should've been updated"); + + if (!logoElt.parentNode.hidden) { + is(logoElt.alt, engineName, "Alt text of logo image should match search engine name") + } else { + is(searchText.placeholder, engineName, "Placeholder text should match search engine name"); + } + } + // Do a sanity check that all attributes are correctly set to begin with + checkSearchUI(currEngine); + + let deferred = Promise.defer(); + promiseBrowserAttributes(gBrowser.selectedTab).then(function() { + // Test if the update propagated + checkSearchUI(unusedEngines[0]); + searchbar.currentEngine = currEngine; + deferred.resolve(); + }); + + // The following cleanup function will set currentEngine back to the previous + // engine if we fail to do so above. + registerCleanupFunction(function() { + searchbar.currentEngine = currEngine; + }); + // Set the current search engine to an unused one + searchbar.currentEngine = unusedEngines[0]; + searchbar.select(); + return deferred.promise; + } +}, + +{ + desc: "Check POST search engine support", + setup: function() {}, + run: function() + { + let deferred = Promise.defer(); + let currEngine = Services.search.defaultEngine; + let searchObserver = function search_observer(aSubject, aTopic, aData) { + let engine = aSubject.QueryInterface(Ci.nsISearchEngine); + info("Observer: " + aData + " for " + engine.name); + + if (aData != "engine-added") + return; + + if (engine.name != "POST Search") + return; + + // Ready to execute the tests! + let needle = "Search for something awesome."; + let document = gBrowser.selectedTab.linkedBrowser.contentDocument; + let searchText = document.getElementById("searchText"); + + // We're about to change the search engine. Once the change has + // propagated to the about:home content, we want to perform a search. + let mutationObserver = new MutationObserver(function (mutations) { + for (let mutation of mutations) { + if (mutation.attributeName == "searchEngineName") { + searchText.value = needle; + searchText.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}); + } + } + }); + mutationObserver.observe(document.documentElement, { attributes: true }); + + // Change the search engine, triggering the observer above. + Services.search.defaultEngine = engine; + + registerCleanupFunction(function() { + mutationObserver.disconnect(); + Services.search.removeEngine(engine); + Services.search.defaultEngine = currEngine; + }); + + + // When the search results load, check them for correctness. + waitForLoad(function() { + let loadedText = gBrowser.contentDocument.body.textContent; + ok(loadedText, "search page loaded"); + is(loadedText, "searchterms=" + escape(needle.replace(/\s/g, "+")), + "Search text should arrive correctly"); + deferred.resolve(); + }); + }; + Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false); + registerCleanupFunction(function () { + Services.obs.removeObserver(searchObserver, "browser-search-engine-modified"); + }); + Services.search.addEngine("http://test:80/browser/browser/base/content/test/POSTSearchEngine.xml", + Ci.nsISearchEngine.DATA_XML, null, false); + return deferred.promise; + } +} + +]; + +function test() +{ + waitForExplicitFinish(); + requestLongerTimeout(2); + ignoreAllUncaughtExceptions(); + + Task.spawn(function () { + for (let test of gTests) { + info(test.desc); + + if (test.beforeRun) + yield test.beforeRun(); + + let tab = yield promiseNewTabLoadEvent("about:home", "DOMContentLoaded"); + + // Must wait for both the snippets map and the browser attributes, since + // can't guess the order they will happen. + // So, start listening now, but verify the promise is fulfilled only + // after the snippets map setup. + let promise = promiseBrowserAttributes(tab); + // Prepare the snippets map with default values, then run the test setup. + let snippetsMap = yield promiseSetupSnippetsMap(tab, test.setup); + // Ensure browser has set attributes already, or wait for them. + yield promise; + info("Running test"); + yield test.run(snippetsMap); + info("Cleanup"); + gBrowser.removeCurrentTab(); + } + }).then(finish, ex => { + ok(false, "Unexpected Exception: " + ex); + finish(); + }); +} + +/** + * Creates a new tab and waits for a load event. + * + * @param aUrl + * The url to load in a new tab. + * @param aEvent + * The load event type to wait for. Defaults to "load". + * @return {Promise} resolved when the event is handled. Gets the new tab. + */ +function promiseNewTabLoadEvent(aUrl, aEventType="load") +{ + let deferred = Promise.defer(); + let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl); + info("Wait tab event: " + aEventType); + tab.linkedBrowser.addEventListener(aEventType, function load(event) { + if (event.originalTarget != tab.linkedBrowser.contentDocument || + event.target.location.href == "about:blank") { + info("skipping spurious load event"); + return; + } + tab.linkedBrowser.removeEventListener(aEventType, load, true); + info("Tab event received: " + aEventType); + deferred.resolve(tab); + }, true); + return deferred.promise; +} + +/** + * Cleans up snippets and ensures that by default we don't try to check for + * remote snippets since that may cause network bustage or slowness. + * + * @param aTab + * The tab containing about:home. + * @param aSetupFn + * The setup function to be run. + * @return {Promise} resolved when the snippets are ready. Gets the snippets map. + */ +function promiseSetupSnippetsMap(aTab, aSetupFn) +{ + let deferred = Promise.defer(); + let cw = aTab.linkedBrowser.contentWindow.wrappedJSObject; + info("Waiting for snippets map"); + cw.ensureSnippetsMapThen(function (aSnippetsMap) { + info("Got snippets map: " + + "{ last-update: " + aSnippetsMap.get("snippets-last-update") + + ", cached-version: " + aSnippetsMap.get("snippets-cached-version") + + " }"); + // Don't try to update. + aSnippetsMap.set("snippets-last-update", Date.now()); + aSnippetsMap.set("snippets-cached-version", AboutHomeUtils.snippetsVersion); + // Clear snippets. + aSnippetsMap.delete("snippets"); + aSetupFn(aSnippetsMap); + // Must be sure to continue after the page snippets map setup. + executeSoon(function() deferred.resolve(aSnippetsMap)); + }); + return deferred.promise; +} + +/** + * Waits for the attributes being set by browser.js and overwrites snippetsURL + * to ensure we won't try to hit the network and we can force xhr to throw. + * + * @param aTab + * The tab containing about:home. + * @return {Promise} resolved when the attributes are ready. + */ +function promiseBrowserAttributes(aTab) +{ + let deferred = Promise.defer(); + + let docElt = aTab.linkedBrowser.contentDocument.documentElement; + //docElt.setAttribute("snippetsURL", "nonexistent://test"); + let observer = new MutationObserver(function (mutations) { + for (let mutation of mutations) { + info("Got attribute mutation: " + mutation.attributeName + + " from " + mutation.oldValue); + if (mutation.attributeName == "snippetsURL" && + docElt.getAttribute("snippetsURL") != "nonexistent://test") { + docElt.setAttribute("snippetsURL", "nonexistent://test"); + } + + // Now we just have to wait for the last attribute. + if (mutation.attributeName == "searchEngineName") { + info("Remove attributes observer"); + observer.disconnect(); + // Must be sure to continue after the page mutation observer. + executeSoon(function() deferred.resolve()); + break; + } + } + }); + info("Add attributes observer"); + observer.observe(docElt, { attributes: true }); + + return deferred.promise; +} + +/** + * Retrieves the number of about:home searches recorded for the current day. + * + * @param aEngineName + * name of the setup search engine. + * + * @return {Promise} Returns a promise resolving to the number of searches. + */ +function getNumberOfSearches(aEngineName) { + let reporter = Components.classes["@mozilla.org/datareporting/service;1"] + .getService() + .wrappedJSObject + .healthReporter; + ok(reporter, "Health Reporter instance available."); + + return reporter.onInit().then(function onInit() { + let provider = reporter.getProvider("org.mozilla.searches"); + ok(provider, "Searches provider is available."); + + let m = provider.getMeasurement("counts", 2); + return m.getValues().then(data => { + let now = new Date(); + let yday = new Date(now); + yday.setDate(yday.getDate() - 1); + + // Add the number of searches recorded yesterday to the number of searches + // recorded today. This makes the test not fail intermittently when it is + // run at midnight and we accidentally compare the number of searches from + // different days. Tests are always run with an empty profile so there + // are no searches from yesterday, normally. Should the test happen to run + // past midnight we make sure to count them in as well. + return getNumberOfSearchesByDate(aEngineName, data, now) + + getNumberOfSearchesByDate(aEngineName, data, yday); + }); + }); +} + +function getNumberOfSearchesByDate(aEngineName, aData, aDate) { + if (aData.days.hasDay(aDate)) { + let id = Services.search.getEngineByName(aEngineName).identifier; + + let day = aData.days.getDay(aDate); + let field = id + ".abouthome"; + + if (day.has(field)) { + return day.get(field) || 0; + } + } + + return 0; // No records found. +} + +function waitForLoad(cb) { + let browser = gBrowser.selectedBrowser; + browser.addEventListener("load", function listener() { + if (browser.currentURI.spec == "about:blank") + return; + info("Page loaded: " + browser.currentURI.spec); + browser.removeEventListener("load", listener, true); + + cb(); + }, true); +} diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js index b1e78d6a95..79b020af89 100644 --- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -215,6 +215,7 @@ function openLinkIn(url, where, params) { var aCharset = params.charset; var aReferrerURI = params.referrerURI; var aRelatedToCurrent = params.relatedToCurrent; + var aAllowMixedContent = params.allowMixedContent; var aInBackground = params.inBackground; var aDisallowInheritPrincipal = params.disallowInheritPrincipal; var aInitiatingDoc = params.initiatingDoc; @@ -328,7 +329,8 @@ function openLinkIn(url, where, params) { postData: aPostData, inBackground: loadInBackground, allowThirdPartyFixup: aAllowThirdPartyFixup, - relatedToCurrent: aRelatedToCurrent}); + relatedToCurrent: aRelatedToCurrent, + allowMixedContent: aAllowMixedContent}); break; } diff --git a/browser/base/jar.mn b/browser/base/jar.mn index 9031f3beb4..b9fc3b4063 100644 --- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -47,6 +47,7 @@ browser.jar: content/browser/aboutRobots-widget-left.png (content/aboutRobots-widget-left.png) content/browser/autorecovery.js (content/autorecovery.js) content/browser/autorecovery.xul (content/autorecovery.xul) + content/browser/aboutTabCrashed.xhtml (content/aboutTabCrashed.xhtml) * content/browser/browser.css (content/browser.css) content/browser/browser-menudragging.xul (content/browser-menudragging.xul) content/browser/browser-menudragging.js (content/browser-menudragging.js) diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index d927b79363..8c2928c58e 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -37,6 +37,10 @@ static RedirEntry kRedirMap[] = { nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT }, + { "tabcrashed", "chrome://browser/content/aboutTabCrashed.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT }, { "feeds", "chrome://browser/content/feeds/subscribe.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT | diff --git a/browser/components/build/nsModule.cpp b/browser/components/build/nsModule.cpp index d5b79b455d..971baafac8 100644 --- a/browser/components/build/nsModule.cpp +++ b/browser/components/build/nsModule.cpp @@ -87,6 +87,7 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = { { NS_FEEDSNIFFER_CONTRACTID, &kNS_FEEDSNIFFER_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "certerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "socialerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "tabcrashed", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "feeds", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "privatebrowsing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "rights", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index b337640505..982e3a3506 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -13,9 +13,15 @@ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AboutHome", + "resource:///modules/AboutHome.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ContentClick", + "resource:///modules/ContentClick.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); @@ -501,6 +507,7 @@ BrowserGlue.prototype = { NewTabUtils.init(); BrowserNewTabPreloader.init(); webrtcUI.init(); + AboutHome.init(); FormValidationHandler.init(); LoginManagerParent.init(); @@ -510,6 +517,9 @@ BrowserGlue.prototype = { Services.prefs.setBoolPref('media.mediasource.webm.enabled', false); } + if (Services.prefs.getBoolPref("browser.tabs.remote")) + ContentClick.init(); + Services.obs.notifyObservers(null, "browser-ui-startup-complete", ""); AddonWatcher.init(this._notifySlowAddon); diff --git a/browser/components/preferences/aboutPermissions.js b/browser/components/preferences/aboutPermissions.js index 1da646c396..0f4d03b7a4 100644 --- a/browser/components/preferences/aboutPermissions.js +++ b/browser/components/preferences/aboutPermissions.js @@ -534,6 +534,7 @@ let AboutPermissions = { Services.prefs.addObserver("dom.webnotifications.enabled", this, false); Services.prefs.addObserver("xpinstall.whitelist.required", this, false); Services.prefs.addObserver("geo.enabled", this, false); + Services.prefs.addObserver("dom.push.enabled", this, false); Services.prefs.addObserver("dom.indexedDB.enabled", this, false); Services.prefs.addObserver("plugins.click_to_play", this, false); Services.prefs.addObserver("full-screen-api.enabled", this, false); @@ -687,6 +688,7 @@ let AboutPermissions = { Services.prefs.removeObserver("dom.webnotifications.enabled", this, false); Services.prefs.removeObserver("xpinstall.whitelist.required", this, false); Services.prefs.removeObserver("geo.enabled", this, false); + Services.prefs.removeObserver("dom.push.enabled", this, false); Services.prefs.removeObserver("dom.indexedDB.enabled", this, false); Services.prefs.removeObserver("plugins.click_to_play", this, false); Services.prefs.removeObserver("full-screen-api.enabled", this, false); diff --git a/browser/components/preferences/tests/browser_permissions.js b/browser/components/preferences/tests/browser_permissions.js new file mode 100644 index 0000000000..2a896b3e0d --- /dev/null +++ b/browser/components/preferences/tests/browser_permissions.js @@ -0,0 +1,331 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Components.utils.import("resource://gre/modules/NetUtil.jsm"); + +const ABOUT_PERMISSIONS_SPEC = "about:permissions"; + +const TEST_URI_1 = NetUtil.newURI("http://mozilla.com/"); +const TEST_URI_2 = NetUtil.newURI("http://mozilla.org/"); + +const TEST_PRINCIPAL_1 = Services.scriptSecurityManager.getNoAppCodebasePrincipal(TEST_URI_1); +const TEST_PRINCIPAL_2 = Services.scriptSecurityManager.getNoAppCodebasePrincipal(TEST_URI_2); + +// values from DefaultPermissions object +const PERM_UNKNOWN = 0; +const PERM_ALLOW = 1; +const PERM_DENY = 2; +// cookie specific permissions +const PERM_FIRST_PARTY_ONLY = 9; + +// used to set permissions on test sites +const TEST_PERMS = { + "password": PERM_ALLOW, + "cookie": PERM_ALLOW, + "geo": PERM_UNKNOWN, + "push": PERM_DENY, + "indexedDB": PERM_UNKNOWN, + "popup": PERM_DENY, + "fullscreen" : PERM_UNKNOWN, + "camera": PERM_UNKNOWN, + "microphone": PERM_UNKNOWN +}; + +const NO_GLOBAL_ALLOW = [ + "geo", + "indexedDB", + "fullscreen" +]; + +// number of managed permissions in the interface +const TEST_PERMS_COUNT = 9; + +function test() { + waitForExplicitFinish(); + registerCleanupFunction(cleanUp); + + // add test history visit + PlacesTestUtils.addVisits(TEST_URI_1).then(() => { + // set permissions ourselves to avoid problems with different defaults + // from test harness configuration + for (let type in TEST_PERMS) { + if (type == "password") { + Services.logins.setLoginSavingEnabled(TEST_URI_2.prePath, true); + } else { + // set permissions on a site without history visits to test enumerateServices + Services.perms.addFromPrincipal(TEST_PRINCIPAL_2, type, TEST_PERMS[type]); + } + } + + // open about:permissions + gBrowser.selectedTab = gBrowser.addTab("about:permissions"); + }); + + function observer() { + Services.obs.removeObserver(observer, "browser-permissions-initialized"); + runNextTest(); + } + Services.obs.addObserver(observer, "browser-permissions-initialized", false); +} + +function cleanUp() { + for (let type in TEST_PERMS) { + if (type != "password") { + Services.perms.removeFromPrincipal(TEST_PRINCIPAL_1, type); + Services.perms.removeFromPrincipal(TEST_PRINCIPAL_2, type); + } + } + + gBrowser.removeTab(gBrowser.selectedTab); +} + +function runNextTest() { + if (gTestIndex == tests.length) { + PlacesTestUtils.clearHistory().then(finish); + return; + } + + let nextTest = tests[gTestIndex++]; + info("[" + nextTest.name + "] running test"); + nextTest(); +} + +var gSitesList; +var gHeaderDeck; +var gSiteLabel; + +var gTestIndex = 0; +var tests = [ + function test_page_load() { + is(gBrowser.currentURI.spec, ABOUT_PERMISSIONS_SPEC, "about:permissions loaded"); + + gSitesList = gBrowser.contentDocument.getElementById("sites-list"); + ok(gSitesList, "got sites list"); + + gHeaderDeck = gBrowser.contentDocument.getElementById("header-deck"); + ok(gHeaderDeck, "got header deck"); + + gSiteLabel = gBrowser.contentDocument.getElementById("site-label"); + ok(gSiteLabel, "got site label"); + + runNextTest(); + }, + + function test_sites_list() { + is(gSitesList.firstChild.id, "all-sites-item", + "all sites is the first item in the sites list"); + + ok(getSiteItem(TEST_URI_1.host), "site item from places db exists"); + ok(getSiteItem(TEST_URI_2.host), "site item from enumerating services exists"); + + runNextTest(); + }, + + function test_filter_sites_list() { + // set filter to test host + let sitesFilter = gBrowser.contentDocument.getElementById("sites-filter"); + sitesFilter.value = TEST_URI_1.host; + sitesFilter.doCommand(); + + // make sure correct sites are collapsed/showing + let testSite1 = getSiteItem(TEST_URI_1.host); + ok(!testSite1.collapsed, "test site 1 is not collapsed"); + let testSite2 = getSiteItem(TEST_URI_2.host); + ok(testSite2.collapsed, "test site 2 is collapsed"); + + // clear filter + sitesFilter.value = ""; + sitesFilter.doCommand(); + + runNextTest(); + }, + + function test_all_sites() { + // "All Sites" item should be selected when the page is first loaded + is(gSitesList.selectedItem, gBrowser.contentDocument.getElementById("all-sites-item"), + "all sites item is selected"); + + let defaultsHeader = gBrowser.contentDocument.getElementById("defaults-header"); + is(defaultsHeader, gHeaderDeck.selectedPanel, + "correct header shown for all sites"); + + ok(gBrowser.contentDocument.getElementById("passwords-count").hidden, + "passwords count is hidden"); + ok(gBrowser.contentDocument.getElementById("cookies-count").hidden, + "cookies count is hidden"); + + // Test to make sure "Allow" items hidden for certain permission types + NO_GLOBAL_ALLOW.forEach(function(aType) { + let menuitem = gBrowser.contentDocument.getElementById(aType + "-" + PERM_ALLOW); + ok(menuitem.hidden, aType + " allow menuitem hidden for all sites"); + }); + + runNextTest(); + }, + + function test_all_sites_permission() { + // apply the old default of allowing all cookies + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + + // there should be no user-set pref for cookie behavior + is(Services.prefs.getIntPref("network.cookie.cookieBehavior"), PERM_UNKNOWN, + "network.cookie.cookieBehavior is expected default"); + + // the default behavior is to allow cookies + let cookieMenulist = getPermissionMenulist("cookie"); + is(cookieMenulist.value, PERM_ALLOW, + "menulist correctly shows that cookies are allowed"); + + // set the pref to block cookies + Services.prefs.setIntPref("network.cookie.cookieBehavior", PERM_DENY); + // check to make sure this change is reflected in the UI + is(cookieMenulist.value, PERM_DENY, "menulist correctly shows that cookies are blocked"); + + // clear the pref + Services.prefs.clearUserPref("network.cookie.cookieBehavior"); + + runNextTest(); + }, + + function test_manage_all_passwords() { + // make sure "Manage All Passwords..." button opens the correct dialog + addWindowListener("chrome://passwordmgr/content/passwordManager.xul", runNextTest); + gBrowser.contentDocument.getElementById("passwords-manage-all-button").doCommand(); + + }, + + function test_manage_all_cookies() { + // make sure "Manage All Cookies..." button opens the correct dialog + addWindowListener("chrome://browser/content/preferences/cookies.xul", runNextTest); + gBrowser.contentDocument.getElementById("cookies-manage-all-button").doCommand(); + }, + + function test_select_site() { + // select the site that has the permissions we set at the beginning of the test + let testSiteItem = getSiteItem(TEST_URI_2.host); + gSitesList.selectedItem = testSiteItem; + + let siteHeader = gBrowser.contentDocument.getElementById("site-header"); + is(siteHeader, gHeaderDeck.selectedPanel, + "correct header shown for a specific site"); + is(gSiteLabel.value, TEST_URI_2.host, "header updated for selected site"); + + ok(!gBrowser.contentDocument.getElementById("passwords-count").hidden, + "passwords count is not hidden"); + ok(!gBrowser.contentDocument.getElementById("cookies-count").hidden, + "cookies count is not hidden"); + + // Test to make sure "Allow" items are *not* hidden for certain permission types + NO_GLOBAL_ALLOW.forEach(function(aType) { + let menuitem = gBrowser.contentDocument.getElementById(aType + "-" + PERM_ALLOW); + ok(!menuitem.hidden, aType + " allow menuitem not hidden for single site"); + }); + + runNextTest(); + }, + + function test_permissions() { + let menulists = gBrowser.contentDocument.getElementsByClassName("pref-menulist"); + is(menulists.length, TEST_PERMS_COUNT, "got expected number of managed permissions"); + + for (let i = 0; i < menulists.length; i++) { + let permissionMenulist = menulists.item(i); + let permissionType = permissionMenulist.getAttribute("type"); + + // permissions should reflect what we set at the beginning of the test + is(permissionMenulist.value, TEST_PERMS[permissionType], + "got expected value for " + permissionType + " permission"); + } + + runNextTest(); + }, + + function test_permission_change() { + let geoMenulist = getPermissionMenulist("geo"); + is(geoMenulist.value, PERM_UNKNOWN, "menulist correctly shows that geolocation permission is unspecified"); + + // change a permission programatically + Services.perms.addFromPrincipal(TEST_PRINCIPAL_2, "geo", PERM_DENY); + // check to make sure this change is reflected in the UI + is(geoMenulist.value, PERM_DENY, "menulist shows that geolocation is blocked"); + + // change a permisssion in the UI + let geoAllowItem = gBrowser.contentDocument.getElementById("geo-" + PERM_ALLOW); + geoMenulist.selectedItem = geoAllowItem; + geoMenulist.doCommand(); + // check to make sure this change is reflected in the permission manager + is(Services.perms.testPermissionFromPrincipal(TEST_PRINCIPAL_2, "geo"), PERM_ALLOW, + "permission manager shows that geolocation is allowed"); + + + // change a site-specific cookie permission, just for fun + let cookieMenuList = getPermissionMenulist("cookie"); + let cookieItem = gBrowser.contentDocument.getElementById("cookie-" + PERM_FIRST_PARTY_ONLY); + cookieMenuList.selectedItem = cookieItem; + cookieMenuList.doCommand(); + is(cookieMenuList.value, PERM_FIRST_PARTY_ONLY, "menulist correctly shows that " + + "first party only cookies are allowed"); + is(Services.perms.testPermissionFromPrincipal(TEST_PRINCIPAL_2, "cookie"), + PERM_FIRST_PARTY_ONLY, "permission manager shows that first party cookies " + + "are allowed"); + + runNextTest(); + }, + + function test_forget_site() { + // click "Forget About This Site" button + gBrowser.contentDocument.getElementById("forget-site-button").doCommand(); + PlacesTestUtils.clearHistory().then(() => { + is(gSiteLabel.value, "", "site label cleared"); + + let allSitesItem = gBrowser.contentDocument.getElementById("all-sites-item"); + is(gSitesList.selectedItem, allSitesItem, + "all sites item selected after forgetting selected site"); + + // check to make sure site is gone from sites list + let testSiteItem = getSiteItem(TEST_URI_2.host); + ok(!testSiteItem, "site removed from sites list"); + + // check to make sure we forgot all permissions corresponding to site + for (let type in TEST_PERMS) { + if (type == "password") { + ok(Services.logins.getLoginSavingEnabled(TEST_URI_2.prePath), + "password saving should be enabled by default"); + } else { + is(Services.perms.testPermissionFromPrincipal(TEST_PRINCIPAL_2, type), PERM_UNKNOWN, + type + " permission should not be set for test site 2"); + } + } + + runNextTest(); + }); + } +]; + +function getPermissionMenulist(aType) { + return gBrowser.contentDocument.getElementById(aType + "-menulist"); +} + +function getSiteItem(aHost) { + return gBrowser.contentDocument. + querySelector(".site[value='" + aHost + "']"); +} + +function addWindowListener(aURL, aCallback) { + Services.wm.addListener({ + onOpenWindow: function(aXULWindow) { + info("window opened, waiting for focus"); + Services.wm.removeListener(this); + + var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + waitForFocus(function() { + is(domwindow.document.location.href, aURL, "should have seen the right window open"); + domwindow.close(); + aCallback(); + }, domwindow); + }, + onCloseWindow: function(aXULWindow) { }, + onWindowTitleChange: function(aXULWindow, aNewTitle) { } + }); +} diff --git a/browser/locales/en-US/chrome/browser/browser.dtd b/browser/locales/en-US/chrome/browser/browser.dtd index 0225f422bd..d9b08d35c4 100644 --- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -656,3 +656,7 @@ just addresses the organization to follow, e.g. "This site is run by " --> + + + + diff --git a/browser/modules/AboutHome.jsm b/browser/modules/AboutHome.jsm new file mode 100644 index 0000000000..35684dedf9 --- /dev/null +++ b/browser/modules/AboutHome.jsm @@ -0,0 +1,210 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +let Cc = Components.classes; +let Ci = Components.interfaces; +let Cu = Components.utils; + +this.EXPORTED_SYMBOLS = [ "AboutHomeUtils", "AboutHome" ]; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); + +// Url to fetch snippets, in the urlFormatter service format. +const SNIPPETS_URL_PREF = "browser.aboutHomeSnippets.updateUrl"; + +// Should be bumped up if the snippets content format changes. +const STARTPAGE_VERSION = 4; + +this.AboutHomeUtils = { + get snippetsVersion() STARTPAGE_VERSION, + + /* + * showKnowYourRights - Determines if the user should be shown the + * about:rights notification. The notification should *not* be shown if + * we've already shown the current version, or if the override pref says to + * never show it. The notification *should* be shown if it's never been seen + * before, if a newer version is available, or if the override pref says to + * always show it. + */ + get showKnowYourRights() { + // Look for an unconditional override pref. If set, do what it says. + // (true --> never show, false --> always show) + try { + return !Services.prefs.getBoolPref("browser.rights.override"); + } catch (e) { } + // Ditto, for the legacy EULA pref. + try { + return !Services.prefs.getBoolPref("browser.EULA.override"); + } catch (e) { } + +#ifndef MOZILLA_OFFICIAL + // Non-official builds shouldn't show the notification. + return false; +#endif + + // Look to see if the user has seen the current version or not. + var currentVersion = Services.prefs.getIntPref("browser.rights.version"); + try { + return !Services.prefs.getBoolPref("browser.rights." + currentVersion + ".shown"); + } catch (e) { } + + // Legacy: If the user accepted a EULA, we won't annoy them with the + // equivalent about:rights page until the version changes. + try { + return !Services.prefs.getBoolPref("browser.EULA." + currentVersion + ".accepted"); + } catch (e) { } + + // We haven't shown the notification before, so do so now. + return true; + } +}; + +/** + * Returns the URL to fetch snippets from, in the urlFormatter service format. + */ +XPCOMUtils.defineLazyGetter(AboutHomeUtils, "snippetsURL", function() { + let updateURL = Services.prefs + .getCharPref(SNIPPETS_URL_PREF) + .replace("%STARTPAGE_VERSION%", STARTPAGE_VERSION); + return Services.urlFormatter.formatURL(updateURL); +}); + +/** + * This code provides services to the about:home page. Whenever + * about:home needs to do something chrome-privileged, it sends a + * message that's handled here. + */ +let AboutHome = { + MESSAGES: [ + "AboutHome:RestorePreviousSession", + "AboutHome:Downloads", + "AboutHome:Bookmarks", + "AboutHome:History", + "AboutHome:Apps", + "AboutHome:Addons", + "AboutHome:Sync", + "AboutHome:Settings", + "AboutHome:RequestUpdate", + "AboutHome:Search", + ], + + init: function() { + let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager); + + for (let msg of this.MESSAGES) { + mm.addMessageListener(msg, this); + } + + Services.obs.addObserver(this, "browser-search-engine-modified", false); + }, + + observe: function(aEngine, aTopic, aVerb) { + switch (aTopic) { + case "browser-search-engine-modified": + this.sendAboutHomeData(null); + break; + } + }, + + receiveMessage: function(aMessage) { + let window = aMessage.target.ownerDocument.defaultView; + + switch (aMessage.name) { + case "AboutHome:RestorePreviousSession": + let ss = Cc["@mozilla.org/browser/sessionstore;1"]. + getService(Ci.nsISessionStore); + if (ss.canRestoreLastSession) { + ss.restoreLastSession(); + } + break; + + case "AboutHome:Downloads": + window.BrowserDownloadsUI(); + break; + + case "AboutHome:Bookmarks": + window.PlacesCommandHook.showPlacesOrganizer("AllBookmarks"); + break; + + case "AboutHome:History": + window.PlacesCommandHook.showPlacesOrganizer("History"); + break; + + case "AboutHome:Apps": + window.openUILinkIn("https://marketplace.mozilla.org/", "tab"); + break; + + case "AboutHome:Addons": + window.BrowserOpenAddonsMgr(); + break; + + case "AboutHome:Sync": + window.openPreferences("paneSync"); + break; + + case "AboutHome:Settings": + window.openPreferences(); + break; + + case "AboutHome:RequestUpdate": + this.sendAboutHomeData(aMessage.target); + break; + + case "AboutHome:Search": + let data; + try { + data = JSON.parse(aMessage.data.searchData); + } catch(ex) { + Cu.reportError(ex); + break; + } +#ifdef MOZ_SERVICES_HEALTHREPORT + window.BrowserSearch.recordSearchInHealthReport(data.engineName, "abouthome"); +#endif + // Trigger a search through nsISearchEngine.getSubmission() + let submission = Services.search.currentEngine.getSubmission(data.searchTerms); + window.loadURI(submission.uri.spec, null, submission.postData); + break; + } + }, + + // Send all the chrome-privileged data needed by about:home. This + // gets re-sent when the search engine changes. + sendAboutHomeData: function(target) { + let wrapper = {}; + Components.utils.import("resource:///modules/sessionstore/SessionStore.jsm", + wrapper); + let ss = wrapper.SessionStore; + ss.promiseInitialized.then(function() { + let data = { + showRestoreLastSession: ss.canRestoreLastSession, + snippetsURL: AboutHomeUtils.snippetsURL, + showKnowYourRights: AboutHomeUtils.showKnowYourRights, + snippetsVersion: AboutHomeUtils.snippetsVersion, + defaultEngineName: Services.search.defaultEngine.name + }; + + if (AboutHomeUtils.showKnowYourRights) { + // Set pref to indicate we've shown the notification. + let currentVersion = Services.prefs.getIntPref("browser.rights.version"); + Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true); + } + + if (target) { + target.messageManager.sendAsyncMessage("AboutHome:Update", data); + } else { + let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager); + mm.broadcastAsyncMessage("AboutHome:Update", data); + } + }).then(null, function onError(x) { + Cu.reportError("Error in AboutHome.sendAboutHomeData " + x); + }); + }, +}; diff --git a/browser/modules/ContentClick.jsm b/browser/modules/ContentClick.jsm new file mode 100644 index 0000000000..d09affd8f5 --- /dev/null +++ b/browser/modules/ContentClick.jsm @@ -0,0 +1,82 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +let Cc = Components.classes; +let Ci = Components.interfaces; +let Cu = Components.utils; + +this.EXPORTED_SYMBOLS = [ "ContentClick" ]; + +Cu.import("resource:///modules/PlacesUIUtils.jsm"); +Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +let ContentClick = { + init: function() { + let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager); + mm.addMessageListener("Content:Click", this); + }, + + receiveMessage: function (message) { + switch (message.name) { + case "Content:Click": + this.contentAreaClick(message.json, message.target) + break; + } + }, + + contentAreaClick: function (json, browser) { + // This is heavily based on contentAreaClick from browser.js (Bug 903016) + // The json is set up in a way to look like an Event. + let window = browser.ownerDocument.defaultView; + + if (!json.href) { + // Might be middle mouse navigation. + if (Services.prefs.getBoolPref("middlemouse.contentLoadURL") && + !Services.prefs.getBoolPref("general.autoScroll")) { + window.middleMousePaste(json); + } + return; + } + + if (json.bookmark) { + // This is the Opera convention for a special link that, when clicked, + // allows to add a sidebar panel. The link's title attribute contains + // the title that should be used for the sidebar panel. + PlacesUIUtils.showBookmarkDialog({ action: "add" + , type: "bookmark" + , uri: Services.io.newURI(json.href, null, null) + , title: json.title + , loadBookmarkInSidebar: true + , hiddenRows: [ "description" + , "location" + , "keyword" ] + }, window); + return; + } + + // Note: We don't need the sidebar code here. + + // This part is based on handleLinkClick. + var where = window.whereToOpenLink(json); + if (where == "current") + return false; + + // Todo(903022): code for where == save + + window.openLinkIn(json.href, where, { referrerURI: browser.documentURI, + charset: browser.characterSet }); + + // Mark the page as a user followed link. This is done so that history can + // distinguish automatic embed visits from user activated ones. For example + // pages loaded in frames are embed visits and lost with the session, while + // visits across frames should be preserved. + try { + if (!PrivateBrowsingUtils.isWindowPrivate(window)) + PlacesUIUtils.markPageAsFollowedLink(href); + } catch (ex) { /* Skip invalid URIs. */ } + } +}; diff --git a/browser/modules/moz.build b/browser/modules/moz.build index b3459bcd5a..9e108ed7b9 100644 --- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -11,6 +11,7 @@ EXTRA_JS_MODULES += [ 'promise.js' ] EXTRA_JS_MODULES += [ 'BrowserNewTabPreloader.jsm', 'CharsetMenu.jsm', + 'ContentClick.jsm', 'FormSubmitObserver.jsm', 'FormValidationHandler.jsm', 'NetworkPrioritizer.jsm', @@ -31,9 +32,9 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': ] EXTRA_PP_JS_MODULES += [ - 'AboutHomeUtils.jsm', + 'AboutHome.jsm', 'RecentWindow.jsm', ] if CONFIG['MOZILLA_OFFICIAL']: - DEFINES['MOZILLA_OFFICIAL'] = 1 \ No newline at end of file + DEFINES['MOZILLA_OFFICIAL'] = 1 diff --git a/browser/themes/linux/aboutTabCrashed.css b/browser/themes/linux/aboutTabCrashed.css new file mode 100644 index 0000000000..d1a9ad53c9 --- /dev/null +++ b/browser/themes/linux/aboutTabCrashed.css @@ -0,0 +1,98 @@ +body { + background-color: rgb(241, 244, 248); + margin-top: 2em; + font: message-box; + font-size: 100%; +} + +p { + font-size: .8em; +} + +#error-box { + background: url('chrome://global/skin/icons/information-24.png') no-repeat left 4px; + -moz-padding-start: 30px; +} + +#error-box:-moz-locale-dir(rtl) { + background-position: right 4px; +} + +#main-error-msg { + color: #4b4b4b; + font-weight: bold; +} + + +#button-box { + text-align: center; + width: 75%; + margin: 0 auto; +} + +@media all and (min-width: 300px) { + #error-box { + max-width: 50%; + margin: 0 auto; + background-image: url('chrome://global/skin/icons/information-32.png'); + min-height: 36px; + -moz-padding-start: 38px; + } + + button { + width: auto !important; + min-width: 150px; + } +} + +@media all and (min-width: 780px) { + #error-box { + max-width: 30%; + } +} + +button { + font: message-box; + font-size: 0.6875em; + -moz-appearance: none; + -moz-user-select: none; + width: 100%; + margin: 2px 0; + padding: 2px 6px; + line-height: 1.2; + background-color: hsla(210,30%,95%,.1); + background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1)); + background-clip: padding-box; + border: 1px solid hsla(210,15%,25%,.4); + border-color: hsla(210,15%,25%,.3) hsla(210,15%,25%,.35) hsla(210,15%,25%,.4); + border-radius: 3px; + box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, + 0 0 0 1px hsla(0,0%,100%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.1); + + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; + +} + +button:hover { + background-color: hsla(210,30%,95%,.8); + border-color: hsla(210,15%,25%,.45) hsla(210,15%,25%,.5) hsla(210,15%,25%,.55); + box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, + 0 0 0 1px hsla(0,0%,100%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.1), + 0 0 3px hsla(210,15%,25%,.1); + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; +} + +button:hover:active { + background-color: hsla(210,15%,25%,.2); + box-shadow: 0 1px 1px hsla(210,15%,25%,.2) inset, + 0 0 2px hsla(210,15%,25%,.4) inset; + transition-property: background-color, border-color, box-shadow; + transition-duration: 10ms; + transition-timing-function: linear; +} diff --git a/browser/themes/linux/browser.css b/browser/themes/linux/browser.css index d3d8f0d627..b0f3b7fea2 100644 --- a/browser/themes/linux/browser.css +++ b/browser/themes/linux/browser.css @@ -1478,6 +1478,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action- color: -moz-dialogtext; } +.tabbrowser-tab[remote] { + text-decoration: underline; +} + #main-window[tabsontop=false]:not([disablechrome]) .tabbrowser-tab[selected=true]:not(:-moz-lwtheme) { background-image: linear-gradient(to top, rgba(0,0,0,.3) 1px, transparent 1px), linear-gradient(@selectedTabHighlight@, @toolbarHighlight@ 32%), diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn index fa0a5c7e2a..e97c1f866b 100644 --- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -16,6 +16,7 @@ browser.jar: #ifdef MOZ_SERVICES_SYNC skin/classic/browser/aboutSyncTabs.css #endif + skin/classic/browser/aboutTabCrashed.css skin/classic/browser/actionicon-tab.png * skin/classic/browser/browser.css skin/classic/browser/click-to-play-warning-stripes.png diff --git a/browser/themes/osx/aboutTabCrashed.css b/browser/themes/osx/aboutTabCrashed.css new file mode 100644 index 0000000000..d1a9ad53c9 --- /dev/null +++ b/browser/themes/osx/aboutTabCrashed.css @@ -0,0 +1,98 @@ +body { + background-color: rgb(241, 244, 248); + margin-top: 2em; + font: message-box; + font-size: 100%; +} + +p { + font-size: .8em; +} + +#error-box { + background: url('chrome://global/skin/icons/information-24.png') no-repeat left 4px; + -moz-padding-start: 30px; +} + +#error-box:-moz-locale-dir(rtl) { + background-position: right 4px; +} + +#main-error-msg { + color: #4b4b4b; + font-weight: bold; +} + + +#button-box { + text-align: center; + width: 75%; + margin: 0 auto; +} + +@media all and (min-width: 300px) { + #error-box { + max-width: 50%; + margin: 0 auto; + background-image: url('chrome://global/skin/icons/information-32.png'); + min-height: 36px; + -moz-padding-start: 38px; + } + + button { + width: auto !important; + min-width: 150px; + } +} + +@media all and (min-width: 780px) { + #error-box { + max-width: 30%; + } +} + +button { + font: message-box; + font-size: 0.6875em; + -moz-appearance: none; + -moz-user-select: none; + width: 100%; + margin: 2px 0; + padding: 2px 6px; + line-height: 1.2; + background-color: hsla(210,30%,95%,.1); + background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1)); + background-clip: padding-box; + border: 1px solid hsla(210,15%,25%,.4); + border-color: hsla(210,15%,25%,.3) hsla(210,15%,25%,.35) hsla(210,15%,25%,.4); + border-radius: 3px; + box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, + 0 0 0 1px hsla(0,0%,100%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.1); + + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; + +} + +button:hover { + background-color: hsla(210,30%,95%,.8); + border-color: hsla(210,15%,25%,.45) hsla(210,15%,25%,.5) hsla(210,15%,25%,.55); + box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, + 0 0 0 1px hsla(0,0%,100%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.1), + 0 0 3px hsla(210,15%,25%,.1); + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; +} + +button:hover:active { + background-color: hsla(210,15%,25%,.2); + box-shadow: 0 1px 1px hsla(210,15%,25%,.2) inset, + 0 0 2px hsla(210,15%,25%,.4) inset; + transition-property: background-color, border-color, box-shadow; + transition-duration: 10ms; + transition-timing-function: linear; +} diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css index e8a5f603b3..bad93672b1 100644 --- a/browser/themes/osx/browser.css +++ b/browser/themes/osx/browser.css @@ -1458,6 +1458,10 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url- min-width: 102px; } +.tabbrowser-tab[remote] { + text-decoration: underline; +} + /* When the tabs are on top and the window is maximized or in full- screen mode, unhide the transparent top border of the tabs so we have a 1px gap between the tabs and the top edge of the screen */ diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn index 84c720d05d..b8e866db4e 100644 --- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -15,6 +15,7 @@ browser.jar: #ifdef MOZ_SERVICES_SYNC skin/classic/browser/aboutSyncTabs.css #endif + skin/classic/browser/aboutTabCrashed.css skin/classic/browser/actionicon-tab.png skin/classic/browser/appmenu-icons.png skin/classic/browser/appmenu-dropmarker.png diff --git a/browser/themes/windows/aboutTabCrashed.css b/browser/themes/windows/aboutTabCrashed.css new file mode 100644 index 0000000000..d1a9ad53c9 --- /dev/null +++ b/browser/themes/windows/aboutTabCrashed.css @@ -0,0 +1,98 @@ +body { + background-color: rgb(241, 244, 248); + margin-top: 2em; + font: message-box; + font-size: 100%; +} + +p { + font-size: .8em; +} + +#error-box { + background: url('chrome://global/skin/icons/information-24.png') no-repeat left 4px; + -moz-padding-start: 30px; +} + +#error-box:-moz-locale-dir(rtl) { + background-position: right 4px; +} + +#main-error-msg { + color: #4b4b4b; + font-weight: bold; +} + + +#button-box { + text-align: center; + width: 75%; + margin: 0 auto; +} + +@media all and (min-width: 300px) { + #error-box { + max-width: 50%; + margin: 0 auto; + background-image: url('chrome://global/skin/icons/information-32.png'); + min-height: 36px; + -moz-padding-start: 38px; + } + + button { + width: auto !important; + min-width: 150px; + } +} + +@media all and (min-width: 780px) { + #error-box { + max-width: 30%; + } +} + +button { + font: message-box; + font-size: 0.6875em; + -moz-appearance: none; + -moz-user-select: none; + width: 100%; + margin: 2px 0; + padding: 2px 6px; + line-height: 1.2; + background-color: hsla(210,30%,95%,.1); + background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1)); + background-clip: padding-box; + border: 1px solid hsla(210,15%,25%,.4); + border-color: hsla(210,15%,25%,.3) hsla(210,15%,25%,.35) hsla(210,15%,25%,.4); + border-radius: 3px; + box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, + 0 0 0 1px hsla(0,0%,100%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.1); + + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; + +} + +button:hover { + background-color: hsla(210,30%,95%,.8); + border-color: hsla(210,15%,25%,.45) hsla(210,15%,25%,.5) hsla(210,15%,25%,.55); + box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, + 0 0 0 1px hsla(0,0%,100%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.1), + 0 0 3px hsla(210,15%,25%,.1); + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; +} + +button:hover:active { + background-color: hsla(210,15%,25%,.2); + box-shadow: 0 1px 1px hsla(210,15%,25%,.2) inset, + 0 0 2px hsla(210,15%,25%,.4) inset; + transition-property: background-color, border-color, box-shadow; + transition-duration: 10ms; + transition-timing-function: linear; +} diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css index 0be8d309a0..a0bdd91636 100644 --- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -1929,6 +1929,10 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action- } } +.tabbrowser-tab[remote] { + text-decoration: underline; +} + .tabbrowser-tab:hover, .tabs-newtab-button:hover { background-image: @toolbarShadowOnTab@, var(--tab-background-hover), diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn index 605c6ff3c9..e1097fe1d3 100644 --- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -15,6 +15,8 @@ browser.jar: #ifdef MOZ_SERVICES_SYNC skin/classic/browser/aboutSyncTabs.css #endif + skin/classic/browser/aboutTabCrashed.css + skin/classic/browser/aboutTabCrashed.css skin/classic/browser/actionicon-tab.png skin/classic/browser/appmenu-icons.png skin/classic/browser/appmenu-dropmarker.png diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 8d51f3289c..28205affaa 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -906,6 +906,18 @@ DOMInterfaces = { 'headerFile': 'HTMLPropertiesCollection.h', }, +'PushEvent': { + 'headerFile': 'ServiceWorkerEvents.h', + 'nativeType': 'mozilla::dom::workers::PushEvent', + 'workers': True +}, + +'PushMessageData': { + 'headerFile': 'ServiceWorkerEvents.h', + 'nativeType': 'mozilla::dom::workers::PushMessageData', + 'workers': True +}, + 'Range': { 'nativeType': 'nsRange', 'binaryNames': { diff --git a/dom/bindings/moz.build b/dom/bindings/moz.build index 25f2ffb835..6342644aaf 100644 --- a/dom/bindings/moz.build +++ b/dom/bindings/moz.build @@ -102,6 +102,10 @@ if CONFIG['MOZ_BUILD_APP'] in ['browser', 'mobile/android', 'xulrunner']: # This is needed for Window.webidl DEFINES['HAVE_SIDEBAR'] = True + +if CONFIG['MOZ_SIMPLEPUSH']: + DEFINES['MOZ_SIMPLEPUSH'] = True + PYTHON_UNIT_TESTS += [ 'mozwebidlcodegen/test/test_mozwebidlcodegen.py', ] diff --git a/dom/cache/test/mochitest/mochitest.ini b/dom/cache/test/mochitest/mochitest.ini index 9c76005179..60df62647a 100644 --- a/dom/cache/test/mochitest/mochitest.ini +++ b/dom/cache/test/mochitest/mochitest.ini @@ -18,6 +18,7 @@ support-files = test_cache_keys.js test_cache_put.js test_cache_requestCache.js + test_cache_delete.js [test_cache.html] [test_cache_add.html] @@ -29,3 +30,4 @@ support-files = [test_cache_keys.html] [test_cache_put.html] [test_cache_requestCache.html] +[test_cache_delete.html] diff --git a/dom/cache/test/mochitest/test_cache_delete.html b/dom/cache/test/mochitest/test_cache_delete.html new file mode 100644 index 0000000000..a9bcf4328f --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_delete.html @@ -0,0 +1,20 @@ + + + +
+
+ + + + +
+