diff options
Diffstat (limited to 'comm/suite/base/content')
43 files changed, 8599 insertions, 0 deletions
diff --git a/comm/suite/base/content/about.js b/comm/suite/base/content/about.js new file mode 100644 index 0000000000..15e3ebad4d --- /dev/null +++ b/comm/suite/base/content/about.js @@ -0,0 +1,46 @@ +/* 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/. */ + +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); + +window.onload = function () { + // get release notes URL and vendor URL from prefs + var releaseNotesURL = Services.urlFormatter.formatURLPref("app.releaseNotesURL"); + if (releaseNotesURL != "about:blank") { + var relnotes = document.getElementById("releaseNotesURL"); + relnotes.href = releaseNotesURL; + } + + var vendorURL = Services.urlFormatter.formatURLPref("app.vendorURL"); + if (vendorURL != "about:blank") { + var vendor = document.getElementById("vendorURL"); + vendor.href = vendorURL; + } + + // append the version of the XUL application (!= XULRunner platform version) + var versionNum = AppConstants.MOZ_APP_VERSION_DISPLAY; + var version = document.getElementById("version"); + version.appendChild(document.createTextNode(versionNum)); + + // append user agent + var ua = navigator.userAgent; + if (ua) { + var uaItem = document.getElementById("userAgent"); + uaItem.appendChild(document.createTextNode(ua)); + uaItem.hidden = false; + } + + // append build identifier + var buildId = Services.appinfo.appBuildID; + if (buildId) { + var buildItem = document.getElementById("buildID"); + buildItem.appendChild(document.createTextNode(buildId)); + buildItem.hidden = false; + } + + // Determine and display current channel. + document.getElementById("currentChannel").textContent = + Services.prefs.getDefaultBranch("").getCharPref("app.update.channel"); +} diff --git a/comm/suite/base/content/about.xhtml b/comm/suite/base/content/about.xhtml new file mode 100644 index 0000000000..7303f040a8 --- /dev/null +++ b/comm/suite/base/content/about.xhtml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" + "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > +%brandDTD; +<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> +%globalDTD; +<!ENTITY % suiteAboutDTD SYSTEM "chrome://communicator/locale/about.dtd" > +%suiteAboutDTD; +]> + +<!-- 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/. --> + +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>About:</title> + <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/> + <script src="chrome://communicator/content/about.js"/> +</head> + +<body dir="&locale.dir;"> + <div id="aboutLogoContainer"> + <a id="vendorURL" href="http://www.seamonkey-project.org/"> + <img src="about:logo" alt="&brandShortName;"/> + <p id="version">&about.version; </p> + </a> + </div> + + <ul id="aboutPageList"> + <li>&channel.description.start;<strong id="currentChannel"/>&channel.description.end;</li> + <li>&about.credits.beforeLink;<a href="about:credits">&about.credits.linkTitle;</a>&about.credits.afterLink;</li> + <li>&about.license.beforeTheLink;<a href="about:license">&about.license.linkTitle;</a>&about.license.afterTheLink;</li> + <li>&about.relnotes.beforeTheLink;<a id="releaseNotesURL" href="">&about.relnotes.linkTitle;</a>&about.relnotes.afterTheLink;</li> + <li>&about.buildconfig.beforeTheLink;<a href="about:buildconfig">&about.buildconfig.linkTitle;</a>&about.buildconfig.afterTheLink;</li> + <li id="userAgent" hidden="true">&about.userAgent;</li> + <li id="buildID" hidden="true">&about.buildIdentifier;</li> + </ul> + +</body> +</html> diff --git a/comm/suite/base/content/aboutLife.xhtml b/comm/suite/base/content/aboutLife.xhtml new file mode 100644 index 0000000000..bd61f40f44 --- /dev/null +++ b/comm/suite/base/content/aboutLife.xhtml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- 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/. --> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" + "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Life, the Universe, and Everything</title> + + <script> + function load() + { + document.getElementById("magic").textContent = Math.sqrt(1764); + } + </script> + + <style> + html { + height: 100%; + width: 100%; + background-color: #B0D8FF; + background-image: radial-gradient(circle closest-side, #72B9FF, #B0D8FF); + } + + body { + margin: 0; + height: 100%; + width: 100%; + display: -moz-box; + -moz-box-pack: center; + -moz-box-align: center; + } + + p:empty { + font-size: 16px; + color: #72B9FF; + text-shadow: none; + transform: rotate(0deg); + } + + p { + font-family: arial; + font-weight: bold; + margin: 0; + transition-property: font-size, color, text-shadow, transform; + transition-duration: 6s; + transition-timing-function: ease; + font-size: 200px; + color: #0303E4; + text-shadow: #333333 3px 3px 3px; + transform: rotate(2160deg); + } + + p:hover { + transition-duration: 1s; + font-size: 300px; + color: #2727E8; + transform: rotate(2520deg); + } + </style> + +</head> + +<body onload="load();"> + <p id="magic"/> +</body> + +</html> diff --git a/comm/suite/base/content/aboutPrivateBrowsing.css b/comm/suite/base/content/aboutPrivateBrowsing.css new file mode 100644 index 0000000000..14f610a5e4 --- /dev/null +++ b/comm/suite/base/content/aboutPrivateBrowsing.css @@ -0,0 +1,12 @@ +/* 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/. */ + +#warningBox:not(.private) .private, +#warningBox:not(.normal) .normal { + display: none; +} + +.title { + background-size: auto; +} diff --git a/comm/suite/base/content/aboutPrivateBrowsing.js b/comm/suite/base/content/aboutPrivateBrowsing.js new file mode 100644 index 0000000000..202bc4dbc8 --- /dev/null +++ b/comm/suite/base/content/aboutPrivateBrowsing.js @@ -0,0 +1,33 @@ +/* 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/. */ + +window.onload = function () { + if (window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsILoadContext) + .usePrivateBrowsing) { + document.getElementById("warningBox").className = "private"; + document.title = document.getElementById("privateTitle").textContent; + } + else { + document.getElementById("warningBox").className = "normal"; + document.title = document.getElementById("normalTitle").textContent; + } + + document.getElementById("learnMoreButton") + .addEventListener("command", function() { + openHelp("private-browsing", + "chrome://communicator/locale/help/suitehelp.rdf"); + }); + + document.getElementById("closeWindowButton") + .addEventListener("command", function() { + window.close(); + }); + + document.getElementById("privateWindowButton") + .addEventListener("command", function() { + openNewPrivateWith(location.href); + }); +} diff --git a/comm/suite/base/content/aboutPrivateBrowsing.xul b/comm/suite/base/content/aboutPrivateBrowsing.xul new file mode 100644 index 0000000000..30361ef38f --- /dev/null +++ b/comm/suite/base/content/aboutPrivateBrowsing.xul @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://communicator/content/aboutPrivateBrowsing.css" type="text/css"?> +<?xml-stylesheet href="chrome://communicator/skin/aboutPrivateBrowsing.css" type="text/css"?> + +<!DOCTYPE window [ + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > + %brandDTD; + <!ENTITY % aboutPrivateBrowsingDTD SYSTEM "chrome://communicator/locale/aboutPrivateBrowsing.dtd" > + %aboutPrivateBrowsingDTD; +]> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + id="warningScreen" + align="center"> + + <script src="chrome://communicator/content/utilityOverlay.js"/> + <script src="chrome://communicator/content/tasksOverlay.js"/> + <script src="chrome://help/content/contextHelp.js"/> + <script src="chrome://communicator/content/aboutPrivateBrowsing.js"/> + + <spacer flex="1"/> + <hbox id="warningBox" align="start"> + <image id="warningBoxIcon"/> + <vbox id="warningOuterBox"> + <vbox id="warningTitle"> + <label id="privateTitle" class="private">&privatebrowsingpage.title.private;</label> + <label id="normalTitle" class="normal">&privatebrowsingpage.title.normal;</label> + </vbox> + <vbox id="warningStatus"> + <label class="private">&privatebrowsingpage.status.private;</label> + <label class="normal">&privatebrowsingpage.status.normal;</label> + </vbox> + <vbox id="warningInnerBox" align="start"> + <description id="warningText">&privatebrowsingpage.common.description;</description> + <hbox id="trackWarnBox" class="private"> + <image id="trackWarnIcon"/> + <description flex="1">&privatebrowsingpage.track.warn;</description> + </hbox> + <button id="learnMoreButton" + label="&privatebrowsingpage.learnmore.label;" + accesskey="&privatebrowsingpage.learnmore.accesskey;"/> + <description class="private">&privatebrowsingpage.close.info;</description> + <button id="closeWindowButton" + class="private" + label="&privatebrowsingpage.close.label;" + accesskey="&privatebrowsingpage.close.accesskey;"/> + <description class="normal">&privatebrowsingpage.start.info;</description> + <button id="privateWindowButton" + class="normal" + label="&privatebrowsingpage.private.label;" + accesskey="&privatebrowsingpage.private.accesskey;"/> + </vbox> + </vbox> + </hbox> + <spacer flex="2"/> +</window> diff --git a/comm/suite/base/content/askViewZoom.js b/comm/suite/base/content/askViewZoom.js new file mode 100644 index 0000000000..6a3f903d6b --- /dev/null +++ b/comm/suite/base/content/askViewZoom.js @@ -0,0 +1,43 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + + * 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/. */ + +var dialog; +var args; + +function onLoad() { + args = window.arguments[0]; + args.zoomOK = false; + + dialog = {}; + dialog.OKButton = document.documentElement.getButton("accept"); + + dialog.input = document.getElementById("zoomValue"); + dialog.input.value = args.value; + dialog.input.select(); + dialog.input.focus(); + + doEnabling(); +} + +function onAccept() { + var zoom = parseFloat(dialog.input.value); + if (!isNaN(zoom) && zoom >= args.zoomMin && zoom <= args.zoomMax) { + args.value = zoom; + args.zoomOK = true; + } + return args.zoomOK; +} + +function doEnabling() { + var enable = false; + if (dialog.input.value) { + var zoom = parseFloat(dialog.input.value); + if (!isNaN(zoom) && zoom >= args.zoomMin && zoom <= args.zoomMax) + enable = true; + } + + dialog.OKButton.disabled = !enable; +} diff --git a/comm/suite/base/content/askViewZoom.xul b/comm/suite/base/content/askViewZoom.xul new file mode 100644 index 0000000000..5f38270d15 --- /dev/null +++ b/comm/suite/base/content/askViewZoom.xul @@ -0,0 +1,27 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://communicator/locale/askViewZoom.dtd"> + +<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + id="askViewZoom" + title="&askViewZoom.title;" + ondialogaccept="return onAccept();" + onload="onLoad();"> + + <script src="chrome://communicator/content/askViewZoom.js"/> + + <hbox> + <label value="&selectZoom.label;" control="zoomValue"/> + </hbox> + + <hbox> + <textbox id="zoomValue" oninput="doEnabling();"/> + </hbox> + +</dialog> diff --git a/comm/suite/base/content/blockedSite.js b/comm/suite/base/content/blockedSite.js new file mode 100644 index 0000000000..4a3cf0d69e --- /dev/null +++ b/comm/suite/base/content/blockedSite.js @@ -0,0 +1,126 @@ +/* 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/. */ + +// Error url MUST be formatted like this: +// about:blocked?e=error_code&u=url + +// Note that this file uses document.documentURI to get +// the URL (with the format from above). This is because +// document.location.href gets the current URI off the docshell, +// which is the URL displayed in the location bar, i.e. +// the URI that the user attempted to load. + +// initializing the page in this way, window.onload won't work here + +initPage(); + +function getErrorCode() +{ + var url = document.documentURI; + var error = url.indexOf("e="); + var duffUrl = url.indexOf("&u="); + return url.slice(error + 2, duffUrl); +} + +function getURL() +{ + var url = document.documentURI; + var match = url.match(/&u=([^&]+)&/); + + // match == null if not found; if so, return an empty string + // instead of what would turn out to be portions of the URI + if (!match) + return ""; + + url = decodeURIComponent(match[1]); + + // If this is a view-source page, then get then real URI of the page + if (url.startsWith("view-source:")) + url = url.slice(12); + return url; +} + + /** + * Check whether this warning page should be overridable or whether + * the "ignore warning" button should be hidden. + */ + function getOverride() + { + var url = document.documentURI; + return /&o=1&/.test(url); + } + +/** + * Attempt to get the hostname via document.location. Fail back + * to getURL so that we always return something meaningful. + */ +function getHostString() +{ + try { + return document.location.hostname; + } catch (e) { + return getURL(); + } +} + +function deleteElement(element) { + var el = document.getElementById(element); + if (el) + el.remove(); +} + +function initPage() +{ + // Handoff to the appropriate initializer, based on error code + var error = ""; + switch (getErrorCode()) { + case "malwareBlocked": + error = "malware"; + break; + case "deceptiveBlocked": + error = "phishing"; + break; + case "unwantedBlocked": + error = "unwanted"; + break; + case "harmfulBlocked": + error = "harmful"; + break; + default: + return; + } + + if (error != "malware") { + deleteElement("errorTitleText_malware"); + deleteElement("errorShortDescText_malware"); + deleteElement("errorLongDescText_malware"); + } + + if (error != "phishing") { + deleteElement("errorTitleText_phishing"); + deleteElement("errorShortDescText_phishing"); + deleteElement("errorLongDescText_phishing"); + } + + if (error != "unwanted") { + deleteElement("errorTitleText_unwanted"); + deleteElement("errorShortDescText_unwanted"); + deleteElement("errorLongDescText_unwanted"); + } + + if (error != "harmful") { + deleteElement("errorTitleText_harmful"); + deleteElement("errorShortDescText_harmful"); + deleteElement("errorLongDescText_harmful"); + } + + // Set sitename + document.getElementById(error + "_sitename").textContent = getHostString(); + document.title = document.getElementById("errorTitleText_" + error) + .innerHTML; + + if (!getOverride()) + deleteElement("ignoreWarningButton"); +} + diff --git a/comm/suite/base/content/blockedSite.xhtml b/comm/suite/base/content/blockedSite.xhtml new file mode 100644 index 0000000000..40cce3eb49 --- /dev/null +++ b/comm/suite/base/content/blockedSite.xhtml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!DOCTYPE html [ + <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> + %htmlDTD; + <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> + %globalDTD; + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > + %brandDTD; + <!ENTITY % blockedSiteDTD SYSTEM "chrome://communicator/locale/safeBrowsing.dtd"> + %blockedSiteDTD; +]> + +<!-- 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/. --> + +<html xmlns="http://www.w3.org/1999/xhtml" class="blacklist"> + <head> + <link rel="stylesheet" href="chrome://communicator/content/certError.css" type="text/css" media="all" /> + <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" /> + <link rel="stylesheet" href="chrome://communicator/skin/blockedSite.css" type="text/css" media="all" /> + <link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/blacklist_favicon.png"/> + </head> + + <body dir="&locale.dir;"> + <div id="errorPageContainer"> + + <!-- Error Title --> + <div id="errorTitle"> + <h1 id="errorTitleText_phishing">&safeb.blocked.phishingPage.title2;</h1> + <h1 id="errorTitleText_malware">&safeb.blocked.malwarePage.title;</h1> + <h1 id="errorTitleText_unwanted">&safeb.blocked.unwantedPage.title;</h1> + <h1 id="errorTitleText_harmful">&safeb.blocked.harmfulPage.title;</h1> + </div> + + <div id="errorLongContent"> + + <!-- Short Description --> + <div id="errorShortDesc"> + <p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc2;</p> + <p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p> + <p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p> + <p id="errorShortDescText_harmful">&safeb.blocked.harmfulPage.shortDesc;</p> + </div> + + <!-- Long Description --> + <div id="errorLongDesc"> + <p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc2;</p> + <p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p> + <p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p> + <p id="errorLongDescText_harmful">&safeb.blocked.harmfulPage.longDesc;</p> + </div> + + <!-- Action buttons --> + <div id="buttons"> + <!-- Commands handled in utilityOverlay.js --> + <span id="getMeOutOfHereButton" + class="button" + label="&safeb.palm.accept.label;"/> + <span id="reportButton" + class="button" + label="&safeb.palm.reportPage.label;"/> + <span id="ignoreWarningButton" + class="button" + label="&safeb.palm.decline.label;"/> + </div> + </div> + </div> + <!-- + - Note: It is important to run the script this way, instead of using + - a window.onload function. This is because error pages are loaded as + - LOAD_BACKGROUND, which means that onload handlers will not be executed. + --> + <script src="chrome://communicator/content/blockedSite.js"/> + </body> +</html> diff --git a/comm/suite/base/content/certError.css b/comm/suite/base/content/certError.css new file mode 100644 index 0000000000..93dc0a4cf0 --- /dev/null +++ b/comm/suite/base/content/certError.css @@ -0,0 +1,7 @@ +/* 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/. */ + +.button { + -moz-binding: url("chrome://communicator/content/certError.xml#button"); +} diff --git a/comm/suite/base/content/certError.js b/comm/suite/base/content/certError.js new file mode 100644 index 0000000000..96a89b0685 --- /dev/null +++ b/comm/suite/base/content/certError.js @@ -0,0 +1,181 @@ +/* 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/. */ + +// The following parameters are parsed from the error URL: +// e - the error code +// s - custom CSS class to allow alternate styling/favicons +// d - error description + +// Note that this file uses document.documentURI to get +// the URL (with the format from above). This is because +// document.location.href gets the current URI off the docshell, +// which is the URL displayed in the location bar, i.e. +// the URI that the user attempted to load. + +// setting up the event listeners and initializing the page +// in this way given that window.onload won't work here + +document.getElementById("technicalContentHeading") + .addEventListener("click", function() { toggle("technicalContent"); }); + +document.getElementById("expertContentHeading") + .addEventListener("click", function() { toggle("expertContent"); }); + +let gSearchParams; + +// Set to true on init if the error code is nssBadCert. +let gIsCertError; + +// Set to true on init if a neterror. +let gIsNetError; + +initPage(); + +function retryThis(buttonEl) { + // Note: The application may wish to handle switching off "offline mode" + // before this event handler runs, but using a capturing event handler. + + // Session history has the URL of the page that failed + // to load, not the one of the error page. So, just call + // reload(), which will also repost POST data correctly. + try { + location.reload(); + } catch (e) { + // We probably tried to reload a URI that caused an exception to + // occur; e.g. a nonexistent file. + } + + buttonEl.disabled = true; +} + +function initPage() { + gSearchParams = new URLSearchParams(document.documentURI.split("?")[1]); + + let err = gSearchParams.get("e"); + // List of neterror pages which have no error code and + // could have an illustration instead. + let illustratedErrors = [ + "malformedURI", "dnsNotFound", "connectionFailure", "netInterrupt", + "netTimeout", "netReset", "netOffline", + ]; + if (illustratedErrors.includes(err)) { + document.body.classList.add("illustrated", err); + } + + gIsCertError = (err == "nssBadCert"); + gIsNetError = (document.documentURI.startsWith("about:neterror")); + + let pageTitle = document.getElementById("ept_" + err); + if (pageTitle) { + document.title = pageTitle.textContent; + } + + // If it's an unknown error or there's no title or description defined, + // get the generic message. + let errTitle = document.getElementById("et_" + err); + let errDesc = document.getElementById("ed_" + err); + if (!errTitle || !errDesc) { + errTitle = document.getElementById("et_generic"); + errDesc = document.getElementById("ed_generic"); + } + + let title = document.getElementById("errorTitleText"); + if (title) { + title.innerHTML = errTitle.innerHTML; + } + + let sd = document.getElementById("errorShortDescText"); + if (sd) { + if (gIsCertError) { + sd.innerHTML = errDesc.innerHTML; + } else if (!err || err == "unknownProtocolFound") { + sd.remove(); + } + } + + let xd = document.getElementById("errorShortDescExtra"); + if (xd) { + let errExtra = document.getElementById("ex_" + err); + if (gIsCertError && errExtra) { + xd.innerHTML = errExtra.innerHTML; + } else { + xd.remove(); + } + } + + let ld = document.getElementById("errorLongDesc"); + if (ld && !gIsCertError) { + ld.innerHTML = errDesc.innerHTML; + } + + // Remove undisplayed errors to avoid bug 39098. + let errContainer = document.getElementById("errorContainer"); + errContainer.remove(); + + if (gIsCertError || err == "inadequateSecurityError") { + for (let host of document.querySelectorAll(".hostname")) { + host.textContent = location.host; + } + } + + if (gIsCertError || err == "sslv3Used") { + document.body.classList.add("certerror"); + } + + if (gIsCertError || err == "remoteXUL" || err == "cspBlocked" || + err == "inadequateSecurityError") { + // Remove the "Try again" button for certificate errors, remote XUL errors, + // CSP violations (Bug 553180) and HTTP/2 inadequate security, + // given that it is useless. + document.getElementById("netErrorButtonContainer").style.display = "none"; + } + + let className = gSearchParams.get("s"); + if (className && className != "expertBadCert") { + // Associate a CSS class with the root of the page, if one was passed in, + // to allow custom styling. + // Not "expertBadCert" though, don't want to deal with the favicon + document.documentElement.classList.add(className); + } + + if (className == "expertBadCert") { + toggle("technicalContent"); + toggle("expertContent"); + } + + // Disallow overrides if this is a Strict-Transport-Security + // host and the cert is bad (STS Spec section 7.3); + // or if the cert error is in a frame (bug 633691). + if (className == "badStsCert" || window != top || !gIsCertError) { + let expertContent = document.getElementById("expertContent"); + expertContent.remove(); + } + if (className == "badStsCert") { + document.getElementById("badStsCertExplanation").removeAttribute("hidden"); + } + + // For neterrors set a suitable class. + if (gIsNetError) { + document.body.classList.add("neterror"); + } + + // For neterrors and null error codes do not show the What Should I Do and + // Technical Details sections. + if (gIsNetError || !err) { + let whatShould = document.getElementById("whatShouldIDoContent"); + whatShould.remove(); + let technicalContent = document.getElementById("technicalContent"); + technicalContent.remove(); + } + var event = new CustomEvent("AboutNetAndCertErrorLoad", {bubbles: true}); + document.dispatchEvent(event); +} + +function toggle(id) { + var el = document.getElementById(id); + if (el.hasAttribute("collapsed")) + el.removeAttribute("collapsed"); + else + el.setAttribute("collapsed", true); +} diff --git a/comm/suite/base/content/certError.xhtml b/comm/suite/base/content/certError.xhtml new file mode 100644 index 0000000000..e63f5d1844 --- /dev/null +++ b/comm/suite/base/content/certError.xhtml @@ -0,0 +1,164 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!DOCTYPE html [ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > + %brandDTD; + <!ENTITY % htmlDTD + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "DTD/xhtml1-strict.dtd"> + %htmlDTD; + <!ENTITY % netErrorDTD SYSTEM "chrome://global/locale/netError.dtd"> + %netErrorDTD; + <!ENTITY % globalDTD + SYSTEM "chrome://global/locale/global.dtd"> + %globalDTD; + <!ENTITY % certerrorDTD + SYSTEM "chrome://communicator/locale/certError.dtd"> + %certerrorDTD; +]> + +<!-- 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/. --> +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>&loadError.label;</title> + <link rel="stylesheet" href="chrome://communicator/content/certError.css" type="text/css" media="all" /> + <link rel="stylesheet" href="chrome://communicator/skin/certError.css" type="text/css" media="all" /> + <link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/warning-16.png"/> + </head> + + <body dir="&locale.dir;"> + + <!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) --> + <div id="errorContainer" style="display: none;"> + <div id="errorPageTitlesContainer"> + <span id="ept_nssBadCert">&certerror.pagetitle;</span> + <span id="ept_dnsNotFound">&dnsNotFound.pageTitle;</span> + <span id="ept_malformedURI">&malformedURI.pageTitle;</span> + </div> + <div id="errorTitlesContainer"> + <h1 id="et_generic">&generic.title;</h1> + <h1 id="et_dnsNotFound">&dnsNotFound.title;</h1> + <h1 id="et_fileNotFound">&fileNotFound.title;</h1> + <h1 id="et_fileAccessDenied">&fileAccessDenied.title;</h1> + <h1 id="et_malformedURI">&malformedURI.title;</h1> + <h1 id="et_unknownProtocolFound">&unknownProtocolFound.title;</h1> + <h1 id="et_connectionFailure">&connectionFailure.title;</h1> + <h1 id="et_netTimeout">&netTimeout.title;</h1> + <h1 id="et_redirectLoop">&redirectLoop.title;</h1> + <h1 id="et_unknownSocketType">&unknownSocketType.title;</h1> + <h1 id="et_netReset">&netReset.title;</h1> + <h1 id="et_notCached">¬Cached.title;</h1> + <h1 id="et_netOffline">&netOffline.title;</h1> + <h1 id="et_netInterrupt">&netInterrupt.title;</h1> + <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1> + <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1> + <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1> + <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1> + <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1> + <h1 id="et_nssFailure2">&nssFailure2.title;</h1> + <h1 id="et_nssBadCert">&certerror.longpagetitle;</h1> + <h1 id="et_cspBlocked">&cspBlocked.title;</h1> + <h1 id="et_remoteXUL">&remoteXUL.title;</h1> + <h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1> + <h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1> + </div> + <div id="errorDescriptionsContainer"> + <div id="ed_generic">&generic.longDesc;</div> + <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div> + <div id="ed_fileNotFound">&fileNotFound.longDesc;</div> + <div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div> + <div id="ed_malformedURI">&malformedURI.longDesc;</div> + <div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div> + <div id="ed_connectionFailure">&connectionFailure.longDesc;</div> + <div id="ed_netTimeout">&netTimeout.longDesc;</div> + <div id="ed_redirectLoop">&redirectLoop.longDesc;</div> + <div id="ed_unknownSocketType">&unknownSocketType.longDesc;</div> + <div id="ed_netReset">&netReset.longDesc;</div> + <div id="ed_notCached">¬Cached.longDesc;</div> + <div id="ed_netOffline">&netOffline.longDesc2;</div> + <div id="ed_netInterrupt">&netInterrupt.longDesc;</div> + <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div> + <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div> + <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div> + <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div> + <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div> + <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div> + <div id="ed_nssBadCert">&certerror.introPara1a;</div> + <div id="ex_nssBadCert">&certerror.introPara2;</div> + <div id="ed_cspBlocked">&cspBlocked.longDesc;</div> + <div id="ed_remoteXUL">&remoteXUL.longDesc;</div> + <div id="ed_corruptedContentErrorv2">&corruptedContentErrorv2.longDesc;</div> + <div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div> + </div> + </div> + + <!-- PAGE CONTAINER (for styling purposes only) --> + <div id="errorPageContainer"> + + <!-- Error Title --> + <div id="errorTitle"> + <h1 id="errorTitleText"/> + </div> + + <!-- LONG CONTENT (the section most likely to require scrolling) --> + <div id="errorLongContent"> + + <!-- Short Description --> + <div id="errorShortDesc"> + <p id="errorShortDescText"/> + <p id="errorShortDescExtra"/> + </div> + + <!-- Long Description (Note: See netError.dtd for used XHTML tags) --> + <div id="errorLongDesc"/> + + <div id="whatShouldIDoContent"> + <h2>&certerror.whatShouldIDo.heading;</h2> + <div id="whatShouldIDoContentText"> + <p>&certerror.whatShouldIDo.content;</p> + <p id="badStsCertExplanation" + hidden="true">&certerror.whatShouldIDo.badStsCertExplanation;</p> + <span id="getMeOutOfHereButton" + class="button" + label="&certerror.getMeOutOfHere.label;"/> + </div> + </div> + + <!-- The following sections can be unhidden by default by setting the + "browser.xul.error_pages.expert_bad_cert" pref to true --> + <div id="technicalContent" collapsed="true"> + <h2 id="technicalContentHeading">&certerror.technical.heading;</h2> + <p id="technicalContentText"/> + </div> + + <div id="expertContent" collapsed="true"> + <h2 id="expertContentHeading">&certerror.expert.heading;</h2> + <div> + <p>&certerror.expert.content;</p> + <p>&certerror.expert.contentPara2;</p> + <span id="exceptionDialogButton" + class="button" + label="&certerror.addException.label;"/> + </div> + </div> + + <div id="netErrorButtonContainer" class="button-container"> + <button id="errorTryAgain" + class="primary" + autocomplete="off" + onclick="retryThis(this);">&retry.label;</button> + </div> + </div> + </div> + + <!-- + - Note: It is important to run the script this way, instead of using + - a window.onload function. This is because error pages are loaded as + - LOAD_BACKGROUND, which means that onload handlers will not be executed. + --> + <script src="chrome://communicator/content/certError.js"/> + + </body> +</html> diff --git a/comm/suite/base/content/certError.xml b/comm/suite/base/content/certError.xml new file mode 100644 index 0000000000..c4fbe12f01 --- /dev/null +++ b/comm/suite/base/content/certError.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<bindings id="certErrorBindings" + xmlns="http://www.mozilla.org/xbl" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:xbl="http://www.mozilla.org/xbl"> + + <binding id="button" bindToUntrustedContent="true"> + <content> + <xul:button xbl:inherits="anonid=id,label"/> + </content> + </binding> + +</bindings> diff --git a/comm/suite/base/content/charsetOverlay.xul b/comm/suite/base/content/charsetOverlay.xul new file mode 100644 index 0000000000..6f318a1d6b --- /dev/null +++ b/comm/suite/base/content/charsetOverlay.xul @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<!-- 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/. --> + +<!DOCTYPE overlay SYSTEM "chrome://global/locale/charsetMenu.dtd"> +<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script> + ChromeUtils.import("resource://gre/modules/CharsetMenu.jsm"); + + function UpdateCharsetMenu(aCharset, aNode) + { + var bundle = document.getElementById("charsetBundle"); + CharsetMenu.update(aNode, bundle.getString(aCharset.toLowerCase())); + } + </script> + + <menu id="charsetMenu" + label="&charsetMenu2.label;" + accesskey="&charsetMenu2.accesskey;"> + <menupopup id="charsetPopup" + onpopupshowing="CharsetMenu.build(this, true, this.getAttribute('detectors') != 'false');"/> + </menu> +</overlay> diff --git a/comm/suite/base/content/communicator.css b/comm/suite/base/content/communicator.css new file mode 100644 index 0000000000..6ec458f0df --- /dev/null +++ b/comm/suite/base/content/communicator.css @@ -0,0 +1,329 @@ +/* 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/. */ + +@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); + +/* ::::: print preview toolbar ::::: */ + +toolbar[printpreview="true"] { + -moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar"); +} + +/* ::::: autocomplete textbox ::::: */ + +textbox[type="autocomplete"] { + -moz-binding: url("chrome://global/content/autocomplete.xml#autocomplete"); +} + +panel[type="autocomplete"] { + -moz-binding: url("chrome://global/content/autocomplete.xml#autocomplete-result-popup"); +} + +.autocomplete-history-popup { + -moz-binding: url("chrome://global/content/autocomplete.xml#autocomplete-history-popup"); +} + +.autocomplete-treebody { + -moz-binding: url("chrome://global/content/autocomplete.xml#autocomplete-treebody"); +} + +panel[type="autocomplete-richlistbox"] { + -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup"); +} + +.autocomplete-richlistbox { + -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistbox"); + -moz-user-focus: ignore; +} + +.autocomplete-richlistbox > scrollbox { + overflow-x: hidden !important; +} + +.autocomplete-history-dropmarker { + -moz-binding: url("chrome://global/content/autocomplete.xml#history-dropmarker"); +} + +.autocomplete-richlistitem { + -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem"); + -moz-box-orient: vertical; + overflow: -moz-hidden-unscrollable; +} + +/* ::::: notification box ::::: */ + +.browser-notificationbox { + -moz-binding: url("chrome://communicator/content/bindings/notification.xml#browser-notificationbox"); +} + +.browser-notificationbox[popupnotification="true"] { + -moz-binding: url("chrome://communicator/content/bindings/notification.xml#popup-notification"); +} + +notification[value="addon-install-started"] { + -moz-binding: url("chrome://communicator/content/bindings/notification.xml#addon-progress-notification"); +} + +/* ::::: toolbaritem ::::: */ +toolbaritem { + -moz-binding: url("chrome://global/content/bindings/general.xml#basecontrol"); +} + +/* With the move to the new toolkit, SeaMonkey needs to overwrite certain bindings + * if it wants to keep its distinctive likeness. The now hidden new toolkit bindings + * will stay accessible via a set xpfe="false" attribute, though, where necessary. + */ + +/******* toolkit access layer *******/ +/* These rules reintroduce the toolkit bindings overwritten later below */ +toolbox[xpfe="false"] { + -moz-binding: url("chrome://communicator/content/bindings/toolbar.xml#toolbox"); +} + +toolbox[xpfe="false"] > toolbar, +toolbar[xpfe="false"][type="menubar"], +toolbar[xpfe="false"] { + -moz-binding: url("chrome://communicator/content/bindings/toolbar.xml#toolbar"); +} + +menubar[xpfe="false"], +toolbar > toolbaritem > menubar, +toolbar > menubar { + -moz-binding: url("chrome://communicator/content/bindings/toolbar.xml#menubar"); +} + +toolbar > toolbarpaletteitem > toolbaritem > menubar { + -moz-binding: url("chrome://communicator/content/bindings/toolbar.xml#menubar") !important; +} + +.menubar-items { + -moz-box-orient: vertical; /* for flex hack */ +} + +.menubar-items > menubar { + -moz-box-flex: 1; /* make menu items expand to fill toolbar height */ +} + +prefwindow, +prefwindow:root, +prefpane { + -moz-box-orient: vertical; +} + +prefwindow[type="child"] > .paneDeckContainer { + overflow: -moz-hidden-unscrollable; +} + +prefwindow[type="child"] > prefpane > .content-box { + -moz-box-flex: 1; + overflow: -moz-hidden-unscrollable; +} + +preferences { + -moz-binding: url("chrome://communicator/content/bindings/preferences.xml#preferences"); + visibility: collapse; +} + +preference { + -moz-binding: url("chrome://communicator/content/bindings/preferences.xml#preference"); + visibility: collapse; +} + +radio[pane] { + -moz-binding: url("chrome://communicator/content/bindings/preferences.xml#panebutton") !important; + -moz-box-orient: vertical; + -moz-box-align: center; +} + +prefwindow[chromehidden~="toolbar"] .chromeclass-toolbar { + display: none; +} + +prefwindow[xpfe="false"] { + -moz-binding: url("chrome://communicator/content/bindings/preferences.xml#prefwindow"); +} + +prefpane[xpfe="false"] { + -moz-binding: url("chrome://communicator/content/bindings/preferences.xml#prefpane"); +} + +findbar[xpfe="false"] { + -moz-binding: url("chrome://global/content/bindings/findbar.xml#findbar"); +} + +prefwindow[xpfe="false"] > .paneDeckContainer, +prefpane[xpfe="false"] > .content-box { + overflow: hidden; +} + +/******* SeaMonkey XPFE *******/ +/* These bindings reflect SeaMonkey XPFE, modulo new toolkit features. */ +toolbox { + -moz-binding: url("chrome://communicator/content/bindings/toolbar-xpfe.xml#grippytoolbox"); +} + +toolbar { + -moz-binding: url("chrome://communicator/content/bindings/toolbar-xpfe.xml#grippytoolbar"); +} + +toolbar[type="menubar"] { + -moz-binding: url("chrome://communicator/content/bindings/toolbar-xpfe.xml#grippytoolbar-menubar"); +} + +toolbargrippy { + -moz-binding: url("chrome://communicator/content/bindings/toolbar-xpfe.xml#toolbargrippy"); +} + +menubar { + -moz-binding: url("chrome://communicator/content/bindings/toolbar-xpfe.xml#grippymenubar"); +} + +prefwindow { + -moz-binding: url("chrome://communicator/content/bindings/prefwindow.xml#prefwindow"); +} + +prefpane { + -moz-binding: url("chrome://communicator/content/bindings/prefwindow.xml#prefpane"); +} + +findbar { + -moz-binding: url("chrome://communicator/content/bindings/findbar.xml#findbar"); +} + +prefwindow > .paneDeckContainer, +prefpane > .content-box { + overflow: visible; +} + +prefwindow[overflow="auto"] > .paneDeckContainer, +prefwindow[overflow="auto"] prefpane > .content-box { + overflow: auto; +} + +dialogheader { + -moz-binding: url("chrome://communicator/content/bindings/generalBindings.xml#dialogheader"); +} + +%ifndef MOZ_WIDGET_GTK +statusbar:not([nowindowdrag="true"]) { + -moz-window-dragging: drag; +} +%endif + +%ifdef XP_MACOSX +.statusbar-resizerpanel { + display: none; +} +%else +window[sizemode="maximized"] statusbarpanel.statusbar-resizerpanel { + visibility: collapse; +} +%endif + +statusbar { + -moz-binding: url("chrome://communicator/content/bindings/generalBindings.xml#statusbar"); +%ifdef XP_MACOSX + padding-right: 14px; +%endif +} + +statusbarpanel { + -moz-binding: url("chrome://communicator/content/bindings/generalBindings.xml#statusbarpanel"); +} + +.statusbarpanel-iconic { + -moz-binding: url("chrome://communicator/content/bindings/general.xml#statusbarpanel-iconic"); +} + +.statusbarpanel-iconic-text { + -moz-binding: url("chrome://communicator/content/bindings/general.xml#statusbarpanel-iconic-text"); +} + +.statusbarpanel-backgroundbox { + -moz-binding: url("chrome://communicator/content/bindings/general.xml#statusbarpanel-backgroundbox"); +} + +textbox[enablehistory="true"] > .autocomplete-history-dropmarker { + display: -moz-box; +} + +/******* lightweight themes *******/ +:root:-moz-lwtheme { + color: var(--lwt-text-color) !important +} + +/** + * [customization-lwtheme] may or may not be used yet; we leave it in + * in case it moves to toolkit in the future. + */ +:root:-moz-lwtheme:not([customization-lwtheme]) { + background-color: var(--lwt-accent-color) !important; + background-image: var(--lwt-header-image) !important; +} + +window[lwtheme="true"] { + background-repeat: no-repeat; + background-position: top right; + background-image: var(--lwt-header-image); +} + +:root[lwthemefooter="true"] #status-bar:-moz-lwtheme { + background-repeat: no-repeat; + background-position: bottom left; + background-color: var(--lwt-accent-color); + background-image: var(--lwt-header-image); +} + +/******* sync *******/ +#sync-notifications { + -moz-binding: url("chrome://communicator/content/sync/syncNotification.xml#notificationbox"); + overflow-y: visible !important; +} + +#sync-notifications > notification { + -moz-binding: url("chrome://communicator/content/sync/syncNotification.xml#notification"); +} + +/******* autohide toolbars *******/ + +toolbar[type="menubar"][autohide="true"] +{ + -moz-binding: url("chrome://communicator/content/bindings/toolbar.xml#toolbar-menubar-autohide"); + overflow: hidden; +} + +toolbar[type="menubar"][autohide="true"][inactive="true"] +{ + min-height: 0px !important; + height: 0px !important; + -moz-appearance: none !important; + border-style: none !important; +} + +/******* datepicker *******/ +datepicker { + -moz-binding: url("chrome://communicator/content/bindings/datetimepicker.xml#datepicker"); +} + +datepicker[type="popup"] { + -moz-binding: url("chrome://communicator/content/bindings/datetimepicker.xml#datepicker-popup"); +} + +datepicker[type="grid"] { + -moz-binding: url("chrome://communicator/content/bindings/datetimepicker.xml#datepicker-grid"); +} + +/******* numberbox *******/ +textbox[type="number"] { + -moz-binding: url("chrome://communicator/content/bindings/numberbox.xml#numberbox"); +} + +/******* spinbuttons *******/ +spinbuttons { + -moz-binding: url("chrome://communicator/content/bindings/spinbuttons.xml#spinbuttons"); +} + +.spinbuttons-button { + -moz-user-focus: ignore; +} diff --git a/comm/suite/base/content/contentAreaClick.js b/comm/suite/base/content/contentAreaClick.js new file mode 100644 index 0000000000..b4c83e991e --- /dev/null +++ b/comm/suite/base/content/contentAreaClick.js @@ -0,0 +1,260 @@ +// /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * - [ Dependencies ] --------------------------------------------------------- + * utilityOverlay.js: + * - gatherTextUnder + */ + + function hrefAndLinkNodeForClickEvent(event) + { + var href = ""; + var isKeyCommand = (event.type == "command"); + var linkNode = isKeyCommand ? document.commandDispatcher.focusedElement + : event.originalTarget; + + while (linkNode instanceof Element) { + if (linkNode instanceof HTMLAnchorElement || + linkNode instanceof HTMLAreaElement || + linkNode instanceof HTMLLinkElement) { + href = linkNode.href; + if (href) + break; + } + // Try MathML href + else if (linkNode.namespaceURI == "http://www.w3.org/1998/Math/MathML" && + linkNode.hasAttribute("href")) { + href = linkNode.getAttribute("href"); + href = makeURLAbsolute(linkNode.baseURI, href); + break; + } + // Try simple XLink + else if (linkNode.hasAttributeNS("http://www.w3.org/1999/xlink", "href")) { + href = linkNode.getAttributeNS("http://www.w3.org/1999/xlink", "href"); + href = makeURLAbsolute(linkNode.baseURI, href); + break; + } + linkNode = linkNode.parentNode; + } + + return href ? {href: href, linkNode: linkNode} : null; + } + + // Called whenever the user clicks in the content area, + // except when left-clicking on links (special case) + // should always return true for click to go through + function contentAreaClick(event) + { + if (!event.isTrusted || event.defaultPrevented) { + return true; + } + + var isKeyCommand = (event.type == "command"); + var ceParams = hrefAndLinkNodeForClickEvent(event); + if (ceParams) { + var href = ceParams.href; + if (isKeyCommand) { + var doc = event.target.ownerDocument; + urlSecurityCheck(href, doc.nodePrincipal); + openLinkIn(href, event && event.altKey ? "tabshifted" : "tab", + { charset: doc.characterSet, + referrerURI: doc.documentURIObject }); + event.stopPropagation(); + } + else { + // if in mailnews block the link left click if we determine + // that this URL is phishy (i.e. a potential email scam) + if ("gMessengerBundle" in this && event.button < 2 && + isPhishingURL(ceParams.linkNode, false, href)) + return false; + handleLinkClick(event, href, ceParams.linkNode); + + // 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 { + PlacesUIUtils.markPageAsFollowedLink(href); + } catch (ex) { /* Skip invalid URIs. */ } + } + return true; + } + + if (!isKeyCommand && event.button == 1 && + Services.prefs.getBoolPref("middlemouse.contentLoadURL") && + !Services.prefs.getBoolPref("general.autoScroll")) { + middleMousePaste(event); + } + + return true; + } + +function handleLinkClick(event, href, linkNode) { + if (event.button == 2) // right click + return false; + + var where = whereToOpenLink(event); + if (where == "current") + return false; + + var doc = event.target.ownerDocument; + + if (where == "save") { + saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, false, + true, doc.documentURIObject, doc); + event.preventDefault(); + return true; + } + + var referrerURI = doc.documentURIObject; + // if the mixedContentChannel is present and the referring URI passes + // a same origin check with the target URI, we can preserve the users + // decision of disabling MCB on a page for it's child tabs. + var persistAllowMixedContentInChildTab = false; + + if (where == "tab" && getBrowser().docShell.mixedContentChannel) { + const sm = Services.scriptSecurityManager; + try { + var targetURI = makeURI(href); + sm.checkSameOriginURI(referrerURI, targetURI, false); + persistAllowMixedContentInChildTab = true; + } + catch (e) { } + } + + urlSecurityCheck(href, doc.nodePrincipal); + let params = { + charset: doc.characterSet, + private: gPrivate ? true : false, + allowMixedContent: persistAllowMixedContentInChildTab, + referrerURI: referrerURI, + noReferrer: BrowserUtils.linkHasNoReferrer(linkNode), + originPrincipal: doc.nodePrincipal, + triggeringPrincipal: doc.nodePrincipal, + }; + + // The new tab/window must use the same userContextId + if (doc.nodePrincipal.originAttributes.userContextId) { + params.userContextId = doc.nodePrincipal.originAttributes.userContextId; + } + + openLinkIn(href, where, params); + event.preventDefault(); + return true; +} + + function middleMousePaste(event) { + + let clipboard = readFromClipboard(); + + if (!clipboard) + return; + + // Strip embedded newlines and surrounding whitespace, to match the URL + // bar's behavior (stripsurroundingwhitespace). + clipboard = clipboard.replace(/\s*\n\s*/g, ""); + + clipboard = stripUnsafeProtocolOnPaste(clipboard); + + // If its not the current tab, we don't need to do anything because the + // browser doesn't exist. + let where = whereToOpenLink(event, true, false); + let lastLocationChange; + if (where == "current") { + lastLocationChange = gBrowser.selectedBrowser.lastLocationChange; + } + + getShortcutOrURIAndPostData(clipboard).then(data => { + try { + makeURI(data.url); + } catch (ex) { + // Not a valid URI. + return; + } + + try { + addToUrlbarHistory(data.url); + } catch (ex) { + // Things may go wrong when adding url to session history, + // but don't let that interfere with the loading of the url. + Cu.reportError(ex); + } + + if (where != "current" || + lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) { + openUILink(data.url, event, + { ignoreButton: true, + disallowInheritPrincipal: !data.mayInheritPrincipal }); + } + }); + + event.stopPropagation(); + } + + function stripUnsafeProtocolOnPaste(pasteData) { + // Don't allow pasting javascript URIs since we don't support + // LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL for those. + let changed = false; + let pasteDataNoJS = pasteData.replace(/\r?\n/g, "") + .replace(/^(?:\s*javascript:)+/i, + () => { changed = true; + return ""; }); + return changed ? pasteDataNoJS : pasteData; + } + + function addToUrlbarHistory(aUrlToAdd) + { + if (gPrivate) + return; + + if (!Services.prefs.getBoolPref("browser.urlbar.historyEnabled")) + return; + + // Remove leading and trailing spaces first. + aUrlToAdd = aUrlToAdd.trim(); + + if (!aUrlToAdd) + return; + // Don't store bad URLs. + if (aUrlToAdd.search(/[\x00-\x1F]/) != -1) + return; + + getShortcutOrURIAndPostData(aUrlToAdd).then(data => { + var fixedUpURI = Services.uriFixup.createFixupURI(data.url, 0); + if (!fixedUpURI.schemeIs("data")) + PlacesUtils.history.markPageAsTyped(fixedUpURI); + }).catch(() => {}); + + // Open or create the urlbar history database. + var file = GetUrlbarHistoryFile(); + var connection = Services.storage.openDatabase(file); + connection.beginTransaction(); + if (!connection.tableExists("urlbarhistory")) + connection.createTable("urlbarhistory", "url TEXT"); + + // If the URL is already present in the database then remove it from + // its current position. It is then reinserted at the top of the list. + var statement = connection.createStatement( + "DELETE FROM urlbarhistory WHERE LOWER(url) = LOWER(?1)"); + statement.bindByIndex(0, aUrlToAdd); + statement.execute(); + statement.finalize(); + + // Put the value as it was typed by the user in to urlbar history. + statement = connection.createStatement( + "INSERT INTO urlbarhistory (url) VALUES (?1)"); + statement.bindByIndex(0, aUrlToAdd); + statement.execute(); + statement.finalize(); + + // Remove any expired history items so that we don't let + // this grow without bound. + connection.executeSimpleSQL( + "DELETE FROM urlbarhistory WHERE ROWID NOT IN " + + "(SELECT ROWID FROM urlbarhistory ORDER BY ROWID DESC LIMIT 30)"); + connection.commitTransaction(); + connection.close(); + } diff --git a/comm/suite/base/content/contentAreaContextOverlay.xul b/comm/suite/base/content/contentAreaContextOverlay.xul new file mode 100644 index 0000000000..57601b7d94 --- /dev/null +++ b/comm/suite/base/content/contentAreaContextOverlay.xul @@ -0,0 +1,401 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<!DOCTYPE overlay SYSTEM "chrome://communicator/locale/contentAreaCommands.dtd" > +<overlay id="contentAreaContextOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <!-- Context menu --> + <script> + // Global variable that holds the nsContextMenu instance. + var gContextMenu = null; + </script> + <script src="chrome://communicator/content/nsContextMenu.js"/> + <script src="chrome://global/content/contentAreaUtils.js"/> + + <stringbundleset> + <stringbundle id="contentAreaCommandsBundle" + src="chrome://communicator/locale/contentAreaCommands.properties"/> + </stringbundleset> + + <popupset id="contentAreaContextSet"> + <!-- This is a generic context menu for a content area. It contains + each and every possible menu choice. The appropriate items are + hidden/shown upon display, based on what the user has clicked-on. + --> + <menupopup id="contentAreaContextMenu" + pagemenu="start" + onpopupshowing="if (event.target != this) return true; gContextMenu = new nsContextMenu(this, event.shiftKey, event); return gContextMenu.shouldDisplay;" + onpopuphiding="if (event.target != this) return; gContextMenu.hiding(); gContextMenu = null;"> + <menuseparator id="page-menu-separator"/> + + <menuitem id="spell-no-suggestions" + disabled="true" + label="&spellNoSuggestions.label;"/> + <menuseparator id="spell-add-separator"/> + <menuitem id="spell-add-to-dictionary" + label="&spellAddToDictionary.label;" + accesskey="&spellAddToDictionary.accesskey;" + oncommand="InlineSpellCheckerUI.addToDictionary();"/> + <menuitem id="spell-undo-add-to-dictionary" + label="&spellUndoAddToDictionary.label;" + accesskey="&spellUndoAddToDictionary.accesskey;" + oncommand="InlineSpellCheckerUI.undoAddToDictionary();"/> + <menuitem id="spell-ignore-word" + label="&spellIgnoreWord.label;" + accesskey="&spellIgnoreWord.accesskey;" + oncommand="InlineSpellCheckerUI.ignoreWord();"/> + <menuseparator id="spell-suggestions-separator"/> + <menuitem id="context-openlinkintab" + label="&openLinkCmdInTab.label;" + accesskey="&openLinkCmdInTab.accesskey;" + usercontextid="0" + oncommand="gContextMenu.openLinkInTab(event);"/> + <menuitem id="context-openlink" + label="&openLinkCmd.label;" + accesskey="&openLinkCmd.accesskey;" + oncommand="gContextMenu.openLinkInWindow();"/> + <menuitem id="context-openlinkinprivatewindow" + label="&openLinkCmdInPrivateWindow.label;" + accesskey="&openLinkCmdInPrivateWindow.accesskey;" + oncommand="gContextMenu.openLinkInPrivateWindow();"/> + <menuseparator id="context-sep-open"/> + <menuitem id="context-bookmarklink" + label="&bookmarkLinkCmd.label;" + accesskey="&bookmarkLinkCmd.accesskey;" + oncommand="gContextMenu.bookmarkLink();"/> + <menuitem id="context-savelink" + valueSaveAs="&saveLinkAsCmd.label;" + valueSave="&saveLinkCmd.label;" + accesskey="&saveLinkCmd.accesskey;" + oncommand="gContextMenu.saveLink();"/> + <menuitem id="context-copyemail" + label="©EmailCmd.label;" + accesskey="©EmailCmd.accesskey;" + oncommand="gContextMenu.copyEmail();"/> + <menuitem id="context-copylink" + label="©LinkCmd.label;" + accesskey="©LinkCmd.accesskey;" + command="cmd_copyLink"/> + <menuseparator id="context-sep-copylink"/> + <menuitem id="context-media-play" + label="&mediaPlay.label;" + accesskey="&mediaPlay.accesskey;" + oncommand="gContextMenu.mediaCommand('play');"/> + <menuitem id="context-media-pause" + label="&mediaPause.label;" + accesskey="&mediaPause.accesskey;" + oncommand="gContextMenu.mediaCommand('pause');"/> + <menuitem id="context-media-mute" + label="&mediaMute.label;" + accesskey="&mediaMute.accesskey;" + oncommand="gContextMenu.mediaCommand('mute');"/> + <menuitem id="context-media-unmute" + label="&mediaUnmute.label;" + accesskey="&mediaUnmute.accesskey;" + oncommand="gContextMenu.mediaCommand('unmute');"/> + <menu id="context-media-playbackrate" + label="&mediaPlaybackRate.label;" + accesskey="&mediaPlaybackRate.accesskey;"> + <menupopup id="media-playbackrate-popup" + oncommand="gContextMenu.mediaCommand('playbackRate', + event.target.id.replace(/.*-/, '') / 100);"> + <menuitem id="context-media-playbackrate-050" + label="&mediaPlaybackRate050.label;" + accesskey="&mediaPlaybackRate050.accesskey;" + type="radio" + name="playbackrate"/> + <menuitem id="context-media-playbackrate-100" + label="&mediaPlaybackRate100.label;" + accesskey="&mediaPlaybackRate100.accesskey;" + type="radio" + name="playbackrate"/> + <menuitem id="context-media-playbackrate-125" + label="&mediaPlaybackRate125.label;" + accesskey="&mediaPlaybackRate125.accesskey;" + type="radio" + name="playbackrate"/> + <menuitem id="context-media-playbackrate-150" + label="&mediaPlaybackRate150.label;" + accesskey="&mediaPlaybackRate150.accesskey;" + type="radio" + name="playbackrate"/> + <menuitem id="context-media-playbackrate-200" + label="&mediaPlaybackRate200.label;" + accesskey="&mediaPlaybackRate200.accesskey;" + type="radio" + name="playbackrate"/> + </menupopup> + </menu> + <menuitem id="context-media-loop" + label="&mediaLoop.label;" + accesskey="&mediaLoop.accesskey;" + type="checkbox" + oncommand="gContextMenu.mediaCommand('loop');"/> + <menuitem id="context-media-showcontrols" + label="&mediaShowControls.label;" + accesskey="&mediaShowControls.accesskey;" + oncommand="gContextMenu.mediaCommand('showcontrols');"/> + <menuitem id="context-media-hidecontrols" + label="&mediaHideControls.label;" + accesskey="&mediaHideControls.accesskey;" + oncommand="gContextMenu.mediaCommand('hidecontrols');"/> + <menuitem id="context-video-showstats" + label="&videoShowStats.label;" + accesskey="&videoShowStats.accesskey;" + oncommand="gContextMenu.mediaCommand('showstats');"/> + <menuitem id="context-video-hidestats" + label="&videoHideStats.label;" + accesskey="&videoHideStats.accesskey;" + oncommand="gContextMenu.mediaCommand('hidestats');"/> + <menuitem id="context-video-fullscreen" + label="&videoFullScreen.label;" + accesskey="&videoFullScreen.accesskey;" + oncommand="gContextMenu.fullScreenVideo();"/> + <menuseparator id="context-media-sep-commands"/> + <menuitem id="context-fitimage" + type="checkbox" + label="&fitImageCmd.label;" + accesskey="&fitImageCmd.accesskey;" + oncommand="gContextMenu.toggleImageSize();"/> + <menuitem id="context-reloadimage" + label="&reloadImageCmd.label;" + accesskey="&reloadImageCmd.accesskey;" + oncommand="gContextMenu.reloadImage();"/> + <menuitem id="context-viewimage" + label="&viewImageCmd.label;" + accesskey="&viewImageCmd.accesskey;" + oncommand="gContextMenu.viewMedia(event);" + onclick="checkForMiddleClick(this, event);"/> + <menuitem id="context-copyimage" + label="©ImageCmd.label;" + accesskey="©ImageCmd.accesskey;" + command="cmd_copyImage"/> + <menuitem id="context-viewvideo" + label="&viewVideoCmd.label;" + accesskey="&viewVideoCmd.accesskey;" + oncommand="gContextMenu.viewMedia(event);" + onclick="checkForMiddleClick(this, event);"/> + <menuitem id="context-copyvideourl" + label="©VideoURLCmd.label;" + accesskey="©VideoURLCmd.accesskey;" + oncommand="gContextMenu.copyMediaLocation();"/> + <menuitem id="context-copyaudiourl" + label="©AudioURLCmd.label;" + accesskey="©AudioURLCmd.accesskey;" + oncommand="gContextMenu.copyMediaLocation();"/> + <menuseparator id="context-sep-copyimage"/> + <menuitem id="context-blockimage" + oncommand="gContextMenu.toggleImageBlocking(true);"/> + <menuitem id="context-unblockimage" + oncommand="gContextMenu.toggleImageBlocking(false);"/> + <menuseparator id="context-sep-blockimage"/> + <menuitem id="context-saveimage" + valueSaveAs="&saveImageAsCmd.label;" + valueSave="&saveImageCmd.label;" + accesskey="&saveImageCmd.accesskey;" + oncommand="gContextMenu.saveMedia();"/> + <menuitem id="context-setDesktopBackground" + label="&setDesktopBackgroundCmd.label;" + accesskey="&setDesktopBackgroundCmd.accesskey;" + oncommand="gContextMenu.setDesktopBackground();"/> + <menuitem id="context-viewimageinfo" + label="&viewImageInfoCmd.label;" + accesskey="&viewImageInfoCmd.accesskey;" + oncommand="gContextMenu.viewImageInfo();"/> + <menuitem id="context-savevideo" + label="&saveVideoCmd.label;" + accesskey="&saveVideoCmd.accesskey;" + oncommand="gContextMenu.saveMedia();"/> + <menuitem id="context-saveaudio" + label="&saveAudioCmd.label;" + accesskey="&saveAudioCmd.accesskey;" + oncommand="gContextMenu.saveMedia();"/> + <menuitem id="context-video-saveimage" + label="&videoSaveImage.label;" + accesskey="&videoSaveImage.accesskey;" + oncommand="gContextMenu.saveVideoFrameAsImage();"/> + <menuseparator id="context-sep-image"/> + <menuitem id="context-back" + label="&goBackCmd.label;" + accesskey="&goBackCmd.accesskey;" + oncommand="BrowserBack(event)" + onclick="checkForMiddleClick(this, event);"/> + <menuitem id="context-forward" + label="&goForwardCmd.label;" + accesskey="&goForwardCmd.accesskey;" + oncommand="BrowserForward(event)" + onclick="checkForMiddleClick(this, event);"/> + <menuitem id="context-reload" + label="&reloadCmd.label;" + accesskey="&reloadCmd.accesskey;" + oncommand="BrowserReload(event);" + onclick="checkForMiddleClick(this, event);"/> + <menuitem id="context-stop" + label="&stopCmd.label;" + accesskey="&stopCmd.accesskey;" + disabled="true" + oncommand="BrowserStop();"/> + <menuseparator id="context-sep-stop"/> + <menuitem id="context-bookmarkpage" + label="&bookmarkPageCmd.label;" + accesskey="&bookmarkPageCmd.accesskey;" + oncommand="gContextMenu.bookmarkThisPage();"/> + <menuitem id="context-savepage" + valueSaveAs="&savePageAsCmd.label;" + valueSave="&savePageCmd.label;" + accesskey="&savePageCmd.accesskey;" + oncommand="saveDocument(window.content.document, true);"/> + <menuseparator id="context-sep-viewbgimage"/> + <menuitem id="context-viewbgimage" + label="&viewBGImageCmd.label;" + accesskey="&viewBGImageCmd.accesskey;" + oncommand="gContextMenu.viewBGImage(event);" + onclick="checkForMiddleClick(this, event);"/> + <menuitem id="context-undo"/> + <menuitem id="context-redo"/> + <menuseparator id="context-sep-undo"/> + <menuitem id="context-cut"/> + <menuitem id="context-copy"/> + <menuitem id="context-paste"/> + <menuitem id="context-delete"/> + <menuseparator id="context-sep-paste"/> + <menuitem id="context-selectall"/> + <menuseparator id="context-sep-selectall"/> + <menuitem id="context-keywordfield" + label="&keywordfield.label;" + accesskey="&keywordfield.accesskey;" + oncommand="AddKeywordForSearchField();"/> + <menuitem id="context-searchselect" + oncommand="BrowserSearch.loadSearch(gContextMenu.searchSelected(), true, event);"/> + <menuseparator id="context-sep-properties"/> + <menuitem id="context-viewpartialsource-selection" + label="&viewPartialSourceForSelectionCmd.label;" + accesskey="&viewPartialSourceCmd.accesskey;" + oncommand="gContextMenu.viewPartialSource('selection');"/> + <menuitem id="context-viewpartialsource-mathml" + label="&viewPartialSourceForMathMLCmd.label;" + accesskey="&viewPartialSourceCmd.accesskey;" + oncommand="gContextMenu.viewPartialSource('mathml');"/> + <menuitem id="context-viewsource" + label="&viewPageSourceCmd.label;" + accesskey="&viewPageSourceCmd.accesskey;" + observes="isImage" + oncommand="BrowserViewSource(gContextMenu.browser);"/> + <menuitem id="context-viewinfo" + label="&viewPageInfoCmd.label;" + accesskey="&viewPageInfoCmd.accesskey;" + oncommand="gContextMenu.viewInfo();"/> + <menuitem id="context-metadata" + label="&metadataCmd.label;" + accesskey="&metadataCmd.accesskey;" + oncommand="gContextMenu.showMetadata();"/> + <menuseparator id="frame-sep"/> + <menu id="frame" label="&thisFrameMenu.label;" accesskey="&thisFrameMenu.accesskey;"> + <menupopup id="frame_popup"> + <menuitem id="context-showonlythisframe" + label="&showOnlyThisFrameCmd.label;" + accesskey="&showOnlyThisFrameCmd.accesskey;" + oncommand="gContextMenu.showOnlyThisFrame();"/> + <menuitem id="context-openframeintab" + label="&openFrameCmdInTab.label;" + accesskey="&openFrameCmdInTab.accesskey;" + oncommand="gContextMenu.openFrameInTab(event);"/> + <menuitem id="context-openframe" + label="&openFrameCmd.label;" + accesskey="&openFrameCmd.accesskey;" + oncommand="gContextMenu.openFrame();"/> + <menuseparator/> + <menuitem id="context-reloadframe" + label="&reloadFrameCmd.label;" + accesskey="&reloadFrameCmd.accesskey;" + oncommand="gContextMenu.reloadFrame();"/> + <menuseparator/> + <menuitem id="context-bookmarkframe" + label="&bookmarkFrameCmd.label;" + accesskey="&bookmarkFrameCmd.accesskey;" + oncommand="gContextMenu.addBookmarkForFrame();"/> + <menuitem id="context-saveframe" + valueSaveAs="&saveFrameAsCmd.label;" + valueSave="&saveFrameCmd.label;" + accesskey="&saveFrameCmd.accesskey;" + oncommand="saveDocument(gContextMenu.target.ownerDocument, true);"/> + <menuseparator/> + <menuitem id="context-printframe" + label="&printFrameCmd.label;" + accesskey="&printFrameCmd.accesskey;" + oncommand="gContextMenu.printFrame();"/> + <menuseparator/> + <menuitem id="context-viewframesource" + label="&viewFrameSourceCmd.label;" + accesskey="&viewFrameSourceCmd.accesskey;" + oncommand="gContextMenu.viewFrameSource();"/> + <menuitem id="context-viewframeinfo" + label="&viewFrameInfoCmd.label;" + accesskey="&viewFrameInfoCmd.accesskey;" + oncommand="gContextMenu.viewFrameInfo();"/> + </menupopup> + </menu> + <menuseparator id="spell-separator"/> + <menuitem id="spell-check-enabled" + label="&spellCheckEnable.label;" + type="checkbox" + accesskey="&spellCheckEnable.accesskey;" + oncommand="InlineSpellCheckerUI.toggleEnabled();"/> + <menuitem id="spell-add-dictionaries-main" + label="&spellAddDictionaries.label;" + accesskey="&spellAddDictionaries.accesskey;" + oncommand="openDictionaryList();"/> + <menu id="spell-dictionaries" + label="&spellDictionaries.label;" + accesskey="&spellDictionaries.accesskey;"> + <menupopup id="spell-dictionaries-menu"> + <menuseparator id="spell-language-separator"/> + <menuitem id="spell-add-dictionaries" + label="&spellAddDictionaries.label;" + accesskey="&spellAddDictionaries.accesskey;" + oncommand="openDictionaryList();"/> + </menupopup> + </menu> + <menuseparator hidden="true" id="context-sep-bidi"/> + <menuitem hidden="true" id="context-bidi-text-direction-toggle" + label="&bidiSwitchTextDirectionItem.label;" + accesskey="&bidiSwitchTextDirectionItem.accesskey;" + command="cmd_switchTextDirection"/> + <menuitem hidden="true" id="context-bidi-page-direction-toggle" + label="&bidiSwitchPageDirectionItem.label;" + accesskey="&bidiSwitchPageDirectionItem.accesskey;" + oncommand="SwitchDocumentDirection(window.content);"/> + <menuseparator id="fill-login-separator" hidden="true"/> + <menu id="fill-login" + label="&fillLoginMenu.label;" + label-login="&fillLoginMenu.label;" + label-password="&fillPasswordMenu.label;" + label-username="&fillUsernameMenu.label;" + accesskey="&fillLoginMenu.accesskey;" + accesskey-login="&fillLoginMenu.accesskey;" + accesskey-password="&fillPasswordMenu.accesskey;" + accesskey-username="&fillUsernameMenu.accesskey;" + hidden="true"> + <menupopup id="fill-login-popup"> + <menuitem id="fill-login-no-logins" + label="&noLoginSuggestions.label;" + disabled="true" + hidden="true"/> + <menuseparator id="saved-logins-separator"/> + <menuitem id="fill-login-saved-passwords" + label="&viewSavedLogins.label;" + oncommand="gContextMenu.openPasswordManager();"/> + </menupopup> + </menu> + <menuseparator id="inspect-separator" + hidden="true"/> + <menuitem id="context-inspect" + hidden="true" + label="&devtoolsInspect.label;" + accesskey="&devtoolsInspect.accesskey;" + oncommand="gContextMenu.inspectNode();"/> + </menupopup> + </popupset> +</overlay> diff --git a/comm/suite/base/content/defaultClientDialog.js b/comm/suite/base/content/defaultClientDialog.js new file mode 100644 index 0000000000..d44fb4f412 --- /dev/null +++ b/comm/suite/base/content/defaultClientDialog.js @@ -0,0 +1,58 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +// this dialog can only be opened if we have a shell service +const {ShellService} = ChromeUtils.import("resource:///modules/ShellService.jsm"); +const nsIPrefBranch = Ci.nsIPrefBranch; + +function onLoad() +{ + var defaultList = document.getElementById("defaultList"); + var appTypes = ShellService.shouldBeDefaultClientFor; + /* Iterate through the list of possible default client types and check for + each list item if we want to be the default for that type using the AND + conjunction */ + for (var i = 0; i < defaultList.getRowCount(); i++) { + var currentItem = defaultList.getItemAtIndex(i); + try { + if (ShellService.isDefaultClient(false, Ci.nsIShellService[currentItem.value])) { + currentItem.checked = true; + currentItem.disabled = true; + } + else if (Ci.nsIShellService[currentItem.value] & appTypes) + currentItem.checked = true; + } catch (e) { + currentItem.hidden = true; + } + } +} + +function onAccept() +{ + // for each checked item, if we aren't already the default, make us the default. + var appTypes = 0; + var appTypesCheck = 0; + var defaultList = document.getElementById("defaultList"); + + for (var i = 0; i < defaultList.getRowCount(); i++) { + var currentItem = defaultList.getItemAtIndex(i); + var currentAppType = Ci.nsIShellService[currentItem.value]; + + if (currentItem.checked) { + appTypesCheck |= currentAppType; + + if (!currentItem.disabled) + appTypes |= currentAppType; + } + } + + if (appTypes) + ShellService.setDefaultClient(false, true, appTypes); + + // Update the pref for which app types we should check if we are the default app + ShellService.shouldBeDefaultClientFor = appTypesCheck; + + ShellService.shouldCheckDefaultClient = document.getElementById('checkOnStartup').checked; +} diff --git a/comm/suite/base/content/defaultClientDialog.xul b/comm/suite/base/content/defaultClientDialog.xul new file mode 100644 index 0000000000..db075a4b88 --- /dev/null +++ b/comm/suite/base/content/defaultClientDialog.xul @@ -0,0 +1,37 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin/"?> + +<!DOCTYPE window [ + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > + %brandDTD; + <!ENTITY % defaultClientDTD SYSTEM "chrome://communicator/locale/defaultClientDialog.dtd" > + %defaultClientDTD; +]> + +<dialog xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + id="defaultClientDialog" + buttons="accept,cancel" + onload="onLoad();" + ondialogaccept="return onAccept();" + title="&defaultClient.title;"> + + <script src="chrome://communicator/content/defaultClientDialog.js"/> + + <description>&defaultClient.intro;</description> + <listbox rows="4" id="defaultList"> + <listitem value="BROWSER" type="checkbox" label="&browser.label;"/> + <listitem value="MAIL" type="checkbox" label="&email.label;"/> + <listitem value="NEWS" type="checkbox" label="&newsgroups.label;"/> + <listitem value="RSS" type="checkbox" label="&feeds.label;"/> + </listbox> + + <separator class="thin"/> + <checkbox id="checkOnStartup" checked="true" label="&checkOnStartup.label;" accesskey="&checkOnStartup.accesskey;"/> + +</dialog> diff --git a/comm/suite/base/content/extensionsOverlay.css b/comm/suite/base/content/extensionsOverlay.css new file mode 100644 index 0000000000..9a2b0ce535 --- /dev/null +++ b/comm/suite/base/content/extensionsOverlay.css @@ -0,0 +1,7 @@ +/* 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/. */ + +.legacy-warning { + display: none; +} diff --git a/comm/suite/base/content/findUtils.js b/comm/suite/base/content/findUtils.js new file mode 100644 index 0000000000..a894e8a4c9 --- /dev/null +++ b/comm/suite/base/content/findUtils.js @@ -0,0 +1,141 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +var gFindBundle; + +function nsFindInstData() {} +nsFindInstData.prototype = +{ + // set the next three attributes on your object to override the defaults + browser : null, + + get rootSearchWindow() { return this._root || this.window.content; }, + set rootSearchWindow(val) { this._root = val; }, + + get currentSearchWindow() { + if (this._current) + return this._current; + + var focusedWindow = this.window.document.commandDispatcher.focusedWindow; + if (!focusedWindow || focusedWindow == this.window) + focusedWindow = this.window.content; + + return focusedWindow; + }, + set currentSearchWindow(val) { this._current = val; }, + + get webBrowserFind() { return this.browser.webBrowserFind; }, + + init : function() { + var findInst = this.webBrowserFind; + // set up the find to search the focussedWindow, bounded by the content window. + var findInFrames = findInst.QueryInterface(Ci.nsIWebBrowserFindInFrames); + findInFrames.rootSearchFrame = this.rootSearchWindow; + findInFrames.currentSearchFrame = this.currentSearchWindow; + + // always search in frames for now. We could add a checkbox to the dialog for this. + findInst.searchFrames = true; + }, + + window : window, + _root : null, + _current : null +} + +// browser is the <browser> element +// rootSearchWindow is the window to constrain the search to (normally window.content) +// currentSearchWindow is the frame to start searching (can be, and normally, rootSearchWindow) +function findInPage(findInstData) +{ + var findbar = document.getElementById("FindToolbar"); + if (findbar && Services.prefs.getBoolPref("browser.findbar.enabled")) + findbar.onFindCommand(); + else if ("findDialog" in window && window.findDialog) // is the find dialog up already? + window.findDialog.focus(); + else + { + findInstData.init(); + window.findDialog = window.openDialog("chrome://global/content/finddialog.xul", "_blank", "chrome,resizable=no,dependent=yes", findInstData); + } +} + +function findAgainInPage(findInstData, reverse) +{ + var findbar = document.getElementById("FindToolbar"); + if (findbar && Services.prefs.getBoolPref("browser.findbar.enabled")) + { + // first, look to see whether XPFE typeaheadfind wants to find next + var sip = Cc["@mozilla.org/supports-interface-pointer;1"] + .createInstance(Ci.nsISupportsInterfacePointer); + sip.data = content; + Services.obs.notifyObservers(sip, "nsWebBrowserFind_FindAgain", reverse ? "up" : "down"); + if (sip.data) // XPFE typeahead find was not interested in this find next + findbar.onFindAgainCommand(reverse); + } + else + { + // get the find service, which stores global find state, and init the + // nsIWebBrowser find with it. We don't assume that there was a previous + // Find that set this up. + var findService = Cc["@mozilla.org/find/find_service;1"] + .getService(Ci.nsIFindService); + + var searchString = findService.searchString; + if (searchString.length == 0) { + // no previous find text + findInPage(findInstData); + return; + } + + findInstData.init(); + var findInst = findInstData.webBrowserFind; + findInst.searchString = searchString; + findInst.matchCase = findService.matchCase; + findInst.wrapFind = findService.wrapFind; + findInst.entireWord = findService.entireWord; + findInst.findBackwards = findService.findBackwards ^ reverse; + + var found = findInst.findNext(); + if (!found) { + if (!gFindBundle) + gFindBundle = document.getElementById("findBundle"); + + Services.prompt.alert(window, gFindBundle.getString("notFoundTitle"), gFindBundle.getString("notFoundWarning")); + } + + // Reset to normal value, otherwise setting can get changed in find dialog + findInst.findBackwards = findService.findBackwards; + } +} + +function canFindAgainInPage() +{ + var findbar = document.getElementById("FindToolbar"); + if (findbar && Services.prefs.getBoolPref("browser.findbar.enabled")) + // The findbar will just be brought up in an error state if you cannot find text again. + return true; + + var findService = Cc["@mozilla.org/find/find_service;1"] + .getService(Ci.nsIFindService); + return (findService.searchString.length > 0); +} + +function findLinksAsYouType() +{ + var findbar = document.getElementById("FindToolbar"); + if (findbar && Services.prefs.getBoolPref("accessibility.typeaheadfind.usefindbar")) + findbar.startFastFind(findbar.FIND_LINKS); + else + goDoCommand("cmd_findTypeLinks"); +} + +function findTextAsYouType() +{ + var findbar = document.getElementById("FindToolbar"); + if (findbar && Services.prefs.getBoolPref("accessibility.typeaheadfind.usefindbar")) + findbar.startFastFind(findbar.FIND_TYPEAHEAD); + else + goDoCommand("cmd_findTypeText"); +} diff --git a/comm/suite/base/content/fullscreen-video.xhtml b/comm/suite/base/content/fullscreen-video.xhtml new file mode 100644 index 0000000000..a56789a06a --- /dev/null +++ b/comm/suite/base/content/fullscreen-video.xhtml @@ -0,0 +1,156 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- 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/. --> + +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" accelerated="11"> +<head> + <link href="chrome://communicator/skin/fullscreen-video.css" + rel="stylesheet" type="text/css"/> + <script> + <![CDATA[ + +var contentVideo = window.arguments[0]; +var isPaused = window.arguments[1]; +var video; +var closeIcon; + +var title = (contentVideo.currentSrc || contentVideo.src).replace(/^.*\//, ""); +try { + title = decodeURI(title); +} catch (e) {} +document.title = title; + +window.addEventListener("focus", onFocus); +window.addEventListener("unload", onUnload); +window.addEventListener("keypress", onKeyPress); + +function onFocus() { + window.removeEventListener("focus", onFocus); + + window.fullScreen = true; + + window.addEventListener("deactivate", autoClose); + + video = document.querySelector("video"); + closeIcon = document.querySelector("div"); + + video.addEventListener("loadeddata", onLoadedData); + + // Automatically close this window when the playback ended, unless the user + // interacted with it. + video.addEventListener("ended", autoClose); + window.addEventListener("click", cancelAutoClose); + window.addEventListener("keypress", cancelAutoClose); + + video.addEventListener("playing", hideUI); + video.addEventListener("seeked", hideUI); + video.addEventListener("seeking", showUI); + video.addEventListener("pause", showUI); + video.addEventListener("ended", showUI); + + window.addEventListener("mousemove", onMouseMove); + + video.src = contentVideo.currentSrc || contentVideo.src; +} + +function onLoadedData() { + video.removeEventListener("loadeddata", onLoadedData); + video.volume = contentVideo.volume; + video.muted = contentVideo.muted; + video.poster = contentVideo.poster; + + if (contentVideo.currentTime && !contentVideo.ended) { + video.addEventListener("seeked", playbackStarts); + + video.currentTime = contentVideo.currentTime; + } else { + playbackStarts(); + } + + video.controls = true; + + if (!isPaused) + video.play(); +} + +function onMouseMove() { + showUI(); + resetIdleTimer(); +} + +function onUnload() { + if (video.currentSrc) { + contentVideo.currentTime = video.currentTime; + contentVideo.volume = video.volume; + contentVideo.muted = video.muted; + if (!video.paused && !video.ended) { + video.pause(); + contentVideo.play(); + } + } +} + +function onKeyPress(event) { + if (event.keyCode == event.DOM_VK_ESCAPE) { + window.close(); + return; + } + + resetIdleTimer(); + + if (!video.controls && + String.fromCharCode(event.charCode) == " ") + video.pause(); +} + +function playbackStarts() { + video.removeEventListener("seeked", playbackStarts); + + // Loading the data from the content video may take a second or two. We hide + // the video during that period. + document.body.style.visibility = "visible"; + video.focus(); +} + +function autoClose() { + window.close(); +} + +function cancelAutoClose() { + video.removeEventListener("ended", autoClose); + window.removeEventListener("click", cancelAutoClose); + window.removeEventListener("keypress", cancelAutoClose); +} + +var idleTimer = 0; +function resetIdleTimer() { + clearTimeout(idleTimer); + idleTimer = setTimeout(hideUI, 2000); +} + +function showUI() { + if (!video.controls) { + window.setCursor("auto"); + closeIcon.style.visibility = "visible"; + video.controls = true; + } +} + +function hideUI() { + if (!video.paused && !video.ended && !video.seeking && !video.error) { + window.setCursor("none"); + closeIcon.style.visibility = "hidden"; + video.controls = false; + } +} + + ]]></script> +</head> +<body> + <div onclick="window.close();"/> + <video/> +</body> +</html> diff --git a/comm/suite/base/content/gopherAddon.xhtml b/comm/suite/base/content/gopherAddon.xhtml new file mode 100644 index 0000000000..002e274972 --- /dev/null +++ b/comm/suite/base/content/gopherAddon.xhtml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!DOCTYPE html [ + <!ENTITY % htmlDTD + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "DTD/xhtml1-strict.dtd"> + %htmlDTD; + <!ENTITY % gopherAddonDTD + SYSTEM "chrome://communicator/locale/gopherAddon.dtd"> + %gopherAddonDTD; +]> + +<!-- 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/. --> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>&loadError.label;</title> + <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css"/> + <script> + <![CDATA[ + + function goToAddOn() { + document.location = "https://addons.thunderbird.net/addon/7685"; + } + + ]]></script> + </head> + + <body> + <div id="errorPageContainer"> + + <!-- Error Title --> + <div id="errorTitle"> + <h1 id="errorTitleText">&gopherAddon.title;</h1> + </div> + + <div id="errorLongContent"> + + <!-- Short Description --> + <div id="errorShortDesc"> + <p id="errorShortDescText">&gopherAddon.shortDesc;</p> + </div> + + <!-- Long Description --> + <div id="errorLongDesc">&gopherAddon.longDesc;</div> + + </div> + <!-- Go To Add-On Button --> + <button id="errorTryAgain" onclick="goToAddOn()">&goToAddOn.label;</button> + + </div> + </body> +</html> diff --git a/comm/suite/base/content/helpEditorOverlay.xul b/comm/suite/base/content/helpEditorOverlay.xul new file mode 100644 index 0000000000..355ea6c59c --- /dev/null +++ b/comm/suite/base/content/helpEditorOverlay.xul @@ -0,0 +1,54 @@ +<?xml version="1.0"?> +<!-- 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/. --> + +<overlay id="helpEditorOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://help/content/contextHelp.js"/> + <script> + <![CDATA[ + setHelpFileURI('chrome://communicator/locale/help/suitehelp.rdf'); + + function doPublishDialogHelpButton() + { + var selTab = document.getElementById('TabBox').selectedTab; + + if (selTab.id == "PublishTab") + openHelp('comp-doc-publish-publishtab'); + else + openHelp('comp-doc-publish-settingstab'); + } + ]]> + </script> + + <dialog id="advancedEditDlg" + buttons="accept,cancel,help" + ondialoghelp="openHelp('advanced_property_editor');"/> + + <dialog id="imageDlg" + buttons="accept,cancel,help" + ondialoghelp="openHelp('image_properties');"/> + + <dialog id="linkDlg" + buttons="accept,cancel,help" + ondialoghelp="openHelp('link_properties');"/> + + <dialog id="publishDlg" + buttons="accept,cancel,help" + ondialoghelp="doPublishDialogHelpButton();"/> + + <dialog id="publishProgressDlg" + buttons="cancel,help" + ondialoghelp="openHelp('comp-doc-publish-troubleshooting');"/> + + <dialog id="publishSettingsDlg" + buttons="accept,cancel,help" + ondialoghelp="openHelp('comp-doc-publish-site-settings');"/> + + <dialog id="tableDlg" + buttons="accept,extra1,cancel,help" + ondialoghelp="openHelp('table_properties');"/> + +</overlay> diff --git a/comm/suite/base/content/helpMessengerOverlay.xul b/comm/suite/base/content/helpMessengerOverlay.xul new file mode 100644 index 0000000000..1672e80624 --- /dev/null +++ b/comm/suite/base/content/helpMessengerOverlay.xul @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<!-- 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/. --> + +<overlay id="helpMessengerOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://help/content/contextHelp.js"/> + <script> + setHelpFileURI('chrome://communicator/locale/help/suitehelp.rdf'); + </script> + + <dialog id="editDirectories" + buttons="accept,help" + ondialoghelp="return openHelp('mail-ldap-properties');"/> + + <dialog id="addDirectory" + buttons="accept,cancel,help" + ondialoghelp="return openHelp('mail-ldap-properties');"/> + + <dialog id="accountManager" + buttons="accept,cancel,help" + ondialoghelp="return doHelpButton();"/> + + <dialog id="FilterEditor" + buttons="accept,cancel,help" + ondialoghelp="return openHelp('mail-filters');"/> + + <dialog id="junkMailInfo" + buttons="accept,help" + ondialoghelp="return openHelp('mail-junk');"/> + + <dialog id="select-offline" + buttons="accept,cancel,help" + ondialoghelp="return openHelp('mail-offline-items');"/> + + <dialog id="subscribeWindow" + buttons="accept,cancel,help" + ondialoghelp="return openHelp('mail-subscribe');"/> + + <dialog id="mailViewListDialog" + buttons="help" + ondialoghelp="return openHelp('message-views-using');"/> + + <dialog id="mailViewSetupDialog" + buttons="accept,cancel,help" + ondialoghelp="return openHelp('message-views-create-new');"/> + + <dialog id="msgCompSecurityInfo" + buttons="accept,help" + ondialoghelp="return openHelp('compose_security');"/> + + <dialog id="msgReadSecurityInfo" + buttons="accept,help" + ondialoghelp="return openHelp('received_security');"/> +</overlay> diff --git a/comm/suite/base/content/helpSecurityOverlay.xul b/comm/suite/base/content/helpSecurityOverlay.xul new file mode 100644 index 0000000000..2291cdb284 --- /dev/null +++ b/comm/suite/base/content/helpSecurityOverlay.xul @@ -0,0 +1,137 @@ +<?xml version="1.0"?> +<!-- 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/. --> + +<overlay id="securityManagerOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://help/content/contextHelp.js"/> + <script> + <![CDATA[ + setHelpFileURI('chrome://communicator/locale/help/suitehelp.rdf'); + + function doCertManagerHelpButton() + { + var selTab = document.getElementById('certMgrTabbox').selectedItem; + var selTabID = selTab.getAttribute('id'); + switch (selTabID) { + case 'mine_tab': + openHelp("my_certs"); + break; + case 'others_tab': + openHelp("others_certs"); + break; + case 'websites_tab': + openHelp("web_certs"); + break; + case 'ca_tab': + openHelp("ca_certs"); + break; + case 'orphan_tab': + openHelp("orphan_certs"); + break; + } + } + + function doDeleteCertificateHelpButton() { + let typeFlag = window.arguments[0]; + switch (typeFlag) { + case "mine_tab": + openHelp("delete_my_certs"); + break; + case "websites_tab": + openHelp("delete_web_certs"); + break; + case "ca_tab": + openHelp("delete_ca_certs"); + break; + case "others_tab": + openHelp("delete_email_certs"); + break; + } + } + ]]> + </script> + + <dialog id="certmanager" + buttons="accept,help" + ondialoghelp="return doCertManagerHelpButton();"/> + + <dialog id="certDetails" + buttons="accept,help" + ondialoghelp="openHelp('cert_details');"/> + + <dialog id="set_password" + buttons="accept,cancel,help" + ondialoghelp="openHelp('change_pwd');"/> + + <dialog id="devicemanager" + buttons="accept,help" + ondialoghelp="openHelp('sec_devices');"/> + + <dialog id="ssl_warning" + buttons="accept,cancel,help" + ondialoghelp="openHelp('which_token');"/> + + <dialog id="certAuthAsk" + buttons="accept,cancel,help" + ondialoghelp="openHelp('which_cert');"/> + + <dialog id="crlImportSuccess" + buttons="accept,cancel,help" + ondialoghelp="openHelp('validation-crl-import');"/> + + <dialog id="deleteCertificate" + buttons="accept,cancel,help" + ondialoghelp="doDeleteCertificateHelpButton();"/> + + <dialog id="editCaCert" + buttons="accept,cancel,help" + ondialoghelp="openHelp('edit_ca_certs');"/> + + <dialog id="editEmailCert" + buttons="accept,cancel,help" + ondialoghelp="openHelp('edit_email_certs');"/> + + <dialog id="editWebsiteCert" + buttons="accept,cancel,help" + ondialoghelp="openHelp('edit_web_certs');"/> + + <dialog id="escrowWarnDialog" + spacerflex="1" + buttons="accept,cancel,help,extra2" + ondialoghelp="openHelp('priv_key_copy');"/> + + <dialog id="getp12password" + buttons="accept,cancel,help" + ondialoghelp="openHelp('my_certs');"/> + + <dialog id="setp12password" + buttons="accept,cancel,help" + ondialoghelp="openHelp('cert_backup_pwd');"/> + + <dialog id="crlUpdatePref" + buttons="accept,cancel,help" + ondialoghelp="openHelp('validation-crl-auto-update-prefs');"/> + + <dialog id="serverCrlNextupdate" + buttons="accept,help" + ondialoghelp="openHelp('exp_crl');"/> + + <dialog id="crlviewer" + buttons="help" + ondialoghelp="openHelp('validation-crl-manage');"> + <hbox id="dialogButtons"> + <button dlgtype="help"/> + </hbox> + </dialog> + + <dialog id="reset_password" + buttons="accept,cancel,help" + ondialoghelp="openHelp('reset_pwd');"/> + + <dialog id="download_cert" + buttons="accept,cancel,help" + ondialoghelp="openHelp('new_ca');"/> +</overlay> diff --git a/comm/suite/base/content/nsContextMenu.js b/comm/suite/base/content/nsContextMenu.js new file mode 100644 index 0000000000..26514ff39d --- /dev/null +++ b/comm/suite/base/content/nsContextMenu.js @@ -0,0 +1,1676 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/*------------------------------ nsContextMenu --------------------------------- +| This JavaScript "class" is used to implement the browser's content-area | +| context menu. | +| | +| For usage, see references to this class in navigator.xul. | +| | +| Currently, this code is relatively useless for any other purpose. In the | +| longer term, this code will be restructured to make it more reusable. | +------------------------------------------------------------------------------*/ + +var {BrowserUtils} = + ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm"); +var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm" +var {LoginManagerContextMenu} = + ChromeUtils.import("resource://gre/modules/LoginManagerContextMenu.jsm"); + +XPCOMUtils.defineLazyGetter(this, "InlineSpellCheckerUI", () => { + let { InlineSpellChecker } = ChromeUtils.import( + "resource://gre/modules/InlineSpellChecker.jsm" + ); + return new InlineSpellChecker(); +}); + +XPCOMUtils.defineLazyGetter(this, "PageMenuParent", function() { + let tmp = {}; + ChromeUtils.import("resource://gre/modules/PageMenu.jsm", tmp); + return new tmp.PageMenuParent(); +}); + +XPCOMUtils.defineLazyModuleGetters(this, { + SpellCheckHelper: "resource://gre/modules/InlineSpellChecker.jsm", + findCssSelector: "resource://gre/modules/css-selector.js", + LoginHelper: "resource://gre/modules/LoginHelper.jsm", + LoginManagerContent: "resource://gre/modules/LoginManagerContent.jsm", + DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.jsm", + NetUtil: "resource://gre/modules/NetUtil.jsm", + ShellService: "resource:///modules/ShellService.jsm", + +}); + +var gContextMenuContentData = null; + +function nsContextMenu(aXulMenu, aIsShift, aEvent) { + this.shouldDisplay = true; + this.initMenu(aXulMenu, aIsShift, aEvent); +} + +// Prototype for nsContextMenu "class." +nsContextMenu.prototype = { + initMenu: function(aXulMenu, aIsShift, aEvent) { + // Get contextual info. + this.setTarget(document.popupNode, document.popupRangeParent, + document.popupRangeOffset); + + if (!this.shouldDisplay) + return; + + this.hasPageMenu = false; + if (!aIsShift && this.browser.docShell.allowJavascript && + Services.prefs.getBoolPref("javascript.enabled")) + this.hasPageMenu = PageMenuParent.buildAndAddToPopup(this.target, aXulMenu); + + this.isTextSelected = this.isTextSelection(); + this.isContentSelected = this.isContentSelection(); + + // Initialize gContextMenuContentData. + if (aEvent) + this.initContentData(aEvent); + // Initialize (disable/remove) menu items. + this.initItems(); + }, + + initContentData: function(aEvent) { + var addonInfo = {}; + var subject = { + event: aEvent, + addonInfo: addonInfo, + }; + subject.wrappedJSObject = subject; + // Notifies the Addon-SDK which then populates addonInfo. + Services.obs.notifyObservers(subject, "content-contextmenu"); + + var popupNode = this.target; + var doc = popupNode.ownerDocument; + + var contentType = null; + var contentDisposition = null; + if (this.onImage) { + try { + let imageCache = Cc["@mozilla.org/image/tools;1"] + .getService(Ci.imgITools) + .getImgCacheForDocument(doc); + let props = imageCache.findEntryProperties(popupNode.currentURI, doc); + if (props) { + let nsISupportsCString = Ci.nsISupportsCString; + contentType = props.get("type", nsISupportsCString).data; + try { + contentDisposition = props.get("content-disposition", + nsISupportsCString).data; + } catch (e) {} + } + } catch (e) { + Cu.reportError(e); + } + } + + gContextMenuContentData = { + isRemote: false, + event: aEvent, + popupNode: popupNode, + browser: this.browser, + principal: doc.nodePrincipal, + addonInfo: addonInfo, + documentURIObject: doc.documentURIObject, + docLocation: doc.location.href, + charSet: doc.characterSet, + referrer: doc.referrer, + referrerPolicy: doc.referrerPolicy, + contentType: contentType, + contentDisposition: contentDisposition, + frameOuterWindowID: doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .outerWindowID, + loginFillInfo: LoginManagerContent.getFieldContext(popupNode), + }; + }, + + hiding: function () { + gContextMenuContentData = null; + InlineSpellCheckerUI.clearSuggestionsFromMenu(); + InlineSpellCheckerUI.clearDictionaryListFromMenu(); + InlineSpellCheckerUI.uninit(); + LoginManagerContextMenu.clearLoginsFromMenu(document); + }, + + initItems: function() { + this.initPageMenuSeparator(); + this.initOpenItems(); + this.initNavigationItems(); + this.initViewItems(); + this.initMiscItems(); + this.initSpellingItems(); + this.initSaveItems(); + this.initClipboardItems(); + this.initMetadataItems(); + this.initMediaPlayerItems(); + this.initPasswordManagerItems(); + }, + + initPageMenuSeparator: function() { + this.showItem("page-menu-separator", this.hasPageMenu); + }, + + initOpenItems: function() { + var showOpen = this.onSaveableLink || (this.inDirList && this.onLink); + this.showItem("context-openlinkintab", showOpen); + this.showItem("context-openlink", showOpen && !gPrivate); + this.showItem("context-openlinkinprivatewindow", showOpen); + this.showItem("context-sep-open", showOpen); + }, + + initNavigationItems: function() { + // Back/Forward determined by canGoBack/canGoForward broadcasters. + this.setItemAttrFromNode("context-back", "disabled", "canGoBack"); + this.setItemAttrFromNode("context-forward", "disabled", "canGoForward"); + + var showNav = !(this.isContentSelected || this.onLink || this.onImage || + this.onCanvas || this.onVideo || this.onAudio || + this.onTextInput); + + this.showItem("context-back", showNav); + this.showItem("context-forward", showNav); + this.showItem("context-reload", showNav); + this.showItem("context-stop", showNav); + this.showItem("context-sep-stop", showNav); + + // XXX: Stop is determined in navigator.js; the canStop broadcaster is broken + //this.setItemAttrFromNode( "context-stop", "disabled", "canStop" ); + }, + + initSaveItems: function() { + var showSave = !(this.inDirList || this.isContentSelected || + this.onTextInput || this.onStandaloneImage || + this.onCanvas || this.onVideo || this.onAudio || + (this.onLink && this.onImage)); + if (showSave) + goSetMenuValue("context-savepage", + this.autoDownload ? "valueSave" : "valueSaveAs"); + this.showItem("context-savepage", showSave); + + // Save/send link depends on whether we're in a link. + if (this.onSaveableLink) + goSetMenuValue("context-savelink", + this.autoDownload ? "valueSave" : "valueSaveAs"); + this.showItem("context-savelink", this.onSaveableLink); + this.showItem("context-sendlink", this.onSaveableLink); + + // Save image depends on having loaded its content, video and audio don't. + showSave = (this.onLoadedImage && this.onCompletedImage) || + this.onStandaloneImage || this.onCanvas; + if (showSave) + goSetMenuValue("context-saveimage", + this.autoDownload ? "valueSave" : "valueSaveAs"); + this.showItem("context-saveimage", showSave); + this.showItem("context-savevideo", this.onVideo); + this.showItem("context-saveaudio", this.onAudio); + this.showItem("context-video-saveimage", this.onVideo); + if (this.onVideo) + this.setItemAttr("context-savevideo", "disabled", !this.mediaURL); + if (this.onAudio) + this.setItemAttr("context-saveaudio", "disabled", !this.mediaURL); + + // Send media URL (but not for canvas, since it's a big data: URL) + this.showItem("context-sendimage", showSave && !this.onCanvas); + this.showItem("context-sendvideo", this.onVideo); + this.showItem("context-sendaudio", this.onAudio); + if (this.onVideo) + this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL); + if (this.onAudio) + this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL); + }, + + initViewItems: function() { + // View source is always OK, unless in directory listing. + this.showItem("context-viewpartialsource-selection", + this.isContentSelected && !this.onTextInput); + this.showItem("context-viewpartialsource-mathml", + this.onMathML && !this.isContentSelected); + + var showView = !(this.inDirList || this.onImage || this.isContentSelected || + this.onCanvas || this.onVideo || this.onAudio || + this.onLink || this.onTextInput); + + this.showItem("context-viewsource", showView); + this.showItem("context-viewinfo", showView); + + var showInspect = DevToolsShim.isEnabled() && + "gDevTools" in window && + Services.prefs.getBoolPref("devtools.inspector.enabled", false); + this.showItem("inspect-separator", showInspect); + this.showItem("context-inspect", showInspect); + + this.showItem("context-sep-properties", + !(this.inDirList || this.isContentSelected || this.onTextInput || + this.onCanvas || this.onVideo || this.onAudio)); + // Set Desktop Background depends on whether an image was clicked on, + // and requires the shell service. + var canSetDesktopBackground = ShellService && + ShellService.canSetDesktopBackground; + this.showItem("context-setDesktopBackground", + canSetDesktopBackground && (this.onLoadedImage || this.onStandaloneImage)); + + this.showItem("context-sep-image", + this.onLoadedImage || this.onStandaloneImage); + + if (canSetDesktopBackground && this.onLoadedImage) + // Disable the Set Desktop Background menu item if we're still trying to load the image + this.setItemAttr("context-setDesktopBackground", "disabled", + (("complete" in this.target) && !this.target.complete) ? "true" : null); + + this.showItem("context-fitimage", this.onStandaloneImage && + content.document.imageResizingEnabled); + if (this.onStandaloneImage && content.document.imageResizingEnabled) { + this.setItemAttr("context-fitimage", "disabled", + content.document.imageIsOverflowing ? null : "true"); + this.setItemAttr("context-fitimage", "checked", + content.document.imageIsResized ? "true" : null); + } + + // Reload image depends on an image that's not fully loaded + this.showItem("context-reloadimage", (this.onImage && !this.onCompletedImage)); + + // View image depends on having an image that's not standalone + // (or is in a frame), or a canvas. + this.showItem("context-viewimage", + (this.onImage && (!this.inSyntheticDoc || this.inFrame)) || + this.onCanvas); + + // View video depends on not having a standalone video. + this.showItem("context-viewvideo", this.onVideo && + (!this.inSyntheticDoc || this.inFrame)); + this.setItemAttr("context-viewvideo", "disabled", !this.mediaURL); + + // View background image depends on whether there is one, but don't make + // background images of a stand-alone media document available + this.showItem("context-viewbgimage", showView && !this.inSyntheticDoc); + this.showItem("context-sep-viewbgimage", showView && !this.inSyntheticDoc); + this.setItemAttr("context-viewbgimage", "disabled", this.hasBGImage ? null : "true"); + + this.showItem("context-viewimageinfo", this.onImage); + + // Hide Block and Unblock menuitems. + this.showItem("context-blockimage", false); + this.showItem("context-unblockimage", false); + this.showItem("context-sep-blockimage", false); + + // Block image depends on whether an image was clicked on. + if (this.onImage) { + var uri = Services.io.newURI(this.mediaURL); + if (uri instanceof Ci.nsIURL && uri.host) { + var serverLabel = uri.host; + // Limit length to max 15 characters. + serverLabel = serverLabel.replace(/^www\./i, ""); + if (serverLabel.length > 15) + serverLabel = serverLabel.substr(0, 15) + this.ellipsis; + + // Set label and accesskey for appropriate action and unhide menuitem. + var id = "context-blockimage"; + var attr = "blockImage"; + if (Services.perms.testPermission(uri, "image") == Services.perms.DENY_ACTION) { + id = "context-unblockimage"; + attr = "unblockImage"; + } + const bundle = document.getElementById("contentAreaCommandsBundle"); + this.setItemAttr(id, "label", + bundle.getFormattedString(attr, [serverLabel])); + this.setItemAttr(id, "accesskey", + bundle.getString(attr + ".accesskey")); + this.showItem(id, true); + this.showItem("context-sep-blockimage", true); + } + } + }, + + initMiscItems: function() { + // Use "Bookmark This Link" if on a link. + this.showItem("context-bookmarkpage", + !(this.isContentSelected || this.onTextInput || + this.onStandaloneImage || this.onVideo || this.onAudio)); + this.showItem("context-bookmarklink", this.onLink && !this.onMailtoLink); + this.showItem("context-searchselect", this.isTextSelected); + this.showItem("context-keywordfield", this.onTextInput && this.onKeywordField); + this.showItem("frame", this.inFrame); + this.showItem("frame-sep", this.inFrame); + if (this.inFrame) + goSetMenuValue("context-saveframe", + this.autoDownload ? "valueSave" : "valueSaveAs"); + + // BiDi UI + this.showItem("context-sep-bidi", !this.onNumeric && gShowBiDi); + this.showItem("context-bidi-text-direction-toggle", + this.onTextInput && !this.onNumeric && gShowBiDi); + this.showItem("context-bidi-page-direction-toggle", + !this.onTextInput && gShowBiDi); + }, + + initSpellingItems: function() { + var canSpell = InlineSpellCheckerUI.canSpellCheck && + !InlineSpellCheckerUI.initialSpellCheckPending && + this.canSpellCheck; + let showDictionaries = canSpell && InlineSpellCheckerUI.enabled; + var onMisspelling = InlineSpellCheckerUI.overMisspelling; + var showUndo = canSpell && InlineSpellCheckerUI.canUndo(); + this.showItem("spell-check-enabled", canSpell); + this.showItem("spell-separator", canSpell); + if (canSpell) + this.setItemAttr("spell-check-enabled", "checked", InlineSpellCheckerUI.enabled); + this.showItem("spell-add-to-dictionary", onMisspelling); + this.showItem("spell-undo-add-to-dictionary", showUndo); + this.showItem("spell-ignore-word", onMisspelling); + + // suggestion list + this.showItem("spell-add-separator", onMisspelling); + this.showItem("spell-suggestions-separator", onMisspelling || showUndo); + if (onMisspelling) { + var suggestionsSeparator = document.getElementById("spell-add-separator"); + var numsug = InlineSpellCheckerUI.addSuggestionsToMenu(suggestionsSeparator.parentNode, suggestionsSeparator, 5); + this.showItem("spell-no-suggestions", numsug == 0); + } else { + this.showItem("spell-no-suggestions", false); + } + + // dictionary list + this.showItem("spell-dictionaries", showDictionaries); + var dictMenu = document.getElementById("spell-dictionaries-menu"); + if (canSpell && dictMenu) { + var dictSep = document.getElementById("spell-language-separator"); + let count = InlineSpellCheckerUI.addDictionaryListToMenu(dictMenu, dictSep); + this.showItem(dictSep, count > 0); + this.showItem("spell-add-dictionaries-main", false); + } + else if (this.onEditableArea) { + // when there is no spellchecker but we might be able to spellcheck + // add the add to dictionaries item. This will ensure that people + // with no dictionaries will be able to download them + this.showItem("spell-language-separator", showDictionaries); + this.showItem("spell-add-dictionaries-main", showDictionaries); + } + else + this.showItem("spell-add-dictionaries-main", false); + }, + + initClipboardItems: function() { + // Copy depends on whether there is selected text. + // Enabling this context menu item is now done through the global + // command updating system + // this.setItemAttr("context-copy", "disabled", !this.isTextSelected()); + + goUpdateGlobalEditMenuItems(); + + this.showItem("context-undo", this.onTextInput); + this.showItem("context-redo", this.onTextInput); + this.showItem("context-sep-undo", this.onTextInput); + this.showItem("context-cut", this.onTextInput); + this.showItem("context-copy", this.isContentSelected || this.onTextInput); + this.showItem("context-paste", this.onTextInput); + this.showItem("context-delete", this.onTextInput); + this.showItem("context-sep-paste", this.onTextInput); + this.showItem("context-selectall", !(this.onLink || this.onImage || + this.onVideo || this.onAudio || + this.inSyntheticDoc)); + this.showItem("context-sep-selectall", + this.isContentSelected && !this.onTextInput); + // In a text area there will be nothing after select all, so we don't want a sep + // Otherwise, if there's text selected then there are extra menu items + // (search for selection and view selection source), so we do want a sep + + // XXX dr + // ------ + // nsDocumentViewer.cpp has code to determine whether we're + // on a link or an image. we really ought to be using that... + + // Copy email link depends on whether we're on an email link. + this.showItem("context-copyemail", this.onMailtoLink); + + // Copy link location depends on whether we're on a link. + this.showItem("context-copylink", this.onLink); + this.showItem("context-sep-copylink", this.onLink); + + // Copy image location depends on whether we're on an image. + this.showItem("context-copyimage", this.onImage); + this.showItem("context-copyvideourl", this.onVideo); + this.showItem("context-copyaudiourl", this.onAudio); + if (this.onVideo) + this.setItemAttr("context-copyvideourl", "disabled", !this.mediaURL); + if (this.onAudio) + this.setItemAttr("context-copyaudiourl", "disabled", !this.mediaURL); + this.showItem("context-sep-copyimage", + this.onImage || this.onVideo || this.onAudio); + }, + + initMetadataItems: function() { + // Show if user clicked on something which has metadata. + this.showItem("context-metadata", this.onMetaDataItem); + }, + + initMediaPlayerItems: function() { + var onMedia = (this.onVideo || this.onAudio); + // Several mutually exclusive items... play/pause, mute/unmute, show/hide + this.showItem("context-media-play", + onMedia && (this.target.paused || this.target.ended)); + this.showItem("context-media-pause", + onMedia && !this.target.paused && !this.target.ended); + this.showItem("context-media-mute", onMedia && !this.target.muted); + this.showItem("context-media-unmute", onMedia && this.target.muted); + this.showItem("context-media-playbackrate", + onMedia && this.target.duration != Number.POSITIVE_INFINITY); + this.showItem("context-media-loop", onMedia); + this.showItem("context-media-showcontrols", onMedia && !this.target.controls); + this.showItem("context-media-hidecontrols", onMedia && this.target.controls); + this.showItem("context-video-fullscreen", this.onVideo && + !this.target.ownerDocument.fullscreenElement); + + var statsShowing = this.onVideo && this.target.mozMediaStatisticsShowing; + this.showItem("context-video-showstats", + this.onVideo && this.target.controls && !statsShowing); + this.showItem("context-video-hidestats", + this.onVideo && this.target.controls && statsShowing); + + // Disable them when there isn't a valid media source loaded. + if (onMedia) { + this.setItemAttr("context-media-playbackrate-050", "checked", this.target.playbackRate == 0.5); + this.setItemAttr("context-media-playbackrate-100", "checked", this.target.playbackRate == 1.0); + this.setItemAttr("context-media-playbackrate-125", "checked", this.target.playbackRate == 1.25); + this.setItemAttr("context-media-playbackrate-150", "checked", this.target.playbackRate == 1.5); + this.setItemAttr("context-media-playbackrate-200", "checked", this.target.playbackRate == 2.0); + this.setItemAttr("context-media-loop", "checked", this.target.loop); + var hasError = this.target.error != null || + this.target.networkState == this.target.NETWORK_NO_SOURCE; + this.setItemAttr("context-media-play", "disabled", hasError); + this.setItemAttr("context-media-pause", "disabled", hasError); + this.setItemAttr("context-media-mute", "disabled", hasError); + this.setItemAttr("context-media-unmute", "disabled", hasError); + this.setItemAttr("context-media-playbackrate", "disabled", hasError); + this.setItemAttr("context-media-showcontrols", "disabled", hasError); + this.setItemAttr("context-media-hidecontrols", "disabled", hasError); + if (this.onVideo) { + let canSave = this.target.readyState >= this.target.HAVE_CURRENT_DATA; + this.setItemAttr("context-video-saveimage", "disabled", !canSave); + this.setItemAttr("context-video-fullscreen", "disabled", hasError); + this.setItemAttr("context-video-showstats", "disabled", hasError); + this.setItemAttr("context-video-hidestats", "disabled", hasError); + } + } + this.showItem("context-media-sep-commands", onMedia); + }, + + initPasswordManagerItems: function() { + let fillMenu = document.getElementById("fill-login"); + // If no fill Menu, probably mailContext so nothing to set up. + if (!fillMenu) + return; + + let loginFillInfo = gContextMenuContentData && gContextMenuContentData.loginFillInfo; + + // If we could not find a password field we + // don't want to show the form fill option. + let showFill = loginFillInfo && loginFillInfo.passwordField.found; + + // Disable the fill option if the user has set a master password + // or if the password field or target field are disabled. + let disableFill = !loginFillInfo || + !Services.logins || + !Services.logins.isLoggedIn || + loginFillInfo.passwordField.disabled || + (!this.onPassword && loginFillInfo.usernameField.disabled); + + this.showItem("fill-login-separator", showFill); + this.showItem("fill-login", showFill); + this.setItemAttr("fill-login", "disabled", disableFill); + + // Set the correct label for the fill menu + if (this.onPassword) { + fillMenu.setAttribute("label", fillMenu.getAttribute("label-password")); + fillMenu.setAttribute("accesskey", fillMenu.getAttribute("accesskey-password")); + } else { + fillMenu.setAttribute("label", fillMenu.getAttribute("label-login")); + fillMenu.setAttribute("accesskey", fillMenu.getAttribute("accesskey-login")); + } + + if (!showFill || disableFill) { + return; + } + let documentURI = gContextMenuContentData.documentURIObject; + let fragment = LoginManagerContextMenu.addLoginsToMenu(this.target, this.browser, documentURI); + + this.showItem("fill-login-no-logins", !fragment); + + if (!fragment) { + return; + } + let popup = document.getElementById("fill-login-popup"); + let insertBeforeElement = document.getElementById("fill-login-no-logins"); + popup.insertBefore(fragment, insertBeforeElement); + }, + + openPasswordManager: function() { + // LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host); + toDataManager(gContextMenuContentData.documentURIObject.host + '|passwords'); + }, + + /** + * Retrieve the array of CSS selectors corresponding to the provided node. The first item + * of the array is the selector of the node in its owner document. Additional items are + * used if the node is inside a frame, each representing the CSS selector for finding the + * frame element in its parent document. + * + * This format is expected by DevTools in order to handle the Inspect Node context menu + * item. + * + * @param {Node} + * The node for which the CSS selectors should be computed + * @return {Array} array of css selectors (strings). + */ + getNodeSelectors: function(node) { + let selectors = []; + while (node) { + selectors.push(findCssSelector(node)); + node = node.ownerGlobal.frameElement; + } + + return selectors; + }, + + inspectNode: function() { + let gBrowser = this.browser.ownerDocument.defaultView.gBrowser; + return DevToolsShim.inspectNode(gBrowser.selectedTab, + this.getNodeSelectors(this.target)); + }, + + // Set various context menu attributes based on the state of the world. + setTarget: function(aNode, aRangeParent, aRangeOffset) { + // Currently "isRemote" is always false. + //this.isRemote = gContextMenuContentData && gContextMenuContentData.isRemote; + + const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + + // Initialize contextual info. + this.onImage = false; + this.onLoadedImage = false; + this.onCompletedImage = false; + this.onStandaloneImage = false; + this.onCanvas = false; + this.onVideo = false; + this.onAudio = false; + this.onMetaDataItem = false; + this.onTextInput = false; + this.onNumeric = false; + this.onKeywordField = false; + this.mediaURL = ""; + this.onLink = false; + this.onMailtoLink = false; + this.onSaveableLink = false; + this.inDirList = false; + this.link = null; + this.linkURL = ""; + this.linkURI = null; + this.linkProtocol = ""; + this.linkHasNoReferrer = false; + this.onMathML = false; + this.inFrame = false; + this.inSyntheticDoc = false; + this.hasBGImage = false; + this.bgImageURL = ""; + this.autoDownload = false; + this.isTextSelected = false; + this.isContentSelected = false; + this.onEditableArea = false; + this.canSpellCheck = false; + this.onPassword = false; + + // Remember the node that was clicked. + this.target = aNode; + + if (aNode.nodeType == Node.DOCUMENT_NODE || + // Not display on XUL element but relax for <label class="text-link"> + (aNode.namespaceURI == xulNS && !isXULTextLinkLabel(aNode))) { + this.shouldDisplay = false; + return; + } + + let editFlags = SpellCheckHelper.isEditable(this.target, window); + this.browser = this.target.ownerDocument.defaultView + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell) + .chromeEventHandler; + this.principal = this.target.ownerDocument.nodePrincipal; + + this.autoDownload = Services.prefs.getBoolPref("browser.download.useDownloadDir"); + + // Check if we are in a synthetic document (stand alone image, video, etc.). + this.inSyntheticDoc = this.target.ownerDocument.mozSyntheticDocument; + // First, do checks for nodes that never have children. + if (this.target.nodeType == Node.ELEMENT_NODE) { + // See if the user clicked on an image. + if (this.target instanceof Ci.nsIImageLoadingContent && + this.target.currentURI) { + this.onImage = true; + + var request = + this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST); + if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE)) + this.onLoadedImage = true; + if (request && + (request.imageStatus & request.STATUS_LOAD_COMPLETE) && + !(request.imageStatus & request.STATUS_ERROR)) { + this.onCompletedImage = true; + } + + this.mediaURL = this.target.currentURI.spec; + + if (this.target.ownerDocument instanceof ImageDocument) + this.onStandaloneImage = true; + } + else if (this.target instanceof HTMLCanvasElement) { + this.onCanvas = true; + } + else if (this.target instanceof HTMLVideoElement) { + // Gecko always creates a HTMLVideoElement when loading an ogg file + // directly. If the media is actually audio, be smarter and provide + // a context menu with audio operations. + if (this.target.readyState >= this.target.HAVE_METADATA && + (this.target.videoWidth == 0 || this.target.videoHeight == 0)) + this.onAudio = true; + else + this.onVideo = true; + + this.mediaURL = this.target.currentSrc || this.target.src; + } + else if (this.target instanceof HTMLAudioElement) { + this.onAudio = true; + this.mediaURL = this.target.currentSrc || this.target.src; + } + else if (editFlags & (SpellCheckHelper.INPUT | SpellCheckHelper.TEXTAREA)) { + this.onTextInput = (editFlags & SpellCheckHelper.TEXTINPUT) !== 0; + this.onNumeric = (editFlags & SpellCheckHelper.NUMERIC) !== 0; + this.onEditableArea = (editFlags & SpellCheckHelper.EDITABLE) !== 0; + this.onPassword = (editFlags & SpellCheckHelper.PASSWORD) !== 0; + if (this.onEditableArea) { + InlineSpellCheckerUI.init(this.target.editor); + InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset); + } + this.onKeywordField = (editFlags & SpellCheckHelper.KEYWORD); + } + else if ( this.target instanceof HTMLHtmlElement ) { + // pages with multiple <body>s are lame. we'll teach them a lesson. + var bodyElt = this.target.ownerDocument.body; + if (bodyElt) { + var computedURL = this.getComputedURL(bodyElt, "background-image"); + if (computedURL) { + this.hasBGImage = true; + this.bgImageURL = makeURLAbsolute(bodyElt.baseURI, computedURL); + } + } + } + else if ("HTTPIndex" in content && + content.HTTPIndex instanceof Ci.nsIHTTPIndex) { + this.inDirList = true; + // Bubble outward till we get to an element with URL attribute + // (which should be the href). + var root = this.target; + while (root && !this.link) { + if (root.tagName == "tree") { + // Hit root of tree; must have clicked in empty space; + // thus, no link. + break; + } + + if (root.getAttribute("URL")) { + // Build pseudo link object so link-related functions work. + this.onLink = true; + this.link = {href: root.getAttribute("URL"), + getAttribute: function(attr) { + if (attr == "title") { + return root.firstChild.firstChild.getAttribute("label"); + } else { + return ""; + } + } + }; + this.linkURL = this.getLinkURL(); + this.linkURI = this.getLinkURI(); + this.linkProtocol = this.getLinkProtocol(); + this.onMailtoLink = (this.linkProtocol == "mailto"); + + // If element is a directory, then you can't save it. + this.onSaveableLink = root.getAttribute("container") != "true"; + } + else { + root = root.parentNode; + } + } + } + + this.canSpellCheck = this._isSpellCheckEnabled(this.target); + } + else if (this.target.nodeType == Node.TEXT_NODE) { + // For text nodes, look at the parent node to determine the spellcheck attribute. + this.canSpellCheck = this.target.parentNode && + this._isSpellCheckEnabled(this.target); + } + + // We have meta data on images. + this.onMetaDataItem = this.onImage; + + // Bubble out, looking for items of interest + const NS_MathML = "http://www.w3.org/1998/Math/MathML"; + const XMLNS = "http://www.w3.org/XML/1998/namespace"; + var elem = this.target; + while (elem) { + if (elem.nodeType == Node.ELEMENT_NODE) { + // Link? + if (!this.onLink && + (isXULTextLinkLabel(elem) || + (elem instanceof HTMLAnchorElement && elem.href) || + (elem instanceof HTMLAreaElement && elem.href) || + elem instanceof HTMLLinkElement || + (elem.namespaceURI == NS_MathML && elem.hasAttribute("href")) || + elem.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")) { + // Clicked on a link. + this.onLink = true; + this.onMetaDataItem = true; + // Remember corresponding element. + this.link = elem; + this.linkURL = this.getLinkURL(); + this.linkURI = this.getLinkURI(); + this.linkProtocol = this.getLinkProtocol(); + this.onMailtoLink = (this.linkProtocol == "mailto"); + // Remember if it is saveable. + this.onSaveableLink = this.isLinkSaveable(); + this.linkHasNoReferrer = BrowserUtils.linkHasNoReferrer(elem); + } + + // Text input? + if (!this.onTextInput) { + // Clicked on a link. + this.onTextInput = this.isTargetATextBox(elem); + } + + // Metadata item? + if (!this.onMetaDataItem) { + // We currently display metadata on anything which fits + // the below test. + if ((elem instanceof HTMLQuoteElement && elem.cite) || + (elem instanceof HTMLTableElement && elem.summary) || + (elem instanceof HTMLModElement && (elem.cite || elem.dateTime)) || + (elem instanceof HTMLElement && (elem.title || elem.lang)) || + elem.getAttributeNS(XMLNS, "lang")) { + dump("On metadata item.\n"); + this.onMetaDataItem = true; + } + } + + // Background image? Don't bother if we've already found a + // background image further down the hierarchy. Otherwise, + // we look for the computed background-image style. + if (!this.hasBGImage) { + var bgImgUrl = this.getComputedURL(elem, "background-image"); + if (bgImgUrl) { + this.hasBGImage = true; + this.bgImageURL = makeURLAbsolute(elem.baseURI, bgImgUrl); + } + } + } + elem = elem.parentNode; + } + + // See if the user clicked on MathML + if ((this.target.nodeType == Node.TEXT_NODE && + this.target.parentNode.namespaceURI == NS_MathML) || + (this.target.namespaceURI == NS_MathML)) + this.onMathML = true; + + // See if the user clicked in a frame. + var docDefaultView = this.target.ownerDocument.defaultView; + if (docDefaultView != docDefaultView.top) + this.inFrame = true; + + // if the document is editable, show context menu like in text inputs + if (!this.onEditableArea) { + if (editFlags & SpellCheckHelper.CONTENTEDITABLE) { + // If this.onEditableArea is false but editFlags is CONTENTEDITABLE, + // then the document itself must be editable. + this.onTextInput = true; + this.onKeywordField = false; + this.onImage = false; + this.onLoadedImage = false; + this.onCompletedImage = false; + this.onMathML = false; + this.inFrame = false; + this.hasBGImage = false; + this.onEditableArea = true; + var win = this.target.ownerDocument.defaultView; + var editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIEditingSession); + InlineSpellCheckerUI.init(editingSession.getEditorForWindow(win)); + InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset); + var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck; + this.showItem("spell-check-enabled", canSpell); + this.showItem("spell-separator", canSpell); + } + } + + function isXULTextLinkLabel(node) { + return node.namespaceURI == xulNS && + node.tagName == "label" && + node.classList.contains('text-link') && + node.href; + } + }, + + _isSpellCheckEnabled: function(aNode) { + // We can always force-enable spellchecking on textboxes + if (this.isTargetATextBox(aNode)) { + return true; + } + // We can never spell check something which is not content editable + var editable = aNode.isContentEditable; + if (!editable && aNode.ownerDocument) { + editable = aNode.ownerDocument.designMode == "on"; + } + if (!editable) { + return false; + } + // Otherwise make sure that nothing in the parent chain disables spellchecking + return aNode.spellcheck; + }, + + // Returns the computed style attribute for the given element. + getComputedStyle: function(aElem, aProp) { + return aElem.ownerDocument + .defaultView + .getComputedStyle(aElem, "").getPropertyValue(aProp); + }, + + // Returns a "url"-type computed style attribute value, with the url() stripped. + getComputedURL: function(aElem, aProp) { + var url = aElem.ownerDocument.defaultView + .getComputedStyle(aElem, "") + .getPropertyCSSValue(aProp); + if (url instanceof CSSPrimitiveValue) + url = [url]; + + for (var i = 0; i < url.length; i++) + if (url[i].primitiveType == CSSPrimitiveValue.CSS_URI) + return url[i].getStringValue(); + return null; + }, + + // Returns true if clicked-on link targets a resource that can be saved. + isLinkSaveable: function() { + return this.linkProtocol && this.linkProtocol != "mailto" && + this.linkProtocol != "javascript"; + }, + + // Block/Unblock image from loading in the future. + toggleImageBlocking: function(aBlock) { + const uri = Services.io.newURI(this.mediaURL); + if (aBlock) + Services.perms.add(uri, "image", Services.perms.DENY_ACTION); + else + Services.perms.remove(uri, "image"); + }, + + _openLinkInParameters : function (extra) { + let params = { charset: gContextMenuContentData.charSet, + originPrincipal: this.principal, + triggeringPrincipal: this.principal, + referrerURI: gContextMenuContentData.documentURIObject, + referrerPolicy: gContextMenuContentData.referrerPolicy, + noReferrer: this.linkHasNoReferrer || this.onPlainTextLink }; + for (let p in extra) { + params[p] = extra[p]; + } + + // If we want to change userContextId, we must be sure that we don't + // propagate the referrer. + if ("userContextId" in params && + params.userContextId != this.principal.originAttributes.userContextId) { + params.noReferrer = true; + } + + return params; + }, + + // Open linked-to URL in a new tab. + openLinkInTab: function(aEvent) { + urlSecurityCheck(this.linkURL, this.principal); + let referrerURI = gContextMenuContentData.documentURIObject; + + // if the mixedContentChannel is present and the referring URI passes + // a same origin check with the target URI, we can preserve the users + // decision of disabling MCB on a page for it's child tabs. + let persistAllowMixedContentInChildTab = false; + + if (this.browser.docShell.mixedContentChannel) { + const sm = Services.scriptSecurityManager; + try { + let targetURI = this.linkURI; + sm.checkSameOriginURI(referrerURI, targetURI, false); + persistAllowMixedContentInChildTab = true; + } + catch (e) { } + } + + let params = { + allowMixedContent: persistAllowMixedContentInChildTab, + userContextId: parseInt(aEvent.target.getAttribute('usercontextid')), + }; + + openLinkIn(this.linkURL, + aEvent && aEvent.shiftKey ? "tabshifted" : "tab", + this._openLinkInParameters(params)); + }, + + // Open linked-to URL in a new window. + openLinkInWindow: function() { + urlSecurityCheck(this.linkURL, this.principal); + openLinkIn(this.linkURL, "window", this._openLinkInParameters()); + }, + + // Open linked-to URL in a private window. + openLinkInPrivateWindow: function() { + urlSecurityCheck(this.linkURL, this.principal); + openLinkIn(this.linkURL, "window", + this._openLinkInParameters({ private: true })); + }, + + // Open frame in a new tab. + openFrameInTab: function(aEvent) { + let referrer = gContextMenuContentData.referrer; + openLinkIn(gContextMenuContentData.docLocation, + aEvent && aEvent.shiftKey ? "tabshifted" : "tab", + { charset: gContextMenuContentData.charSet, + referrerURI: referrer ? makeURI(referrer) : null }); + }, + + // Reload clicked-in frame. + reloadFrame: function() { + this.target.ownerDocument.location.reload(); + }, + + // Open clicked-in frame in its own window. + openFrame: function() { + let referrer = gContextMenuContentData.referrer; + openLinkIn(gContextMenuContentData.docLocation, "window", + { charset: gContextMenuContentData.charSet, + referrerURI: referrer ? makeURI(referrer) : null }); + }, + + printFrame: function() { + PrintUtils.printWindow(gContextMenuContentData.frameOuterWindowID, + this.browser); + }, + + // Open clicked-in frame in the same window + showOnlyThisFrame: function() { + urlSecurityCheck(gContextMenuContentData.docLocation, + this.principal, + Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); + let referrer = gContextMenuContentData.referrer; + openUILinkIn(gContextMenuContentData.docLocation, "current", + { disallowInheritPrincipal: true, + referrerURI: referrer ? makeURI(referrer) : null }); + }, + + // View Partial Source + viewPartialSource: function(aContext) { + var browser = getBrowser().selectedBrowser; + var target = aContext == "mathml" ? this.target : null; + gViewSourceUtils.viewPartialSourceInBrowser(browser, target, null); + }, + + // Open new "view source" window with the frame's URL. + viewFrameSource: function() { + gViewSourceUtils.viewSource({ + browser: this.browser, + URL: gContextMenuContentData.docLocation, + outerWindowID: gContextMenuContentData.frameOuterWindowID, + }); + }, + + viewInfo: function() { + BrowserPageInfo(gContextMenuContentData.docLocation, null, + null, null, this.browser); + }, + + viewImageInfo: function() { + BrowserPageInfo(gContextMenuContentData.docLocation, "mediaTab", + this.target, null, this.browser); + }, + + viewFrameInfo: function() { + BrowserPageInfo(gContextMenuContentData.docLocation, null, null, + gContextMenuContentData.frameOuterWindowID, this.browser); + }, + + toggleImageSize: function() { + content.document.toggleImageSize(); + }, + + // Reload image + reloadImage: function() { + urlSecurityCheck(this.mediaURL, + this.target.nodePrincipal, + Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); + if (this.target instanceof Ci.nsIImageLoadingContent) + this.target.forceReload(); + }, + + // Change current window to the URL of the image, video, or audio. + viewMedia(e) { + let doc = this.target.ownerDocument; + let where = whereToOpenLink(e); + + if (this.onCanvas) { + let systemPrincipal = Services.scriptSecurityManager + .getSystemPrincipal(); + this.target.toBlob((blob) => { + openUILinkIn(URL.createObjectURL(blob), where, + { referrerURI: doc.documentURIObject, + triggeringPrincipal: systemPrincipal, + }); + }); + } else { + urlSecurityCheck(this.mediaURL, + this.target.nodePrincipal, + Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); + openUILinkIn(this.mediaURL, where, + { referrerURI: doc.documentURIObject, + triggeringPrincipal: this.target.nodePrincipal, + }); + } + }, + + saveVideoFrameAsImage: function () { + urlSecurityCheck(this.mediaURL, this.browser.contentPrincipal, + Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); + var name = "snapshot.jpg"; + try { + let uri = makeURI(this.mediaURL); + let url = uri.QueryInterface(Ci.nsIURL); + if (url.fileBaseName) + name = decodeURI(url.fileBaseName) + ".jpg"; + } catch (e) { } + var video = this.target; + var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + var ctxDraw = canvas.getContext("2d"); + ctxDraw.drawImage(video, 0, 0); + saveImageURL(canvas.toDataURL("image/jpeg", ""), name, "SaveImageTitle", + true, true, + this.target.ownerDocument.documentURIObject, + null, null, null, (gPrivate ? true : false), + this.principal); + }, + + // Full screen video playback + fullScreenVideo: function() { + var isPaused = this.target.paused && this.target.currentTime > 0; + this.target.pause(); + + openDialog("chrome://communicator/content/fullscreen-video.xhtml", + "", "chrome,centerscreen,dialog=no", this.target, isPaused); + }, + + // Change current window to the URL of the background image. + viewBGImage(e) { + urlSecurityCheck(this.bgImageURL, + this.target.nodePrincipal, + Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); + + let doc = this.target.ownerDocument; + let where = whereToOpenLink(e); + openUILinkIn(this.bgImageURL, where, + { referrerURI: doc.documentURIObject, + triggeringPrincipal: this.target.nodePrincipal, + }); + }, + + setDesktopBackground: function() { + let url = (new URL(this.target.ownerDocument.location.href)).pathname; + let imageName = url.substr(url.lastIndexOf("/") + 1); + openDialog("chrome://communicator/content/setDesktopBackground.xul", + "_blank", "chrome,modal,titlebar,centerscreen", this.target, + imageName); + }, + + // Save URL of clicked-on frame. + saveFrame: function() { + saveDocument(this.target.ownerDocument, true); + }, + + // Save URL of clicked-on link. + saveLink: function() { + var doc = this.target.ownerDocument; + urlSecurityCheck(this.linkURL, this.principal); + this.saveHelper(this.linkURL, this.linkText(), null, true, doc); + }, + + // Helper function to wait for appropriate MIME-type headers and + // then prompt the user with a file picker + saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc) { + // canonical def in nsURILoader.h + const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020; + + // an object to proxy the data through to + // nsIExternalHelperAppService.doContent, which will wait for the + // appropriate MIME-type headers and then prompt the user with a + // file picker + function SaveAsListener() {} + SaveAsListener.prototype = { + extListener: null, + + onStartRequest: function onStartRequest(aRequest, aContext) { + // If the timer fired, the error status will have been caused by that, + // and we'll be restarting in onStopRequest, so no reason to notify + // the user. + if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT) + return; + + clearTimeout(timer); + + // some other error occured; notify the user... + if (!Components.isSuccessCode(aRequest.status)) { + try { + const bundle = Services.strings.createBundle( + "chrome://mozapps/locale/downloads/downloads.properties"); + + const title = bundle.GetStringFromName("downloadErrorAlertTitle"); + const msg = bundle.GetStringFromName("downloadErrorGeneric"); + + Services.prompt.alert(doc.defaultView, title, msg); + } catch (ex) {} + return; + } + + var extHelperAppSvc = + Cc["@mozilla.org/uriloader/external-helper-app-service;1"] + .getService(Ci.nsIExternalHelperAppService); + var channel = aRequest.QueryInterface(Ci.nsIChannel); + this.extListener = extHelperAppSvc.doContent(channel.contentType, aRequest, + doc.defaultView, true); + this.extListener.onStartRequest(aRequest, aContext); + }, + + onStopRequest: function onStopRequest(aRequest, aContext, aStatusCode) { + if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) { + // Do it the old fashioned way, which will pick the best filename + // it can without waiting. + saveURL(linkURL, linkText, dialogTitle, bypassCache, true, doc.documentURIObject, doc); + } + if (this.extListener) + this.extListener.onStopRequest(aRequest, aContext, aStatusCode); + }, + + onDataAvailable: function onDataAvailable(aRequest, aContext, aInputStream, + aOffset, aCount) { + this.extListener.onDataAvailable(aRequest, aContext, aInputStream, + aOffset, aCount); + } + } + + function Callbacks() {} + Callbacks.prototype = { + getInterface: function getInterface(aIID) { + if (aIID.equals(Ci.nsIAuthPrompt) || + aIID.equals(Ci.nsIAuthPrompt2)) { + // If the channel demands authentication prompt, we must cancel it + // because the save-as-timer would expire and cancel the channel + // before we get credentials from user. Both authentication dialog + // and save as dialog would appear on the screen as we fall back to + // the old fashioned way after the timeout. + timer.cancel(); + channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT); + } + throw Cr.NS_ERROR_NO_INTERFACE; + } + } + + // If we don't have the headers after a short time the user won't have + // received any feedback from the click. That's bad, so we give up + // waiting for the filename. + function timerCallback() { + channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT); + } + + // set up a channel to do the saving + var channel = NetUtil.newChannel({ + uri: makeURI(linkURL), + loadUsingSystemPrincipal: true, + securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL + }); + + channel.notificationCallbacks = new Callbacks(); + + var flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS; + + if (bypassCache) + flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; + + if (channel instanceof Ci.nsICachingChannel) + flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY; + + channel.loadFlags |= flags; + + if (channel instanceof Ci.nsIPrivateBrowsingChannel) + channel.setPrivate(gPrivate); + + if (channel instanceof Ci.nsIHttpChannel) { + channel.referrer = doc.documentURIObject; + if (channel instanceof Ci.nsIHttpChannelInternal) + channel.forceAllowThirdPartyCookie = true; + } + + // fallback to the old way if we don't see the headers quickly + var timeToWait = Services.prefs.getIntPref("browser.download.saveLinkAsFilenameTimeout"); + var timer = setTimeout(timerCallback, timeToWait); + + // kick off the channel with our proxy object as the listener + channel.asyncOpen2(new SaveAsListener()); + }, + + // Save URL of clicked-on image, video, or audio. + saveMedia: function() { + var doc = this.target.ownerDocument; + let referrerURI = doc.documentURIObject; + + if (this.onCanvas) + // Bypass cache, since it's a data: URL. + saveImageURL(this.target.toDataURL(), "canvas.png", "SaveImageTitle", + true, false, referrerURI, null, null, null, + (gPrivate ? true : false), + document.nodePrincipal /* system, because blob: */); + else if (this.onImage) { + urlSecurityCheck(this.mediaURL, this.principal); + saveImageURL(this.mediaURL, null, "SaveImageTitle", false, + false, referrerURI, null, gContextMenuContentData.contentType, + gContextMenuContentData.contentDisposition, + (gPrivate ? true : false), + this.principal); + } + else if (this.onVideo || this.onAudio) { + urlSecurityCheck(this.mediaURL, this.principal); + var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle"; + this.saveHelper(this.mediaURL, null, dialogTitle, false, doc); + } + }, + + // Backwards-compatibility wrapper + saveImage: function() { + if (this.onCanvas || this.onImage) + this.saveMedia(); + }, + + // Generate email address. + getEmail: function() { + // Get the comma-separated list of email addresses only. + // There are other ways of embedding email addresses in a mailto: + // link, but such complex parsing is beyond us. + var addresses; + try { + // Let's try to unescape it using a character set + var characterSet = this.target.ownerDocument.characterSet; + const textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"] + .getService(Ci.nsITextToSubURI); + addresses = this.linkURL.match(/^mailto:([^?]+)/)[1]; + addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses); + } + catch(ex) { + // Do nothing. + } + return addresses; + }, + + // Copy email to clipboard + copyEmail: function() { + var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"] + .getService(Ci.nsIClipboardHelper); + clipboard.copyString(this.getEmail()); + }, + + bookmarkThisPage : function() { + window.top.PlacesCommandHook.bookmarkPage(this.browser, + true); + }, + + bookmarkLink: function CM_bookmarkLink() { + window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId, + this.linkURL, + this.linkText()); + }, + + addBookmarkForFrame: function() { + var doc = this.target.ownerDocument; + var uri = doc.documentURIObject; + + var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri); + if (itemId == -1) { + var title = doc.title; + var description = PlacesUIUtils.getDescriptionFromDocument(doc); + PlacesUIUtils.showMinimalAddBookmarkUI(uri, title, description); + } + else + PlacesUIUtils.showItemProperties(itemId, + PlacesUtils.bookmarks.TYPE_BOOKMARK); + }, + + // Open Metadata window for node + showMetadata: function() { + window.openDialog("chrome://navigator/content/metadata.xul", + "_blank", + "scrollbars,resizable,chrome,dialog=no", + this.target); + }, + + /////////////// + // Utilities // + /////////////// + + // Show/hide one item (specified via name or the item element itself). + showItem: function(aItemOrId, aShow) { + var item = aItemOrId.constructor == String ? document.getElementById(aItemOrId) : aItemOrId; + if (item) + item.hidden = !aShow; + }, + + // Set given attribute of specified context-menu item. If the + // value is null, then it removes the attribute (which works + // nicely for the disabled attribute). + setItemAttr: function(aID, aAttr, aVal) { + var elem = document.getElementById(aID); + if (elem) { + if (aVal == null) { + // null indicates attr should be removed. + elem.removeAttribute(aAttr); + } + else { + // Set attr=val. + elem.setAttribute(aAttr, aVal); + } + } + }, + + // Set context menu attribute according to like attribute of another node + // (such as a broadcaster). + setItemAttrFromNode: function(aItem_id, aAttr, aOther_id) { + var elem = document.getElementById(aOther_id); + if (elem && elem.getAttribute(aAttr) == "true") { + this.setItemAttr(aItem_id, aAttr, "true"); + } + else { + this.setItemAttr(aItem_id, aAttr, null); + } + }, + + // Temporary workaround for DOM api not yet implemented by XUL nodes. + cloneNode: function(aItem) { + // Create another element like the one we're cloning. + var node = document.createElement(aItem.tagName); + + // Copy attributes from argument item to the new one. + var attrs = aItem.attributes; + for (var i = 0; i < attrs.length; i++) { + var attr = attrs.item(i); + node.setAttribute(attr.nodeName, attr.nodeValue); + } + + // Voila! + return node; + }, + + // Generate fully qualified URL for clicked-on link. + getLinkURL: function() { + if (this.link.href) + return this.link.href; + + var href; + if (this.link.namespaceURI == "http://www.w3.org/1998/Math/MathML") + href = this.link.getAttribute("href"); + + if (!href) + href = this.link.getAttributeNS("http://www.w3.org/1999/xlink", "href"); + + if (!href || !href.match(/\S/)) { + // Without this we try to save as the current doc, + // for example, HTML case also throws if empty + throw "Empty href"; + } + + return makeURLAbsolute(this.link.baseURI, href); + }, + + getLinkURI: function() { + try { + return makeURI(this.linkURL); + } + catch (ex) { + // e.g. empty URL string + } + + return null; + }, + + getLinkProtocol: function() { + if (this.linkURI) + return this.linkURI.scheme; // can be |undefined| + + return null; + }, + + // Get text of link. + linkText: function() { + var text = gatherTextUnder(this.link); + if (text && text.match(/\S/)) + return text; + + text = this.link.getAttribute("title"); + if (text && text.match(/\S/)) + return text; + + text = this.link.getAttribute("alt"); + if (text && text.match(/\S/)) + return text; + + if (this.link.href) + return this.link.href; + + if (elem.namespaceURI == "http://www.w3.org/1998/Math/MathML") + text = elem.getAttribute("href"); + if (!text || !text.match(/\S/)) + text = elem.getAttributeNS("http://www.w3.org/1999/xlink", "href"); + if (text && text.match(/\S/)) + return makeURLAbsolute(this.link.baseURI, text); + + return null; + }, + + /** + * Determines whether the focused window has selected text, and if so + * formats the first 15 characters for the label of the context-searchselect + * element according to the searchText string. + * @return true if there is selected text, false if not + */ + isTextSelection: function() { + var searchSelectText = this.searchSelected(16); + + if (!searchSelectText) + return false; + + if (searchSelectText.length > 15) + searchSelectText = searchSelectText.substr(0, 15) + this.ellipsis; + + // Use the current engine if it's a browser window and the search bar is + // visible, the default engine otherwise. + var engineName = ""; + if (window.BrowserSearch && + (isElementVisible(BrowserSearch.searchBar) || + BrowserSearch.searchSidebar)) + engineName = Services.search.currentEngine.name; + else + engineName = Services.search.defaultEngine.name; + + // format "Search <engine> for <selection>" string to show in menu + const bundle = document.getElementById("contentAreaCommandsBundle"); + var menuLabel = bundle.getFormattedString("searchSelected", + [engineName, searchSelectText]); + this.setItemAttr("context-searchselect", "label", menuLabel); + this.setItemAttr("context-searchselect", "accesskey", + bundle.getString("searchSelected.accesskey")); + + return true; + }, + + searchSelected: function(aCharlen) { + var focusedWindow = document.commandDispatcher.focusedWindow; + var searchStr = focusedWindow.getSelection(); + searchStr = searchStr.toString(); + + if (this.onTextInput) { + var fElem = this.target; + if ((fElem instanceof HTMLInputElement && + fElem.mozIsTextField(true)) || + fElem instanceof HTMLTextAreaElement) { + searchStr = fElem.value.substring(fElem.selectionStart, fElem.selectionEnd); + } + } + + // searching for more than 150 chars makes no sense + if (!aCharlen) + aCharlen = 150; + if (aCharlen < searchStr.length) { + // only use the first charlen important chars. see bug 221361 + var pattern = new RegExp("^(?:\\s*.){0," + aCharlen + "}"); + pattern.test(searchStr); + searchStr = RegExp.lastMatch; + } + + return searchStr.trim().replace(/\s+/g, " "); + }, + + // Returns true if anything is selected. + isContentSelection: function() { + return !document.commandDispatcher.focusedWindow.getSelection().isCollapsed; + }, + + // Returns true if the target is editable + isTargetEditable: function() { + if (this.target.ownerDocument.designMode == "on") + return true; + + for (var node = this.target; node; node = node.parentNode) + if (node.nodeType == node.ELEMENT_NODE && + node.namespaceURI == "http://www.w3.org/1999/xhtml") + return node.isContentEditable; + return false; + }, + + toString: function() { + return "contextMenu.target = " + this.target + "\n" + + "contextMenu.onImage = " + this.onImage + "\n" + + "contextMenu.onLink = " + this.onLink + "\n" + + "contextMenu.link = " + this.link + "\n" + + "contextMenu.inFrame = " + this.inFrame + "\n" + + "contextMenu.hasBGImage = " + this.hasBGImage + "\n"; + }, + + isTextBoxEnabled: function(aNode) { + return !aNode.ownerDocument.defaultView + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .isNodeDisabledForEvents(aNode); + }, + + isTargetATextBox: function(aNode) { + if (aNode instanceof HTMLInputElement) + return aNode.mozIsTextField(false) && this.isTextBoxEnabled(aNode); + + return aNode instanceof HTMLTextAreaElement && this.isTextBoxEnabled(aNode); + }, + + /** + * Determine whether a separator should be shown based on whether + * there are any non-hidden items between it and the previous separator. + * @param aSeparatorID + * The id of the separator element + * @return true if the separator should be shown, false if not + */ + shouldShowSeparator: function(aSeparatorID) { + let separator = document.getElementById(aSeparatorID); + if (separator) { + let sibling = separator.previousSibling; + while (sibling && sibling.localName != "menuseparator") { + if (sibling.getAttribute("hidden") != "true") + return true; + sibling = sibling.previousSibling; + } + } + return false; + }, + + mediaCommand: function(aCommand, aData) { + var media = this.target; + + switch (aCommand) { + case "play": + media.play(); + break; + case "pause": + media.pause(); + break; + case "loop": + media.loop = !media.loop; + break; + case "mute": + media.muted = true; + break; + case "unmute": + media.muted = false; + break; + case "playbackRate": + media.playbackRate = aData; + break; + case "hidecontrols": + media.removeAttribute("controls"); + break; + case "showcontrols": + media.setAttribute("controls", "true"); + break; + case "showstats": + case "hidestats": + var win = media.ownerDocument.defaultView; + var showing = aCommand == "showstats"; + media.dispatchEvent(new win.CustomEvent("media-showStatistics", + { bubbles: false, cancelable: true, detail: showing })); + break; + } + }, + + copyMediaLocation: function() { + var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"] + .getService(Ci.nsIClipboardHelper); + clipboard.copyString(this.mediaURL); + }, + + get imageURL() { + if (this.onImage) + return this.mediaURL; + return ""; + } +}; + +XPCOMUtils.defineLazyGetter(nsContextMenu.prototype, "ellipsis", function() { + return Services.prefs.getComplexValue("intl.ellipsis", + Ci.nsIPrefLocalizedString).data; +}); diff --git a/comm/suite/base/content/openLocation.js b/comm/suite/base/content/openLocation.js new file mode 100644 index 0000000000..2bb9524d94 --- /dev/null +++ b/comm/suite/base/content/openLocation.js @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + +var gInput; +var gAcceptButton; +var gLastPref = "general.open_location.last_url"; +var gOpenAppList; +var gBundle; +var gAction; + +function onLoad() +{ + gInput = document.getElementById("dialog.input"); + gAcceptButton = document.documentElement.getButton("accept"); + gOpenAppList = document.getElementById("openAppList"); + gBundle = document.getElementById("openLocationBundle"); + gAction = window.arguments[0].action; + // Set arguments action to prevent problems on cancel. + window.arguments[0].action = "-1"; + + switch (gAction) { + case "5": // attach web page + document.title = gBundle.getString("attachTitle"); + document.getElementById("enterLabel").value = gBundle.getString("attachEnterLabel"); + document.getElementById("openWhereBox").setAttribute("hidden", true); + + // Change accept button text to 'attach'. + gAcceptButton.label = gBundle.getString("attachButtonLabel"); + gLastPref = "mailnews.attach_web_page.last_url"; + + break; + + case "2": // open web page from composer + gOpenAppList.selectedItem = document.getElementById("editWindow"); + var openTopWindow = document.getElementById("currentTab"); + + // Change string to make more sense for Composer. + openTopWindow.setAttribute("label", + gBundle.getString("existingNavigatorWindow")); + + // Disable existing browser and new tab menuitems and create indicator + // if no browser windows found. + if (!Services.wm.getMostRecentWindow("navigator:browser")) { + openTopWindow.setAttribute("disabled", "true"); + document.getElementById("newTab").setAttribute("disabled", "true"); + gAction = "-1"; + } + break; + + default: // open web page + gOpenAppList.value = Services.prefs.getIntPref("general.open_location.last_window_choice"); + } + + gInput.value = Services.prefs.getStringPref(gLastPref, ""); + if (gInput.value) + gInput.select(); // XXX should probably be done automatically + + doEnabling(); +} + +function doEnabling() +{ + gAcceptButton.disabled = !gInput.value; +} + +function accept() +{ + var params = window.arguments[0]; + params.url = gInput.value; + params.action = gOpenAppList.value; + if (gAction == "4" || params.action == "4") + return; // private, don't set any preferences + + if (gAction != "5") { // open web page + // If there were no browser windows open and not set to open in composer + // then set to open in a new window. + if (gAction == "-1" && params.action != "2") + params.action = "1"; + + // If open web page from navigator window, save last window choice. + if (gAction == "0") + Services.prefs.setIntPref("general.open_location.last_window_choice", + gOpenAppList.value); + } + + SetStringPref(gLastPref, gInput.value); +} + +function onChooseFile() +{ + const nsIFilePicker = Ci.nsIFilePicker; + let fp = Cc["@mozilla.org/filepicker;1"] + .createInstance(nsIFilePicker); + fp.init(window, gBundle.getString("chooseFileDialogTitle"), + nsIFilePicker.modeOpen); + if (window.arguments[0].action != "5" && gOpenAppList.value == "2") { + // When loading into Composer, direct user to prefer HTML files and text + // files, so we call separately to control the order of the filter list. + fp.appendFilters(nsIFilePicker.filterHTML | nsIFilePicker.filterText); + fp.appendFilters(nsIFilePicker.filterAll); + } else { + fp.appendFilters(nsIFilePicker.filterHTML | nsIFilePicker.filterText | + nsIFilePicker.filterAll | nsIFilePicker.filterImages | + nsIFilePicker.filterXML); + } + + fp.open(rv => { + if (rv == nsIFilePicker.returnOK && fp.fileURL.spec && + fp.fileURL.spec.length > 0) { + gInput.value = fp.fileURL.spec; + } + + doEnabling(); + }); +} + +function useUBHistoryItem(aValue) +{ + gInput.value = aValue; + gInput.focus(); + doEnabling(); +} diff --git a/comm/suite/base/content/openLocation.xul b/comm/suite/base/content/openLocation.xul new file mode 100644 index 0000000000..1116f76562 --- /dev/null +++ b/comm/suite/base/content/openLocation.xul @@ -0,0 +1,74 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?> + +<!DOCTYPE dialog [ + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > + %brandDTD; + <!ENTITY % openDialogDTD SYSTEM "chrome://communicator/locale/openLocation.dtd" > + %openDialogDTD; +]> + +<dialog id="openLocation" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&caption.label;" + onload="onLoad()" + ondialogaccept="return accept();" + buttonlabelaccept="&open.label;" + style="width: 40em;" + persist="screenX screenY" + screenX="24" screenY="24"> + + <script src="chrome://global/content/globalOverlay.js"/> + <script src="chrome://communicator/content/openLocation.js"/> + <script src="chrome://communicator/content/utilityOverlay.js"/> + <script src="chrome://navigator/content/sessionHistoryUI.js"/> + + <stringbundle id="openLocationBundle" src="chrome://communicator/locale/openLocation.properties"/> + + <hbox> + <separator orient="vertical" class="thin"/> + <vbox flex="1"> + <label id="enterLabel" + value="&enter.label;" + control="dialog.input" + accesskey="&enter.accesskey;"/> + <separator class="thin"/> + + <hbox align="center"> + <textbox id="dialog.input" flex="1" type="autocomplete" + autocompletesearch="history file" timeout="50" maxrows="6" + enablehistory="true" class="uri-element" + oninput="doEnabling();"> + <menupopup id="ubhist-popup" class="autocomplete-history-popup" + popupalign="topleft" popupanchor="bottomleft" + onpopupshowing="createUBHistoryMenu(event.target);" + oncommand="useUBHistoryItem(event.target.label);"/> + </textbox> + <button label="&chooseFile.label;" accesskey="&chooseFile.accesskey;" oncommand="onChooseFile();"/> + </hbox> + <hbox id="openWhereBox" align="center"> + <label value="&openWhere.label;" accesskey="&openWhere.accesskey;" control="openAppList"/> + <menulist id="openAppList"> + <menupopup> + <menuitem value="0" + id="currentTab" + label="¤tTab.label;" + selected="true"/> + <menuitem value="3" id="newTab" label="&newTab.label;"/> + <menuitem value="1" id="newWindow" label="&newWindow.label;"/> + <menuitem value="4" id="newPrivate" label="&newPrivate.label;"/> + <menuseparator/> + <menuitem value="2" id="editWindow" label="&editNewWindow.label;"/> + </menupopup> + </menulist> + <spacer flex="1"/> + </hbox> + </vbox> + </hbox> + +</dialog> diff --git a/comm/suite/base/content/overrides/app-license.html b/comm/suite/base/content/overrides/app-license.html new file mode 100644 index 0000000000..ca69723fa8 --- /dev/null +++ b/comm/suite/base/content/overrides/app-license.html @@ -0,0 +1,8 @@ +<!-- 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/. --> + + <p><b>Binaries</b> of this product have been made available to you by the + <a href="http://www.seamonkey-project.org/">SeaMonkey Project</a> + under the Mozilla Public License 2.0 (MPL). + <a href="about:rights">Know your rights</a>.</p> diff --git a/comm/suite/base/content/safeMode.js b/comm/suite/base/content/safeMode.js new file mode 100644 index 0000000000..7572bfef8b --- /dev/null +++ b/comm/suite/base/content/safeMode.js @@ -0,0 +1,92 @@ +/* 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/. */ + +const { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm"); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +const appStartup = Services.startup; + +function restartApp() { + appStartup.quit(appStartup.eForceQuit | appStartup.eRestart); +} + +function clearAllPrefs() { + Services.prefs.resetUserPrefs(); + + // Remove the pref-overrides dir, if it exists. + try { + var prefOverridesDir = Services.dirsvc.get("PrefDOverride", Ci.nsIFile); + prefOverridesDir.remove(true); + } catch (ex) { + Cu.reportError(ex); + } +} + +function restoreDefaultBookmarks() { + Services.prefs.setBoolPref("browser.bookmarks.restore_default_bookmarks", true); +} + +function deleteLocalstore() { + // Delete the xulstore file. + let xulstoreFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + xulstoreFile.append("xulstore.json"); + if (xulstoreFile.exists()) + xulstoreFile.remove(false); +} + +function disableAddons() { + AddonManager.getAllAddons(function(aAddons) { + aAddons.forEach(function(aAddon) { + if (aAddon.type == "theme") { + // Setting userDisabled to false on the default theme activates it, + // disables all other themes and deactivates the applied persona, if + // any. + const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}"; + if (aAddon.id == DEFAULT_THEME_ID) + aAddon.userDisabled = false; + } + else { + aAddon.userDisabled = true; + } + }); + + restartApp(); + }); +} + +function onOK() { + try { + if (document.getElementById("resetUserPrefs").checked) + clearAllPrefs(); + if (document.getElementById("deleteBookmarks").checked) + restoreDefaultBookmarks(); + if (document.getElementById("resetToolbars").checked) + deleteLocalstore(); + if (document.getElementById("restoreSearch").checked) + Services.search.restoreDefaultEngines(); + if (document.getElementById("disableAddons").checked) { + disableAddons(); + // disableAddons will asynchronously restart the application + return false; + } + } catch(e) { + } + + restartApp(); + return false; +} + +function onCancel() { + appStartup.quit(appStartup.eForceQuit); + return false; +} + +function onLoad() { + document.documentElement.getButton("extra1").focus(); +} + +function UpdateOKButtonState() { + document.documentElement.getButton("accept").disabled = + !document.getElementsByAttribute("checked", "true").item(0); +} diff --git a/comm/suite/base/content/safeMode.xul b/comm/suite/base/content/safeMode.xul new file mode 100644 index 0000000000..76b874c783 --- /dev/null +++ b/comm/suite/base/content/safeMode.xul @@ -0,0 +1,58 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<!DOCTYPE prefwindow [ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> +%brandDTD; +<!ENTITY % safeModeDTD SYSTEM "chrome://communicator/locale/safeMode.dtd"> +%safeModeDTD; +<!ENTITY % utilityDTD SYSTEM "chrome://communicator/locale/utilityOverlay.dtd"> +%utilityDTD; +]> + +<?xml-stylesheet href="chrome://global/skin/"?> + +<dialog id="safeModeDialog" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&safeModeDialog.title;" + buttons="accept,cancel,extra1" + buttonlabelaccept="&changeAndRestartButton.label;" + buttonlabelcancel="&quitApplicationCmd.label;" + buttonlabelextra1="&continueButton.label;" + width="&window.width;" + ondialogaccept="return onOK();" + ondialogcancel="return onCancel();" + ondialogextra1="window.close();" + onload="onLoad();" + buttondisabledaccept="true"> + + <script src="chrome://communicator/content/safeMode.js"/> + + <description>&safeModeDescription.label;</description> + + <separator class="thin"/> + + <label value="&safeModeDescription2.label;"/> + <vbox id="tasks" oncommand="UpdateOKButtonState();"> + <checkbox id="disableAddons" + label="&disableAddons.label;" + accesskey="&disableAddons.accesskey;"/> + <checkbox id="resetToolbars" + label="&resetToolbars.label;" + accesskey="&resetToolbars.accesskey;"/> + <checkbox id="deleteBookmarks" + label="&deleteBookmarks.label;" + accesskey="&deleteBookmarks.accesskey;"/> + <checkbox id="resetUserPrefs" + label="&resetUserPrefs.label;" + accesskey="&resetUserPrefs.accesskey;"/> + <checkbox id="restoreSearch" + label="&restoreSearch.label;" + accesskey="&restoreSearch.accesskey;"/> + </vbox> + + <separator class="thin"/> +</dialog> diff --git a/comm/suite/base/content/tasksOverlay.js b/comm/suite/base/content/tasksOverlay.js new file mode 100644 index 0000000000..49ec7c03af --- /dev/null +++ b/comm/suite/base/content/tasksOverlay.js @@ -0,0 +1,276 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +function toNavigator() +{ + if (!CycleWindow("navigator:browser")) + OpenBrowserWindow(); +} + +function ExpirePassword() +{ + // Queries the HTTP Auth Manager and clears all sessions + Cc['@mozilla.org/network/http-auth-manager;1'] + .getService(Ci.nsIHttpAuthManager) + .clearAll(); + + // Expires the master password + Cc["@mozilla.org/security/pk11tokendb;1"] + .createInstance(Ci.nsIPK11TokenDB) + .getInternalKeyToken() + .checkPassword(""); +} + +function toDownloadManager() +{ + Cc["@mozilla.org/suite/suiteglue;1"] + .getService(Ci.nsISuiteGlue) + .showDownloadManager(); +} + +function toDataManager(aView) +{ + var useDlg = Services.prefs.getBoolPref("suite.manager.dataman.openAsDialog"); + + if (useDlg) { + var url = "chrome://communicator/content/dataman/dataman.xul"; + var win = toOpenWindowByType("data:manager", url, "", aView); + if (win && aView) + win.gDataman.loadView(aView); + return; + } + + switchToTabHavingURI("about:data", true, function(browser) { + if (aView) + browser.contentWindow.wrappedJSObject.gDataman.loadView(aView); + }); +} + +function toEM(aView) +{ + var useDlg = Services.prefs.getBoolPref("suite.manager.addons.openAsDialog"); + + if (useDlg) { + var view = aView ? { view: aView } : null; + var url = "chrome://mozapps/content/extensions/extensions.xul"; + var win = toOpenWindowByType("Addons:Manager", url, "", view); + if (win && aView) + win.loadView(aView); + return; + } + + switchToTabHavingURI("about:addons", true, function(browser) { + if (aView) + browser.contentWindow.wrappedJSObject.loadView(aView); + }); +} + +function toBookmarksManager() +{ + toOpenWindowByType("Places:Organizer", + "chrome://communicator/content/places/places.xul"); +} + +function toJavaScriptConsole() +{ + toOpenWindowByType("suite:console", "chrome://communicator/content/console/console.xul"); +} + +function toOpenWindow( aWindow ) +{ + try { + // Try to focus the previously focused window e.g. message compose body + aWindow.document.commandDispatcher.focusedWindow.focus(); + } catch (e) { + // e.g. non-XUL document; just raise the top window + aWindow.focus(); + } +} + +function toOpenWindowByType(inType, uri, features, args) +{ + // don't do several loads in parallel + if (uri in window) + return; + + var topWindow = Services.wm.getMostRecentWindow(inType); + if ( topWindow ) + { + toOpenWindow( topWindow ); + return topWindow; + } + else + { + // open the requested window, but block it until it's fully loaded + function newWindowLoaded(event) + { + // make sure that this handler is called only once + window.removeEventListener("unload", newWindowLoaded); + window[uri].removeEventListener("load", newWindowLoaded); + delete window[uri]; + } + + // Remember the newly loading window until it's fully loaded + // or until the current window passes away. + // Only pass args if they exist and have a value (see Bug 1279738). + if (typeof args != "undefined" && args) { + window[uri] = openDialog(uri, "", + features || "non-private,all,dialog=no", + args || null); + } + else { + window[uri] = openDialog(uri, "", + features || "non-private,all,dialog=no"); + } + + window[uri].addEventListener("load", newWindowLoaded); + window.addEventListener("unload", newWindowLoaded); + } + return; +} + +function OpenBrowserWindow() +{ + var win = Services.wm.getMostRecentWindow("navigator:browser"); + if (document.documentElement.getAttribute("windowtype") == + "navigator:browser" && window.content && window.content.document) + { + // if and only if the current window is a browser window and + // it has a document with a character set, then extract the + // current charset menu setting from the current document + // and use it to initialize the new browser window + return window.openDialog(getBrowserURL(), "_blank", + "chrome,all,dialog=no,non-private", null, + "charset=" + window.content.document.characterSet); + } + + if (win) { + // if a browser window already exists then set startpage to null so + // navigator.js can check pref for how new window should be opened + return win.openDialog(getBrowserURL(), "_blank", + "chrome,all,dialog=no,non-private", null); + } + + // open the first browser window as if we were starting up + var cmdLine = { + handleFlagWithParam: function handleFlagWithParam(flag, caseSensitive) { + return flag == "remote" ? "xfeDoCommand(openBrowser)" : null; + }, + handleFlag: function handleFlag(flag, caseSensitive) { + return false; + }, + preventDefault: true + }; + const clh_prefix = "@mozilla.org/commandlinehandler/general-startup;1"; + Cc[clh_prefix + "?type=browser"] + .getService(Ci.nsICommandLineHandler) + .handle(cmdLine); + return null; +} + +function CycleWindow(aType) { + let topWindowOfType = Services.wm.getMostRecentWindow(aType); + if (topWindowOfType == null) + return null; + + let topWindow = Services.wm.getMostRecentWindow(null); + if (topWindowOfType != topWindow) { + toOpenWindow(topWindowOfType); + return topWindowOfType; + } + + let topFound = false; + let enumerator = Services.wm.getEnumerator(aType); + let iWindow; + let firstWindow; + + while (enumerator.hasMoreElements()) { + iWindow = enumerator.getNext(); + if (!iWindow.closed) { + if (!firstWindow) { + firstWindow = iWindow; + } + if (topFound) { + toOpenWindow(iWindow); + return iWindow; + } + if (iWindow == topWindow) { + topFound = true; + } + } + } + + if (firstWindow == topWindow) // Only one window + return null; + + toOpenWindow(firstWindow); + return firstWindow; +} + +function windowMenuDidHide() +{ + let sep = document.getElementById("sep-window-list"); + // Clear old items + while (sep.nextElementSibling) { + sep.nextElementSibling.remove(); + } +} + +function checkFocusedWindow() +{ + let windows = Services.wm.getEnumerator(""); + let frag = document.createDocumentFragment(); + while (windows.hasMoreElements()) { + let win = windows.getNext(); + if (win.closed || win.document.documentElement.getAttribute("inwindowmenu") == "false") { + continue; + } + let item = document.createElement("menuitem"); + item.setAttribute("label", win.document.title); + item.setAttribute("type", "radio"); + if (win == window) { + item.setAttribute("checked", "true"); + } + item.addEventListener("command", () => { + if (win.windowState == window.STATE_MINIMIZED) { + win.restore(); + } + win.focus(); + }); + frag.appendChild(item); + } + document.getElementById("windowPopup").appendChild(frag); +} + +function toProfileManager() +{ + var promgrWin = Services.wm.getMostRecentWindow("mozilla:profileSelection"); + if (promgrWin) { + promgrWin.focus(); + } else { + var params = Cc["@mozilla.org/embedcomp/dialogparam;1"] + .createInstance(Ci.nsIDialogParamBlock); + + params.SetNumberStrings(1); + params.SetString(0, "menu"); + window.openDialog("chrome://communicator/content/profile/profileSelection.xul", + "", + "centerscreen,chrome,titlebar,resizable", + params); + } + // Here, we don't care about the result code + // that was returned in the param block. +} + +// This function is only used by macs. +function ZoomCurrentWindow() +{ + if (window.windowState == STATE_NORMAL) + window.maximize(); + else + window.restore(); +} diff --git a/comm/suite/base/content/tasksOverlay.xul b/comm/suite/base/content/tasksOverlay.xul new file mode 100644 index 0000000000..2835d5302e --- /dev/null +++ b/comm/suite/base/content/tasksOverlay.xul @@ -0,0 +1,124 @@ +<?xml version="1.0"?> +<!-- 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/. --> + +<?xml-stylesheet href="chrome://communicator/skin/tasksOverlay.css" type="text/css"?> + +<!DOCTYPE overlay [ +<!ENTITY % tasksDTD SYSTEM "chrome://communicator/locale/tasksOverlay.dtd" > +%tasksDTD; +]> + +<overlay id="tasksOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script src="chrome://communicator/content/tasksOverlay.js"/> + +<keyset id="tasksKeys"> +#ifdef XP_MACOSX + <key id="key_minimizeWindow" + command="cmd_minimizeWindow" + key="&minimizeWindowCmd.key;" + modifiers="accel"/> +#endif + <key id="key_navigator" key="&navigatorCmd.commandkey;" command="Tasks:Navigator" modifiers="accel"/> + <key id="key_downloadManager" key="&downloadManagerCmd.commandkey;" + command="downloadmgr" modifiers="accel"/> + <key id="key_addOnsManager" key="&addOnsManagerCmd.commandkey;" + command="addonsmgr" modifiers="accel,shift"/> + <key id="key_errorConsole" key="&errorConsoleCmd.commandkey2;" + command="Tasks:ErrorConsole" modifiers="accel,alt"/> +</keyset> +<commandset id="tasksCommands"> +#ifdef XP_MACOSX + <command id="cmd_minimizeWindow" oncommand="window.minimize();"/> + <command id="cmd_zoomWindow" oncommand="ZoomCurrentWindow();"/> +#endif + <command id="Tasks:DataMan" oncommand="toDataManager();"/> + <command id="Tasks:Navigator" oncommand="toNavigator();"/> + <command id="Tasks:ErrorConsole" oncommand="toJavaScriptConsole();"/> +</commandset> + +<broadcasterset id="mainBroadcasterSet"> + <broadcaster id="sync-setup-state" hidden="true"/> + <broadcaster id="sync-syncnow-state" hidden="true"/> +</broadcasterset> + + <!-- Tasks Menu --> + <menu id="tasksMenu" label="&tasksMenu.label;" accesskey="&tasksMenu.accesskey;"> + <menupopup id="taskPopup"> + <menuitem id="tasksDataman" + label="&datamanCmd.label;" + accesskey="&datamanCmd.accesskey;" + command="Tasks:DataMan"/> + <menu id="menu_passwordManager" + label="&passwordManagerCmd.label;" + accesskey="&passwordManagerCmd.accesskey;"> + <menupopup id="passwordPopup"> + <menuitem label="&passwordDisplayCmd.label;" + accesskey="&passwordDisplayCmd.accesskey;" + oncommand="toDataManager('|passwords');"/> + <menuitem label="&passwordExpireCmd.label;" + accesskey="&passwordExpireCmd.accesskey;" + oncommand="ExpirePassword();"/> + </menupopup> + </menu> + <menuitem id="downloadmgr" label="&downloadManagerCmd.label;" + accesskey="&downloadManagerCmd.accesskey;" + key="key_downloadManager" oncommand="toDownloadManager();"/> + <menuitem id="addonsmgr" label="&addOnsManagerCmd.label;" + accesskey="&addOnsManagerCmd.accesskey;" + key="key_addOnsManager" oncommand="toEM();"/> + <!-- only one of sync-setup or sync-syncnowitem will be showing at once + <menuitem id="sync-setup" + label="&syncSetup.label;" + accesskey="&syncSetup.accesskey;" + observes="sync-setup-state" + oncommand="gSyncUI.openSetup();" + hidden="true"/> + <menuitem id="sync-syncnowitem" + label="&syncSyncNowItem.label;" + accesskey="&syncSyncNowItem.accesskey;" + observes="sync-syncnow-state" + oncommand="gSyncUI.doSync(event);" + hidden="true"/> --> + <menuseparator id="devToolsSeparator"/> + <menu label="&webDevelopment.label;" accesskey="&webDevelopment.accesskey;"> + <menupopup id="toolsPopup"> + <menuitem id="javascriptConsole" label="&errorConsoleCmd.label;" + accesskey="&errorConsoleCmd.accesskey;" + key="key_errorConsole" command="Tasks:ErrorConsole"/> + </menupopup> + </menu> + <menuseparator id="sep_switchprofile"/> + <menuitem id="cmd_switchprofile" label="&switchProfileCmd.label;" accesskey="&switchProfileCmd.accesskey;" oncommand="toProfileManager();"/> + </menupopup> + </menu> + + <menu id="windowMenu" label="&windowMenu.label;" accesskey="&windowMenu.accesskey;" + onpopupshowing="checkFocusedWindow();" onpopuphidden="windowMenuDidHide();"> + <menupopup id="windowPopup"> +#ifdef XP_MACOSX + <menuitem command="cmd_minimizeWindow" + label="&minimizeWindowCmd.label;" + key="key_minimizeWindow" + position="1"/> + <menuitem command="cmd_zoomWindow" + label="&zoomWindowCmd.label;" + position="2"/> + <menuseparator position="3"/> +#endif + <menuitem label="&navigatorCmd.label;" accesskey="&navigatorCmd.accesskey;" key="key_navigator" command="Tasks:Navigator" id="tasksMenuNavigator" class="menuitem-iconic icon-navigator16"/> + + <!-- Overlays get stuffed in here. --> + <menuseparator id="sep-window-list"/> + </menupopup> + </menu> + + + <statusbarpanel id="component-bar" persist="collapsed"> + <toolbarbutton class="taskbutton" id="mini-nav" oncommand="toNavigator();" + tooltiptext="&taskNavigator.tooltip;"/> + </statusbarpanel> +</overlay> diff --git a/comm/suite/base/content/utilityOverlay.js b/comm/suite/base/content/utilityOverlay.js new file mode 100644 index 0000000000..627203d435 --- /dev/null +++ b/comm/suite/base/content/utilityOverlay.js @@ -0,0 +1,1969 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/** + * Communicator Shared Utility Library + * for shared application glue for the Communicator suite of applications + **/ + +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var { XPCOMUtils } = + ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + + +XPCOMUtils.defineLazyModuleGetters(this, { + BrowserUtils: "resource://gre/modules/BrowserUtils.jsm", + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", + RecentWindow: "resource:///modules/RecentWindow.jsm", +}); + +// XPCOMUtils.defineLazyGetter(this, "Weave", function() { +// let tmp = {}; +// ChromeUtils.import("resource://services-sync/main.js", tmp); +// return tmp.Weave; +// }); + +/* + Note: All Editor/Composer-related methods have been moved to editorApplicationOverlay.js, + so app windows that require those must include editorTasksOverlay.xul +*/ + +/** + * Go into online/offline mode + **/ + +const kProxyManual = ["network.proxy.ftp", + "network.proxy.http", + "network.proxy.socks", + "network.proxy.ssl"]; +var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab"; +var gShowBiDi = false; +var gUtilityBundle = null; +var gPrivate = null; + +function toggleOfflineStatus() +{ + var checkfunc; + try { + checkfunc = document.getElementById("offline-status").getAttribute('checkfunc'); + } + catch (ex) { + checkfunc = null; + } + + if (checkfunc) { + if (!eval(checkfunc)) { + // the pre-offline check function returned false, so don't go offline + return; + } + } + Services.io.manageOfflineStatus = false; + Services.io.offline = !Services.io.offline; +} + +function setNetworkStatus(networkProxyType) +{ + try { + Services.prefs.setIntPref("network.proxy.type", networkProxyType); + } + catch (ex) {} +} + +function InitProxyMenu() +{ + var networkProxyNo = document.getElementById("network-proxy-no"); + var networkProxyManual = document.getElementById("network-proxy-manual"); + var networkProxyPac = document.getElementById("network-proxy-pac"); + var networkProxyWpad = document.getElementById("network-proxy-wpad"); + var networkProxySystem = document.getElementById("network-proxy-system"); + + var proxyLocked = Services.prefs.prefIsLocked("network.proxy.type"); + if (proxyLocked) { + networkProxyNo.setAttribute("disabled", "true"); + networkProxyWpad.setAttribute("disabled", "true"); + networkProxySystem.setAttribute("disabled", "true"); + } + else { + networkProxyNo.removeAttribute("disabled"); + networkProxyWpad.removeAttribute("disabled"); + networkProxySystem.removeAttribute("disabled"); + } + + // If no proxy is configured, disable the menuitems. + // Checking for proxy manual settings. + var proxyManuallyConfigured = false; + for (var i = 0; i < kProxyManual.length; i++) { + if (Services.prefs.getStringPref(kProxyManual[i], "") != "") { + proxyManuallyConfigured = true; + break; + } + } + + if (proxyManuallyConfigured && !proxyLocked) { + networkProxyManual.removeAttribute("disabled"); + } + else { + networkProxyManual.setAttribute("disabled", "true"); + } + + //Checking for proxy PAC settings. + var proxyAutoConfigured = false; + if (Services.prefs.getStringPref("network.proxy.autoconfig_url", "") != "") + proxyAutoConfigured = true; + + if (proxyAutoConfigured && !proxyLocked) { + networkProxyPac.removeAttribute("disabled"); + } + else { + networkProxyPac.setAttribute("disabled", "true"); + } + + // The pref value 3 for network.proxy.type is unused to maintain + // backwards compatibility. Treat 3 equally to 0. See bug 115720. + var networkProxyStatus = [networkProxyNo, networkProxyManual, networkProxyPac, + networkProxyNo, networkProxyWpad, + networkProxySystem]; + var networkProxyType = Services.prefs.getIntPref("network.proxy.type", 0); + networkProxyStatus[networkProxyType].setAttribute("checked", "true"); +} + +function setProxyTypeUI() +{ + var panel = document.getElementById("offline-status"); + if (!panel) + return; + + var onlineTooltip = "onlineTooltip" + + Services.prefs.getIntPref("network.proxy.type", 0); + panel.setAttribute("tooltiptext", gUtilityBundle.getString(onlineTooltip)); +} + +function SetStringPref(aPref, aValue) +{ + try { + Services.prefs.setStringPref(aPref, aValue); + } catch (e) {} +} + +function GetLocalizedStringPref(aPrefName, aDefaultValue) +{ + try { + return Services.prefs.getComplexValue(aPrefName, + Ci.nsIPrefLocalizedString).data; + } catch (e) { + Cu.reportError("Couldn't get " + aPrefName + " pref: " + e); + } + return aDefaultValue; +} + +function GetLocalFilePref(aName) +{ + try { + return Services.prefs.getComplexValue(aName, + Ci.nsIFile); + } catch (e) {} + return null; +} + +/** + * Returns the Desktop folder. + */ +function GetDesktopFolder() +{ + return Services.dirsvc.get("Desk", Ci.nsIFile); +} + +/** + * Returns the relevant nsIFile directory. + */ +function GetSpecialDirectory(aName) +{ + return Services.dirsvc.get(aName, Ci.nsIFile); +} + +function GetUrlbarHistoryFile() +{ + var profileDir = GetSpecialDirectory("ProfD"); + profileDir.append("urlbarhistory.sqlite"); + return profileDir; +} + +function setOfflineUI(offline) +{ + var broadcaster = document.getElementById("Communicator:WorkMode"); + var panel = document.getElementById("offline-status"); + if (!broadcaster || !panel) return; + + // Checking for a preference "network.online", if it's locked, disabling + // network icon and menu item + if (Services.prefs.prefIsLocked("network.online")) + broadcaster.setAttribute("disabled", "true"); + + if (offline) + { + broadcaster.setAttribute("offline", "true"); + broadcaster.setAttribute("checked", "true"); + panel.removeAttribute("context"); + panel.setAttribute("tooltiptext", gUtilityBundle.getString("offlineTooltip")); + } + else + { + broadcaster.removeAttribute("offline"); + broadcaster.removeAttribute("checked"); + panel.setAttribute("context", "networkProperties"); + setProxyTypeUI(); + } +} + +function getBrowserURL() { + + try { + var url = Services.prefs.getCharPref("browser.chromeURL"); + if (url) + return url; + } catch(e) { + } + return "chrome://navigator/content/navigator.xul"; +} + +function goPreferences(paneID) +{ + //check for an existing pref window and focus it; it's not application modal + var lastPrefWindow = Services.wm.getMostRecentWindow("mozilla:preferences"); + if (lastPrefWindow) + lastPrefWindow.focus(); + else + openDialog("chrome://communicator/content/pref/preferences.xul", + "PrefWindow", "non-private,chrome,titlebar,dialog=no,resizable", + paneID); +} + +function goToggleToolbar(id, elementID) +{ + var toolbar = document.getElementById(id); + if (!toolbar) + return; + + var type = toolbar.getAttribute("type"); + var toggleAttribute = type == "menubar" ? "autohide" : "hidden"; + var hidden = toolbar.getAttribute(toggleAttribute) == "true"; + var element = document.getElementById(elementID); + + toolbar.setAttribute(toggleAttribute, !hidden); + if (element) + element.setAttribute("checked", hidden) + + document.persist(id, toggleAttribute); + document.persist(elementID, "checked"); + + if (toolbar.hasAttribute("customindex")) + persistCustomToolbar(toolbar); + +} + +var gCustomizeSheet = false; + +function SuiteCustomizeToolbar(aMenuItem) +{ + let toolbar = aMenuItem.parentNode.triggerNode; + while (toolbar.localName != "toolbar") { + toolbar = toolbar.parentNode; + if (!toolbar) + return false; + } + return goCustomizeToolbar(toolbar.toolbox); +} + +function goCustomizeToolbar(toolbox) +{ + /* If the toolbox has a method "customizeInit" then call it first. + The optional "customizeDone" method will be invoked by the callback + from the Customize Window so we don't need to take care of that */ + if ("customizeInit" in toolbox) + toolbox.customizeInit(); + + var customizeURL = "chrome://communicator/content/customizeToolbar.xul"; + + gCustomizeSheet = + Services.prefs.getBoolPref("toolbar.customization.usesheet", false); + + if (gCustomizeSheet) { + var sheetFrame = document.getElementById("customizeToolbarSheetIFrame"); + var panel = document.getElementById("customizeToolbarSheetPopup"); + sheetFrame.hidden = false; + sheetFrame.toolbox = toolbox; + sheetFrame.panel = panel; + + // The document might not have been loaded yet, if this is the first time. + // If it is already loaded, reload it so that the onload initialization + // code re-runs. + if (sheetFrame.getAttribute("src") == customizeURL) + sheetFrame.contentWindow.location.reload(); + else + sheetFrame.setAttribute("src", customizeURL); + + // Open the panel, but make it invisible until the iframe has loaded so + // that the user doesn't see a white flash. + panel.style.visibility = "hidden"; + toolbox.addEventListener("beforecustomization", function toolboxBeforeCustom() { + toolbox.removeEventListener("beforecustomization", toolboxBeforeCustom); + panel.style.removeProperty("visibility"); + }); + panel.openPopup(toolbox, "after_start", 0, 0); + return sheetFrame.contentWindow; + } + else { + return window.openDialog(customizeURL, + "", + "chrome,all,dependent", + toolbox); + } +} + +function onViewToolbarsPopupShowing(aEvent, aInsertPoint) +{ + var popup = aEvent.target; + if (popup != aEvent.currentTarget) + return; + + // Empty the menu + var deadItems = popup.getElementsByAttribute("toolbarid", "*"); + for (let i = deadItems.length - 1; i >= 0; --i) + deadItems[i].remove(); + + // Thunderbird/Lightning function signature is: + // onViewToolbarsPopupShowing(aEvent, toolboxIds, aInsertPoint) + // where toolboxIds is either a string or an array of strings. + var firstMenuItem = aInsertPoint instanceof XULElement ? aInsertPoint + : popup.firstChild; + + var toolbar = document.popupNode || popup; + while (toolbar.localName != "toolbar") + toolbar = toolbar.parentNode; + var toolbox = toolbar.toolbox; + var externalToolbars = Array.from(toolbox.externalToolbars) + .filter(function(toolbar) { + return toolbar.hasAttribute("toolbarname")}); + var toolbars = Array.from(toolbox.getElementsByAttribute("toolbarname", "*")) + .filter(function(toolbar) { + return !toolbar.hasAttribute("hideinmenu")}); + toolbars = toolbars.concat(externalToolbars); + var menusep = document.getElementById("toolbarmode-sep"); + + var menubar = toolbox.getElementsByAttribute("type", "menubar").item(0); + if (!menubar || !toolbars.length) { + if (menusep) + menusep.hidden = true; + return; + } + if (menusep) + menusep.hidden = false; + + toolbars.forEach(function(bar) { + let type = bar.getAttribute("type"); + let toggleAttribute = type == "menubar" ? "autohide" : "hidden"; + let isHidden = bar.getAttribute(toggleAttribute) == "true"; + let menuItem = document.createElement("menuitem"); + menuItem.setAttribute("id", "toggle_" + bar.id); + menuItem.setAttribute("toolbarid", bar.id); + menuItem.setAttribute("type", "checkbox"); + menuItem.setAttribute("label", bar.getAttribute("toolbarname")); + menuItem.setAttribute("accesskey", bar.getAttribute("accesskey")); + menuItem.setAttribute("checked", !isHidden); + popup.insertBefore(menuItem, firstMenuItem); + }); +} + +function onToolbarModePopupShowing(aEvent) +{ + var popup = aEvent.target; + + var toolbar = document.popupNode; + while (toolbar.localName != "toolbar") + toolbar = toolbar.parentNode; + var toolbox = toolbar.toolbox; + + var mode = toolbar.getAttribute("mode") || "full"; + var modePopup = document.getElementById("toolbarModePopup"); + var radio = modePopup.getElementsByAttribute("value", mode); + radio[0].setAttribute("checked", "true"); + + var small = toolbar.getAttribute("iconsize") == "small"; + var smallicons = document.getElementById("toolbarmode-smallicons"); + smallicons.setAttribute("checked", small); + smallicons.setAttribute("disabled", mode == "text"); + + var end = toolbar.getAttribute("labelalign") == "end"; + var labelalign = document.getElementById("toolbarmode-labelalign"); + labelalign.setAttribute("checked", end); + labelalign.setAttribute("disabled", mode != "full"); + + var custommode = (toolbar.getAttribute("mode") || "full") != + (toolbar.getAttribute("defaultmode") || + toolbox.getAttribute("mode") || + "full"); + var customicon = (toolbar.getAttribute("iconsize") || "large") != + (toolbar.getAttribute("defaulticonsize") || + toolbox.getAttribute("iconsize") || + "large"); + var customalign = (toolbar.getAttribute("labelalign") || "bottom") != + (toolbar.getAttribute("defaultlabelalign") || + toolbox.getAttribute("labelalign") || + "bottom"); + var custom = custommode || customicon || customalign || + toolbar.hasAttribute("ignoremodepref"); + + var defmode = document.getElementById("toolbarmode-default"); + defmode.setAttribute("checked", !custom); + defmode.setAttribute("disabled", !custom); + + var command = document.getElementById("cmd_CustomizeToolbars"); + var menuitem = document.getElementById("customize_toolbars"); + menuitem.hidden = !command; + menuitem.previousSibling.hidden = !command; +} + +function onViewToolbarCommand(aEvent) +{ + var toolbar = aEvent.originalTarget.getAttribute("toolbarid"); + if (toolbar) + goToggleToolbar(toolbar); +} + +function goSetToolbarState(aEvent) +{ + aEvent.stopPropagation(); + var toolbar = document.popupNode; + while (toolbar.localName != "toolbar") + toolbar = toolbar.parentNode; + var toolbox = toolbar.parentNode; + + var target = aEvent.originalTarget; + var mode = target.value; + var radiogroup = target.getAttribute("name"); + var primary = /toolbar-primary/.test(toolbar.getAttribute("class")); + + switch (mode) { + case "smallicons": + var size = target.getAttribute("checked") == "true" ? "small" : "large"; + toolbar.setAttribute("iconsize", size); + break; + + case "end": + var align = target.getAttribute("checked") == "true" ? "end" : "bottom"; + toolbar.setAttribute("labelalign", align); + break; + + case "default": + toolbar.setAttribute("mode", toolbar.getAttribute("defaultmode") || + toolbox.getAttribute("mode")); + toolbar.setAttribute("iconsize", toolbar.getAttribute("defaulticonsize") || + toolbox.getAttribute("iconsize")); + toolbar.setAttribute("labelalign", toolbar.getAttribute("defaultlabelalign") || + toolbox.getAttribute("labelalign")); + if (primary) + toolbar.removeAttribute("ignoremodepref"); + break; + + default: + toolbar.setAttribute("mode", mode); + if (primary) + toolbar.setAttribute("ignoremodepref", "true"); + break; + } + document.persist(toolbar.id, "mode"); + document.persist(toolbar.id, "iconsize"); + document.persist(toolbar.id, "labelalign"); + if (primary) + document.persist(toolbar.id, "ignoremodepref"); + if (toolbar.hasAttribute("customindex")) + persistCustomToolbar(toolbar); +} + +function persistCustomToolbar(toolbar) +{ + var toolbox = toolbar.parentNode; + var name = toolbar.getAttribute("toolbarname").replace(" ", "_"); + var attrs = ["mode", "iconsize", "labelalign", "hidden"]; + for (let i = 0; i < attrs.length; i++) { + let value = toolbar.getAttribute(attrs[i]); + let attr = name + attrs[i]; + toolbox.toolbarset.setAttribute(attr, value); + document.persist(toolbox.toolbarset.id, attr); + } +} + +/* Common Customize Toolbar code */ + +function toolboxCustomizeInit(menubarID) +{ + // Disable the toolbar context menu items + var menubar = document.getElementById(menubarID); + for (let i = 0; i < menubar.childNodes.length; ++i) { + let item = menubar.childNodes[i]; + if (item.getAttribute("disabled") != "true") { + item.setAttribute("disabled", "true"); + item.setAttribute("saved-disabled", "false"); + } + } + + var cmd = document.getElementById("cmd_CustomizeToolbars"); + cmd.setAttribute("disabled", "true"); +} + +function toolboxCustomizeDone(menubarID, toolbox, aToolboxChanged) +{ + if (gCustomizeSheet) { + document.getElementById("customizeToolbarSheetIFrame").hidden = true; + document.getElementById("customizeToolbarSheetPopup").hidePopup(); + if (content) + content.focus(); + else + window.focus(); + } + + // Re-enable parts of the UI we disabled during the dialog + var menubar = document.getElementById(menubarID); + for (let i = 0; i < menubar.childNodes.length; ++i) { + let item = menubar.childNodes[i]; + if (item.hasAttribute("saved-disabled")) { + item.removeAttribute("disabled"); + item.removeAttribute("saved-disabled"); + } + } + + var cmd = document.getElementById("cmd_CustomizeToolbars"); + cmd.removeAttribute("disabled"); + + var toolbars = toolbox.getElementsByAttribute("customindex", "*"); + for (let i = 0; i < toolbars.length; ++i) { + persistCustomToolbar(toolbars[i]); + } +} + +function toolboxCustomizeChange(toolbox, event) +{ + if (event != "reset") + return; + var toolbars = toolbox.getElementsByAttribute("toolbarname", "*"); + for (let i = 0; i < toolbars.length; ++i) { + let toolbar = toolbars[i]; + toolbar.setAttribute("labelalign", + toolbar.getAttribute("defaultlabelalign") || + toolbox.getAttribute("labelalign")); + document.persist(toolbar.id, "labelalign"); + let primary = /toolbar-primary/.test(toolbar.getAttribute("class")); + if (primary) { + toolbar.removeAttribute("ignoremodepref"); + document.persist(toolbar.id, "ignoremodepref"); + } + } +} + +function goClickThrobber(urlPref, aEvent) +{ + var url = GetLocalizedStringPref(urlPref); + if (url) + openUILinkIn(url, whereToOpenLink(aEvent, false, true, true)); +} + +function getTopWin(skipPopups) { + // If this is called in a browser window, use that window regardless of + // whether it's the frontmost window, since commands can be executed in + // background windows (bug 626148). + if (top.document.documentElement.getAttribute("windowtype") == "navigator:browser" && + (!skipPopups || top.toolbar.visible)) + return top; + + let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window); + return RecentWindow.getMostRecentBrowserWindow({private: isPrivate, + allowPopups: !skipPopups}); +} + +function isRestricted( url ) +{ + try { + let uri = Services.uriFixup + .createFixupURI(url, Ci.nsIURIFixup.FIXUP_FLAG_NONE); + const URI_INHERITS_SECURITY_CONTEXT = + Ci.nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT; + return Services.netUtils + .URIChainHasFlags(uri, URI_INHERITS_SECURITY_CONTEXT); + } catch (e) { + return false; + } +} + +function goAbout(aProtocol) +{ + var target; + var url = "about:" + (aProtocol || ""); + var defaultAboutState = Services.prefs.getIntPref("browser.link.open_external"); + + switch (defaultAboutState) { + case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW: + target = "window"; + break; + case Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW: + target = "current"; + break; + default: + target = "tabfocused"; + } + openUILinkIn(url, target); +} + +function goTroubleshootingPage() +{ + goAbout("support"); +} + +function goReleaseNotes() +{ + // get release notes URL from prefs + try { + openUILink(Services.urlFormatter.formatURLPref("app.releaseNotesURL")); + } + catch (ex) { dump(ex); } +} + +function openDictionaryList() +{ + try { + openAsExternal(Services.urlFormatter.formatURLPref("spellchecker.dictionaries.download.url")); + } + catch (ex) { + dump(ex); + } +} + +// Prompt user to restart the browser in safe mode +function safeModeRestart() +{ + // prompt the user to confirm + var promptTitle = gUtilityBundle.getString("safeModeRestartPromptTitle"); + var promptMessage = gUtilityBundle.getString("safeModeRestartPromptMessage"); + var restartText = gUtilityBundle.getString("safeModeRestartButton"); + var checkboxText = gUtilityBundle.getString("safeModeRestartCheckbox"); + var checkbox = { value: true }; + var buttonFlags = (Services.prompt.BUTTON_POS_0 * + Services.prompt.BUTTON_TITLE_IS_STRING) + + (Services.prompt.BUTTON_POS_1 * + Services.prompt.BUTTON_TITLE_CANCEL) + + Services.prompt.BUTTON_POS_0_DEFAULT; + + var rv = Services.prompt.confirmEx(window, promptTitle, promptMessage, + buttonFlags, restartText, null, null, + checkboxText, checkbox); + if (rv == 0) { + if (checkbox.value) + Cc["@mozilla.org/process/environment;1"] + .getService(Ci.nsIEnvironment) + .set("MOZ_SAFE_MODE_RESTART", "1"); + BrowserUtils.restartApplication(); + } +} + +function checkForUpdates() +{ + var um = Cc["@mozilla.org/updates/update-manager;1"] + .getService(Ci.nsIUpdateManager); + var prompter = Cc["@mozilla.org/updates/update-prompt;1"] + .createInstance(Ci.nsIUpdatePrompt); + + // If there's an update ready to be applied, show the "Update Downloaded" + // UI instead and let the user know they have to restart the browser for + // the changes to be applied. + if (um.activeUpdate && um.activeUpdate.state == "pending") + prompter.showUpdateDownloaded(um.activeUpdate); + else + prompter.checkForUpdates(); +} + +function updateCheckUpdatesItem() +{ + var hasUpdater = "nsIApplicationUpdateService" in Ci; + var checkForUpdates = document.getElementById("checkForUpdates"); + + if (!hasUpdater) + { + var updateSeparator = document.getElementById("updateSeparator"); + + checkForUpdates.hidden = true; + updateSeparator.hidden = true; + return; + } + + var updates = Cc["@mozilla.org/updates/update-service;1"] + .getService(Ci.nsIApplicationUpdateService); + var um = Cc["@mozilla.org/updates/update-manager;1"] + .getService(Ci.nsIUpdateManager); + + // Disable the UI if the update enabled pref has been locked by the + // administrator or if we cannot update for some other reason. + var canCheckForUpdates = updates.canCheckForUpdates; + checkForUpdates.setAttribute("disabled", !canCheckForUpdates); + + if (!canCheckForUpdates) + return; + + // By default, show "Check for Updates..." + var key = "default"; + if (um.activeUpdate) { + switch (um.activeUpdate.state) { + case "downloading": + // If we're downloading an update at present, show the text: + // "Downloading SeaMonkey x.x..." otherwise we're paused, and show + // "Resume Downloading SeaMonkey x.x..." + key = updates.isDownloading ? "downloading" : "resume"; + break; + case "pending": + // If we're waiting for the user to restart, show: "Apply Downloaded + // Updates Now..." + key = "pending"; + break; + } + } + + // If there's an active update, substitute its name into the label + // we show for this item, otherwise display a generic label. + if (um.activeUpdate && um.activeUpdate.name) + checkForUpdates.label = gUtilityBundle.getFormattedString("updatesItem_" + key, + [um.activeUpdate.name]); + else + checkForUpdates.label = gUtilityBundle.getString("updatesItem_" + key + "Fallback"); + + checkForUpdates.accessKey = gUtilityBundle.getString("updatesItem_" + key + "AccessKey"); + + if (um.activeUpdate && updates.isDownloading) + checkForUpdates.setAttribute("loading", "true"); + else + checkForUpdates.removeAttribute("loading"); +} + +// update menu items that rely on focus +function goUpdateGlobalEditMenuItems() +{ + goUpdateCommand('cmd_undo'); + goUpdateCommand('cmd_redo'); + goUpdateCommand('cmd_cut'); + goUpdateCommand('cmd_copy'); + goUpdateCommand('cmd_paste'); + goUpdateCommand('cmd_selectAll'); + goUpdateCommand('cmd_delete'); + if (gShowBiDi) + goUpdateCommand('cmd_switchTextDirection'); +} + +// update menu items that rely on the current selection +function goUpdateSelectEditMenuItems() +{ + goUpdateCommand('cmd_cut'); + goUpdateCommand('cmd_copy'); + goUpdateCommand('cmd_delete'); + goUpdateCommand('cmd_selectAll'); +} + +// update menu items that relate to undo/redo +function goUpdateUndoEditMenuItems() +{ + goUpdateCommand('cmd_undo'); + goUpdateCommand('cmd_redo'); +} + +// update menu items that depend on clipboard contents +function goUpdatePasteMenuItems() +{ + goUpdateCommand('cmd_paste'); +} + +// update Find As You Type menu items, they rely on focus +function goUpdateFindTypeMenuItems() +{ + goUpdateCommand('cmd_findTypeText'); + goUpdateCommand('cmd_findTypeLinks'); +} + +// Gather all descendent text under given document node. +function gatherTextUnder(root) +{ + var text = ""; + var node = root.firstChild; + var depth = 1; + while ( node && depth > 0 ) { + // See if this node is text. + if ( node.nodeType == Node.TEXT_NODE ) { + // Add this text to our collection. + text += " " + node.data; + } else if ( node instanceof HTMLImageElement ) { + // If it has an alt= attribute, add that. + var altText = node.getAttribute( "alt" ); + if ( altText && altText != "" ) { + text += " " + altText; + } + } + // Find next node to test. + // First, see if this node has children. + if ( node.hasChildNodes() ) { + // Go to first child. + node = node.firstChild; + depth++; + } else { + // No children, try next sibling. + if ( node.nextSibling ) { + node = node.nextSibling; + } else { + // Last resort is a sibling of an ancestor. + while ( node && depth > 0 ) { + node = node.parentNode; + depth--; + if ( node.nextSibling ) { + node = node.nextSibling; + break; + } + } + } + } + } + + // Strip leading and trailing whitespaces, + // then compress remaining whitespaces. + return text.trim().replace(/\s+/g, " "); +} + +var offlineObserver = { + observe: function(subject, topic, state) { + // sanity checks + if (topic != "network:offline-status-changed") return; + setOfflineUI(state == "offline"); + } +} + +var proxyTypeObserver = { + observe: function(subject, topic, state) { + // sanity checks + if (state == "network.proxy.type" && !Services.io.offline) + setProxyTypeUI(); + } +} + +function utilityOnLoad(aEvent) +{ + gUtilityBundle = document.getElementById("bundle_utilityOverlay"); + + var broadcaster = document.getElementById("Communicator:WorkMode"); + if (!broadcaster) return; + + Services.obs.addObserver(offlineObserver, "network:offline-status-changed"); + // make sure we remove this observer later + Services.prefs.addObserver("network.proxy.type", proxyTypeObserver); + + addEventListener("unload", utilityOnUnload, false); + + // set the initial state + setOfflineUI(Services.io.offline); + + // Check for system proxy settings class and show menuitem if present + if ("@mozilla.org/system-proxy-settings;1" in Cc && + document.getElementById("network-proxy-system")) + document.getElementById("network-proxy-system").hidden = false; +} + +function utilityOnUnload(aEvent) +{ + Services.obs.removeObserver(offlineObserver, "network:offline-status-changed"); + Services.prefs.removeObserver("network.proxy.type", proxyTypeObserver); +} + +addEventListener("load", utilityOnLoad, false); + +/** + * example use: + * suggestUniqueFileName("testname", ".txt", ["testname.txt", "testname(2).txt"]) + * returns "testname(3).txt" + * does not check file system for existing files + * + * @param aBaseName base name for generating unique filenames. + * + * @param aExtension extension name to use for the generated filename. + * + * @param aExistingNames array of names in use. + * + * @return suggested filename as a string. + */ +function suggestUniqueFileName(aBaseName, aExtension, aExistingNames) +{ + var suffix = 1; + aBaseName = validateFileName(aBaseName); + var suggestion = aBaseName + aExtension; + while (aExistingNames.includes(suggestion)) + { + suffix++; + suggestion = aBaseName + "(" + suffix + ")" + aExtension; + } + return suggestion; +} + +function focusElement(aElement) +{ + if (isElementVisible(aElement)) + aElement.focus(); +} + +function isElementVisible(aElement) +{ + if (!aElement) + return false; + + // If aElement or a direct or indirect parent is hidden or collapsed, + // height, width or both will be 0. + var bo = aElement.boxObject; + return (bo.height > 0 && bo.width > 0); +} + +function makeURLAbsolute(aBase, aUrl, aCharset) +{ + // Construct nsIURL. + return Services.io.newURI(aUrl, aCharset, + Services.io.newURI(aBase, aCharset)).spec; +} + +/** + * whereToLoadExternalLink: Returns values for opening a new external link. + * + * @returns (object[]} an array of objects with the following structure: + * - (string) where location where to open the link. + * - (bool) loadInBackground load url in background. + * - (bool) Focus browser after load. + */ +function whereToLoadExternalLink() { + let openParms = { + where: null, + loadInBackground: false, + avoidBrowserFocus: false, + } + + switch (Services.prefs.getIntPref("browser.link.open_external")) { + case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW: + openParms.where = "window"; + break; + case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB: + openParms.where = "tab"; + break; + case Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW: + openParms.where = "current"; + break; + default: + console.log("Check pref browser.link.open_external"); + openParms.where = "current"; + } + openParms.loadInBackground = + Services.prefs.getBoolPref("browser.tabs.loadDivertedInBackground"); + + openParms.avoidBrowserFocus = + Services.prefs.getBoolPref("browser.tabs.avoidBrowserFocus"); + + return openParms; +} + +function openAsExternal(aURL) { + let openParms = whereToLoadExternalLink(); + + openNewTabWindowOrExistingWith(aURL, openParms.where, null, + openParms.loadInBackground); +} + +/** + * openNewTabWith: opens a new tab with the given URL. + * openNewWindowWith: opens a new window with the given URL. + * openNewPrivateWith: opens a private window with the given URL. + * + * @param aURL + * The URL to open (as a string). + * @param aDocument + * The document from which the URL came, or null. This is used to set + * the referrer header and to do a security check of whether the + * document is allowed to reference the URL. If null, there will be no + * referrer header and no security check. + * @param aPostData + * Form POST data, or null. + * @param aEvent + * The triggering event (for the purpose of determining whether to open + * in the background), or null. + * @param aAllowThirdPartyFixup + * If true, then we allow the URL text to be sent to third party + * services (e.g., Google's I Feel Lucky) for interpretation. This + * parameter may be undefined in which case it is treated as false. + * @param [optional] aReferrer + * If aDocument is null, then this will be used as the referrer. + * There will be no security check. + * @param [optional] aReferrerPolicy + * Referrer policy - Ci.nsIHttpChannel.REFERRER_POLICY_*. + */ +function openNewPrivateWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup, + aReferrer, aReferrerPolicy) { + return openNewTabWindowOrExistingWith(aURL, "private", aDocument, null, + aPostData, aAllowThirdPartyFixup, + aReferrer, aReferrerPolicy); +} + +function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup, + aReferrer, aReferrerPolicy) { + return openNewTabWindowOrExistingWith(aURL, "window", aDocument, null, + aPostData, aAllowThirdPartyFixup, + aReferrer, aReferrerPolicy); +} + +function openNewTabWith(aURL, aDocument, aPostData, aEvent, + aAllowThirdPartyFixup, aReferrer, aReferrerPolicy) { + let where = aEvent && aEvent.shiftKey ? "tabshifted" : "tab"; + return openNewTabWindowOrExistingWith(aURL, where, aDocument, null, + aPostData, aAllowThirdPartyFixup, + aReferrer, aReferrerPolicy); +} + +function openNewTabWindowOrExistingWith(aURL, aWhere, aDocument, + aLoadInBackground, aPostData, + aAllowThirdPartyFixup, aReferrer, + aReferrerPolicy) { + // Make sure we are allowed to open this url + if (aDocument) + urlSecurityCheck(aURL, aDocument.nodePrincipal); + + // Where appropriate we want to pass the charset of the + // current document over to a new tab / window. + var originCharset = null; + if (aWhere != "current") { + originCharset = aDocument && aDocument.characterSet; + if (!originCharset && + document.documentElement.getAttribute("windowtype") == "navigator:browser") + originCharset = window.content.document.characterSet; + } + + var isPrivate = false; + if (aWhere == "private") { + aWhere = "window"; + isPrivate = true; + } + var referrerURI = aDocument ? aDocument.documentURIObject : aReferrer; + return openLinkIn(aURL, aWhere, + { charset: originCharset, + postData: aPostData, + inBackground: aLoadInBackground, + allowThirdPartyFixup: aAllowThirdPartyFixup, + referrerURI: referrerURI, + referrerPolicy: aReferrerPolicy, + private: isPrivate, }); +} + +/** + * Handle command events bubbling up from error page content + * called from oncommand by <browser>s that support error pages + */ +function BrowserOnCommand(event) +{ + // Don't trust synthetic events + if (!event.isTrusted) + return; + + const ot = event.originalTarget; + const ownerDoc = ot.ownerDocument; + const docURI = ownerDoc.documentURI; + const buttonID = ot.getAttribute("anonid"); + + // If the event came from an ssl error page, it is probably either the "Add + // Exception" or "Get Me Out Of Here" button + if (docURI.startsWith("about:certerror?")) { + if (buttonID == "exceptionDialogButton") { + let docshell = ownerDoc.defaultView + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + let securityInfo = docshell.failedChannel.securityInfo; + let sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider) + .SSLStatus; + + let params = { exceptionAdded : false, sslStatus : sslStatus }; + + switch (Services.prefs.getIntPref("browser.ssl_override_behavior", 2)) { + case 2 : // Pre-fetch & pre-populate. + params.prefetchCert = true; + // Fall through. + case 1 : // Pre-populate. + params.location = ownerDoc.location.href; + } + + window.openDialog('chrome://pippki/content/exceptionDialog.xul', + '', 'chrome,centerscreen,modal', params); + + // If the user added the exception cert, attempt to reload the page + if (params.exceptionAdded) + ownerDoc.location.reload(); + } + else if (buttonID == "getMeOutOfHereButton") { + // Redirect them to a known-functioning page, default start page + getMeOutOfHere(); + } + } + else if (docURI.startsWith("about:blocked")) { + // The event came from a button on a malware/phishing block page + // First check whether the reason, so that we can + // use the right strings/links + let reason = "phishing"; + + if (/e=malwareBlocked/.test(docURI)) { + reason = "malware"; + } else if (/e=unwantedBlocked/.test(docURI)) { + reason = "unwanted"; + } else if (/e=harmfulBlocked/.test(docURI)) { + reason = "harmful"; + } + + let docShell = ownerDoc.defaultView + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + let blockedInfo = {}; + if (docShell.failedChannel) { + let classifiedChannel = docShell.failedChannel. + QueryInterface(Ci.nsIClassifiedChannel); + if (classifiedChannel) { + let httpChannel = docShell.failedChannel.QueryInterface(Ci.nsIHttpChannel); + + let reportUri = httpChannel.URI.clone(); + + // Remove the query to avoid leaking sensitive data + if (reportUri instanceof Ci.nsIURL) { + reportUri = reportUri.mutate().setQuery("").finalize(); + } + + blockedInfo = { list: classifiedChannel.matchedList, + provider: classifiedChannel.matchedProvider, + uri: reportUri.asciiSpec }; + } + } + + switch (buttonID) { + case "getMeOutOfHereButton": + getMeOutOfHere(); + break; + case "reportButton": + // This is the "Why is this site blocked" button. We redirect + // to the generic page describing phishing/malware protection. + try { + loadURI(Services.urlFormatter.formatURLPref("browser.safebrowsing.warning.infoURL")); + } catch (e) { + Cu.reportError("Couldn't get phishing info URL: " + e); + } + break; + case "ignoreWarningButton": + if (Services.prefs.getBoolPref("browser.safebrowsing.allowOverride")) { + getBrowser().getNotificationBox().ignoreSafeBrowsingWarning(reason, blockedInfo); + } + break; + } + } +} + +/** + * Re-direct the browser to a known-safe page. This function is + * used when, for example, the user browses to a known malware page + * and is presented with about:blocked. The "Get me out of here!" + * button should take the user to the default start page so that even + * when their own homepage is infected, we can get them somewhere safe. + */ +function getMeOutOfHere() { + // Get the start page from the *default* pref branch, not the user's + var prefs = Services.prefs.getDefaultBranch(null); + var url = "about:blank"; + try { + url = prefs.getComplexValue("browser.startup.homepage", + Ci.nsIPrefLocalizedString).data; + } catch(e) {} + loadURI(url); +} + +function popupNotificationMenuShowing(event) +{ + var notificationbox = document.popupNode.parentNode.control; + var uri = notificationbox.activeBrowser.currentURI; + var allowPopupsForSite = document.getElementById("allowPopupsForSite"); + allowPopupsForSite.notificationbox = notificationbox; + var showPopupManager = document.getElementById("showPopupManager"); + + // Only offer this menu item for the top window. + // See bug 280536 for problems with frames and iframes. + try { + // uri.host generates an exception on nsISimpleURIs. + var allowString = gUtilityBundle.getFormattedString("popupAllow", [uri.host || uri.spec]); + allowPopupsForSite.setAttribute("label", allowString); + showPopupManager.hostport = uri.hostPort; + allowPopupsForSite.hidden = gPrivate; + } catch (ex) { + allowPopupsForSite.hidden = true; + showPopupManager.hostport = ""; + } + + var separator = document.getElementById("popupNotificationMenuSeparator"); + separator.hidden = !createShowPopupsMenu(event.target, notificationbox.activeBrowser); +} + +function RemovePopupsItems(parent) +{ + while (parent.lastChild && parent.lastChild.hasAttribute("popupReportIndex")) + parent.lastChild.remove(); +} + +function createShowPopupsMenu(parent, browser) +{ + if (!browser) + return false; + + if (!browser.blockedPopups || + browser.blockedPopups.count == 0) + return false; + + parent.browser = browser; + + browser.retrieveListOfBlockedPopups().then(blockedPopups => { + + for (var i = 0; i < blockedPopups.length; i++) { + + let blockedPopup = blockedPopups[i]; + // popupWindowURI will be null if the file picker popup is blocked. + if (!blockedPopup.popupWindowURIspec) + continue; + + let str = gUtilityBundle.getFormattedString("popupMenuShow", [blockedPopup.popupWindowURIspec]); + // Check for duplicates in the blockedPopups list and reuse the old menuitem. + let menuitem = parent.getElementsByAttribute("label", str).item(0); + if (!menuitem) { + menuitem = document.createElement("menuitem"); + menuitem.setAttribute("label", str); + } + menuitem.setAttribute("popupReportIndex", i); + parent.appendChild(menuitem); + } + }, null); + + return parent.getElementsByAttribute("popupReportIndex", "*").item(0) != null; +} + +function popupBlockerMenuCommand(target) +{ + if (target.hasAttribute("popupReportIndex")) + target.parentNode.browser.unblockPopup(target.getAttribute("popupReportIndex")); +} + +function hostUrl() +{ + var url = ""; + try { + url = getBrowser().currentURI.scheme + "://" + getBrowser().currentURI.hostPort; + } catch (e) {} + return url; +} + +function disablePopupBlockerNotifications() +{ + Services.prefs.setBoolPref("privacy.popups.showBrowserMessage", false); +} + +// Used as an onclick handler for UI elements with link-like behavior. +// e.g. onclick="checkForMiddleClick(this, event);" +function checkForMiddleClick(node, event) { + // We should be using the disabled property here instead of the attribute, + // but some elements that this function is used with don't support it (e.g. + // menuitem). + if (node.getAttribute("disabled") == "true") + return; // Do nothing + + if (event.button == 1) { + /* Execute the node's oncommand or command. + * + * XXX: we should use node.oncommand(event) once bug 246720 is fixed. + */ + var target = node.hasAttribute("oncommand") ? node : + node.ownerDocument.getElementById(node.getAttribute("command")); + var fn = new Function("event", target.getAttribute("oncommand")); + fn.call(target, event); + + // If the middle-click was on part of a menu, close the menu. + // (Menus close automatically with left-click but not with middle-click.) + closeMenus(event.target); + } +} + +// Closes all popups that are ancestors of the node. +function closeMenus(node) { + if ("tagName" in node) { + if (node.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + && (node.tagName == "menupopup" || node.tagName == "popup")) + node.hidePopup(); + + closeMenus(node.parentNode); + } +} + +/** + * Toggle a splitter to show or hide some piece of UI (e.g. the message preview + * pane). + * + * @param aSplitterId the splitter that should be toggled + */ +function togglePaneSplitter(aSplitterId) +{ + var splitter = document.getElementById(aSplitterId); + if (splitter.getAttribute("state") == "collapsed") + splitter.setAttribute("state", "open"); + else + splitter.setAttribute("state", "collapsed"); +} + +/* openUILink handles clicks on UI elements that cause URLs to load. + * + * As the third argument, you may pass an object with the same properties as + * accepted by openUILinkIn, plus "ignoreButton" and "ignoreSave". + * + * Note: Firefox uses aIgnoreAlt while SeaMonkey uses aIgnoreSave because in + * SeaMonkey, Save can be Alt or Shift depending on ui.key.saveLink.shift. + * + * For API compatibility with Firefox the object version uses params.ignoreAlt + * although for SeaMonkey it is effectively ignoreSave. + */ +function openUILink(url, aEvent, aIgnoreButton, aIgnoreSave, + aAllowThirdPartyFixup, aPostData, aReferrerURI) { + var params; + if (aIgnoreButton && typeof aIgnoreButton == "object") { + params = aIgnoreButton; + + // don't forward "ignoreButton" and "ignoreSave" to openUILinkIn. + aIgnoreButton = params.ignoreButton; + aIgnoreSave = params.ignoreAlt; + delete params.ignoreButton; + delete params.ignoreAlt; + } + else { + params = {allowThirdPartyFixup: aAllowThirdPartyFixup, + postData: aPostData, + referrerURI: aReferrerURI, + referrerPolicy: Ci.nsIHttpChannel.REFERRER_POLICY_UNSET, + initiatingDoc: aEvent ? aEvent.target.ownerDocument : document,} + } + + var where = whereToOpenLink(aEvent, aIgnoreButton, aIgnoreSave); + return openUILinkIn(url, where, params); +} + +/* whereToOpenLink() looks at an event to decide where to open a link. + * + * The event may be a mouse event (click, double-click, middle-click) or keypress event (enter). + * + * The logic for modifiers is as following: + * If browser.tabs.opentabfor.middleclick is true, then Ctrl (or Meta) and middle-click + * open a new tab, depending on Shift, browser.tabs.loadInBackground, and + * ignoreBackground. + * Otherwise if middlemouse.openNewWindow is true, then Ctrl (or Meta) and middle-click + * open a new window. + * Otherwise if middle-click is pressed then nothing happens. + * Save is Alt or Shift depending on the ui.key.saveLink.shift preference. + * Otherwise if Alt, or Shift, or Ctrl (or Meta) is pressed then nothing happens. + * Otherwise the most recent browser is used for left clicks. + * + * Exceptions: + * - Alt is ignored for menu items selected using the keyboard so you don't accidentally save stuff. + * - Alt is hard to use in context menus, because pressing Alt closes the menu. + * - Alt can't be used on the bookmarks toolbar because Alt is used for "treat this as something draggable". + * - The button is ignored for the middle-click-paste-URL feature, since it's always a middle-click. + */ +function whereToOpenLink(e, ignoreButton, ignoreSave, ignoreBackground = false) +{ + // This method must treat a null event like a left click without modifier keys (i.e. + // e = { shiftKey:false, ctrlKey:false, metaKey:false, altKey:false, button:0 }) + // for compatibility purposes. + if (!e) + return "current"; + + var shift = e.shiftKey; + var ctrl = e.ctrlKey; + var meta = e.metaKey; + var alt = e.altKey && !ignoreSave; + + // ignoreButton allows "middle-click paste" to use function without always opening in a new window. + var middle = !ignoreButton && e.button == 1; + + // Don't do anything special with right-mouse clicks. They're probably clicks on context menu items. + + // On macOS ctrl is not evaluated. + var metaKey = AppConstants.platform == "macosx" ? meta : ctrl; + + if (metaKey || middle) { + if (Services.prefs.getBoolPref("browser.tabs.opentabfor.middleclick", true)) + return ignoreBackground ? "tabfocused" : shift ? "tabshifted" : "tab"; + if (Services.prefs.getBoolPref("middlemouse.openNewWindow", true)) + return "window"; + if (middle) + return null; + } + if (!ignoreSave) { + if (Services.prefs.getBoolPref("ui.key.saveLink.shift", true) ? shift : alt) + return "save"; + } + if (alt || shift || meta || ctrl) + return null; + + return "current"; +} + +/* openUILinkIn opens a URL in a place specified by the parameter |where|. + * + * |where| can be: + * "current" current tab (if there aren't any browser windows, then in a new window instead) + * "tab" new tab (if there aren't any browser windows, then in a new window instead) + * "tabshifted" same as "tab" but in background if default is to select new tabs, and vice versa + * "tabfocused" same as "tab" but explicitly focus new tab + * "private" private browsing window + * "window" new window + * "save" save to disk (with no filename hint!) + * + * aAllowThirdPartyFixup controls whether third party services such as Google's + * I'm Feeling Lucky are allowed to interpret this URL. This parameter may be + * undefined, which is treated as false. + * + * Instead of aAllowThirdPartyFixup, you may also pass an object with any of + * these properties: + * allowThirdPartyFixup (boolean) + * postData (nsIInputStream) + * referrerURI (nsIURI) + * relatedToCurrent (boolean) + * initiatingDoc (document) + * userContextId (unsigned int) + */ +function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI) { + var params; + + if (arguments.length == 3 && typeof arguments[2] == "object") { + params = aAllowThirdPartyFixup; + } else { + params = { + allowThirdPartyFixup: aAllowThirdPartyFixup, + postData: aPostData, + referrerURI: aReferrerURI, + referrerPolicy: Ci.nsIHttpChannel.REFERRER_POLICY_UNSET, + }; + } + + if (where == "private") { + where = "window"; + params.private = true; + } + + params.fromChrome = true; + + return openLinkIn(url, where, params); +} + +function openLinkIn(url, where, params) +{ + if (!where || !url) + return null; + + var aFromChrome = params.fromChrome; + var aAllowThirdPartyFixup = params.allowThirdPartyFixup; + var aPostData = params.postData; + var aCharset = params.charset; + var aReferrerURI = params.referrerURI; + var aReferrerPolicy = ("referrerPolicy" in params ? + params.referrerPolicy : Ci.nsIHttpChannel.REFERRER_POLICY_UNSET); + var aRelatedToCurrent = params.relatedToCurrent; + var aAllowMixedContent = params.allowMixedContent; + var aInBackground = params.inBackground; + var aAvoidBrowserFocus = params.avoidBrowserFocus; + var aDisallowInheritPrincipal = params.disallowInheritPrincipal; + var aInitiatingDoc = params.initiatingDoc ? params.initiatingDoc : document; + var aIsPrivate = params.private; + var aNoReferrer = params.noReferrer; + var aUserContextId = params.userContextId; + var aPrincipal = params.originPrincipal; + var aTriggeringPrincipal = params.triggeringPrincipal; + var aForceAboutBlankViewerInCurrent = + params.forceAboutBlankViewerInCurrent; + + if (where == "save") { + saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, + aInitiatingDoc); + return null; + } + + // Establish which window we'll load the link in. + var w = getTopWin(); + // We don't want to open tabs in popups, so try to find a non-popup window in + // that case. + if ((where == "tab" || where == "tabshifted") && w && !w.toolbar.visible) { + w = getTopWin(true); + aRelatedToCurrent = false; + } + + // Teach the principal about the right OA to use, e.g. in case when + // opening a link in a new private window, or in a new container tab. + // Please note we do not have to do that for SystemPrincipals and we + // can not do it for NullPrincipals since NullPrincipals are only + // identical if they actually are the same object (See Bug: 1346759) + function useOAForPrincipal(principal) { + if (principal && principal.isCodebasePrincipal) { + let attrs = { + userContextId: aUserContextId, + }; + return Services.scriptSecurityManager.createCodebasePrincipal(principal.URI, attrs); + } + return principal; + } + aPrincipal = useOAForPrincipal(aPrincipal); + aTriggeringPrincipal = useOAForPrincipal(aTriggeringPrincipal); + + if (!w || where == "window") { + let features = "chrome,dialog=no,all"; + if (aIsPrivate) { + features += ",private"; + // To prevent regular browsing data from leaking to private browsing + // sites, strip the referrer when opening a new private window. + aNoReferrer = true; + } + + // This propagates to window.arguments. + var sa = Cc["@mozilla.org/array;1"]. + createInstance(Ci.nsIMutableArray); + + var wuri = Cc["@mozilla.org/supports-string;1"]. + createInstance(Ci.nsISupportsString); + wuri.data = url; + + let charset = null; + if (aCharset) { + charset = Cc["@mozilla.org/supports-string;1"] + .createInstance(Ci.nsISupportsString); + charset.data = "charset=" + aCharset; + } + + var allowThirdPartyFixupSupports = Cc["@mozilla.org/supports-PRBool;1"]. + createInstance(Ci.nsISupportsPRBool); + allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup; + + var referrerURISupports = null; + if (aReferrerURI && !aNoReferrer) { + referrerURISupports = Cc["@mozilla.org/supports-string;1"]. + createInstance(Ci.nsISupportsString); + referrerURISupports.data = aReferrerURI.spec; + } + + var referrerPolicySupports = Cc["@mozilla.org/supports-PRUint32;1"]. + createInstance(Ci.nsISupportsPRUint32); + referrerPolicySupports.data = aReferrerPolicy; + + var userContextIdSupports = Cc["@mozilla.org/supports-PRUint32;1"]. + createInstance(Ci.nsISupportsPRUint32); + userContextIdSupports.data = aUserContextId; + + sa.appendElement(wuri); + sa.appendElement(charset); + sa.appendElement(referrerURISupports); + sa.appendElement(aPostData); + sa.appendElement(allowThirdPartyFixupSupports); + sa.appendElement(referrerPolicySupports); + sa.appendElement(userContextIdSupports); + sa.appendElement(aPrincipal); + sa.appendElement(aTriggeringPrincipal); + + const sourceWindow = (w || window); + Services.ww.openWindow(sourceWindow, getBrowserURL(), null, features, sa); + return; + } + + let loadInBackground = aInBackground; + if (loadInBackground == null) { + loadInBackground = + aFromChrome ? false : + Services.prefs.getBoolPref("browser.tabs.loadInBackground"); + } + + if (aAvoidBrowserFocus == null) { + aAvoidBrowserFocus = + Services.prefs.getBoolPref("browser.tabs.avoidBrowserFocus", false); + } + + // reuse the browser if its current tab is empty + if (isBrowserEmpty(w.getBrowser())) + where = "current"; + + switch (where) { + case "current": + let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; + + if (aAllowThirdPartyFixup) { + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; + } + if (aDisallowInheritPrincipal) { + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; + } + + if (aForceAboutBlankViewerInCurrent) { + w.gBrowser.selectedBrowser.createAboutBlankContentViewer(aPrincipal); + } + + w.getBrowser().loadURIWithFlags(url, { + triggeringPrincipal: aTriggeringPrincipal, + flags, + referrerURI: aNoReferrer ? null : aReferrerURI, + referrerPolicy: aReferrerPolicy, + postData: aPostData, + userContextId: aUserContextId + }); + if (!aAvoidBrowserFocus) { + w.content.focus(); + } + break; + + case "tabfocused": + // forces tab to be focused + loadInBackground = true; + // fall through + case "tabshifted": + loadInBackground = !loadInBackground; + // fall through + case "tab": + var browser = w.getBrowser(); + var tab = browser.addTab(url, { + referrerURI: aReferrerURI, + referrerPolicy: aReferrerPolicy, + charset: aCharset, + postData: aPostData, + ownerTab: loadInBackground ? null : browser.selectedTab, + allowThirdPartyFixup: aAllowThirdPartyFixup, + relatedToCurrent: aRelatedToCurrent, + allowMixedContent: aAllowMixedContent, + noReferrer: aNoReferrer, + userContextId: aUserContextId, + originPrincipal: aPrincipal, + triggeringPrincipal: aTriggeringPrincipal, + }); + if (!loadInBackground) { + browser.selectedTab = tab; + } + if (!aAvoidBrowserFocus) { + w.content.focus(); + } + + break; + } + + return w; +} + +// This opens the URLs contained in the given array in new tabs +// of the most recent window, creates a new window if necessary. +function openUILinkArrayIn(urlArray, where, allowThirdPartyFixup) +{ + if (!where || !urlArray.length) + return null; + + if (where == "save") { + for (var i = 0; i < urlArray.length; i++) + saveURL(urlArray[i], null, null, true, true, null, document); + return null; + } + + var w = getTopWin(); + + if (!w || where == "window") { + return window.openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", + urlArray.join("\n"), // Pretend that we're a home page group + null, null, null, allowThirdPartyFixup); + } + + var loadInBackground = + Services.prefs.getBoolPref("browser.tabs.loadInBackground"); + + var browser = w.getBrowser(); + switch (where) { + case "current": + w.loadURI(urlArray[0], null, null, allowThirdPartyFixup); + w.content.focus(); + break; + case "tabshifted": + loadInBackground = !loadInBackground; + // fall through + case "tab": + var tab = browser.addTab(urlArray[0], {allowThirdPartyFixup: allowThirdPartyFixup}); + if (!loadInBackground) { + browser.selectedTab = tab; + w.content.focus(); + } + } + var relatedToCurrent = where == "current"; + for (var i = 1; i < urlArray.length; i++) + browser.addTab(urlArray[i], {allowThirdPartyFixup: allowThirdPartyFixup, relatedToCurrent: relatedToCurrent}); + + return w; +} + +/** + * Switch to a tab that has a given URI, and focusses its browser window. + * If a matching tab is in this window, it will be switched to. Otherwise, other + * windows will be searched. + * + * @param aURI + * URI to search for + * @param aOpenNew + * True to open a new tab and switch to it, if no existing tab is found + * @param A callback to call when the tab is open, the tab's browser will be + * passed as an argument + * @return True if a tab was switched to (or opened), false otherwise + */ +function switchToTabHavingURI(aURI, aOpenNew, aCallback) { + function switchIfURIInWindow(aWindow) { + if (!aWindow.gBrowser) + return false; + let browsers = aWindow.gBrowser.browsers; + for (let i = 0; i < browsers.length; i++) { + let browser = browsers[i]; + if (browser.currentURI.equals(aURI)) { + // Focus the matching window & tab + aWindow.focus(); + aWindow.gBrowser.tabContainer.selectedIndex = i; + if (aCallback) + aCallback(browser); + return true; + } + } + return false; + } + + // This can be passed either nsIURI or a string. + if (!(aURI instanceof Ci.nsIURI)) + aURI = Services.io.newURI(aURI); + + // Prioritise this window. + if (switchIfURIInWindow(window)) + return true; + + let winEnum = Services.wm.getEnumerator("navigator:browser"); + while (winEnum.hasMoreElements()) { + let browserWin = winEnum.getNext(); + // Skip closed (but not yet destroyed) windows, + // and the current window (which was checked earlier). + if (browserWin.closed || browserWin == window) + continue; + if (switchIfURIInWindow(browserWin)) + return true; + } + + // No opened tab has that url. + if (aOpenNew) { + let browserWin = openUILinkIn(aURI.spec, "tabfocused"); + if (aCallback) { + browserWin.addEventListener("pageshow", function browserWinPageShow(event) { + if (event.target.location.href != aURI.spec) + return; + browserWin.removeEventListener("pageshow", browserWinPageShow, true); + aCallback(browserWin.getBrowser().selectedBrowser); + }, true); + } + return true; + } + + return false; +} + +// Determines if a browser is "empty" +function isBrowserEmpty(aBrowser) { + return aBrowser.sessionHistory.count < 2 && + aBrowser.currentURI.spec == "about:blank" && + !aBrowser.contentDocument.body.hasChildNodes(); +} + +function subscribeToFeed(href, event) { + // Just load the feed in the content area to either subscribe or show the + // preview UI + var w = getTopWin(); + var charset; + if (w) { + var browser = w.getBrowser(); + charset = browser.characterSet; + } else { + // When calling this function without any open navigator window + charset = document.characterSet; + } + let feedURI = makeURI(href, charset); + + openUILink(href, event, false, true); +} + +function subscribeToFeedMiddleClick(href, event) { + if (event.button == 1) { + this.subscribeToFeed(href, event); + // unlike for command events, we have to close the menus manually + closeMenus(event.target); + } +} + +function OpenSearchEngineManager() { + var window = Services.wm.getMostRecentWindow("Browser:SearchManager"); + if (window) + window.focus(); + else { + var arg = { value: false }; + openDialog("chrome://communicator/content/search/engineManager.xul", + "_blank", "chrome,dialog,modal,centerscreen,resizable", arg); + if (arg.value) + loadAddSearchEngines(); + } +} + +function loadAddSearchEngines() { + var newWindowPref = Services.prefs.getIntPref("browser.link.open_newwindow"); + var where = newWindowPref == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB ? "tabfocused" : "window"; + var searchEnginesURL = Services.urlFormatter.formatURLPref("browser.search.searchEnginesURL"); + openUILinkIn(searchEnginesURL, where); +} + +function FillInHTMLTooltip(tipElement) +{ + // Don't show the tooltip if the tooltip node is a document or disconnected. + if (!tipElement.ownerDocument || + (tipElement.ownerDocument.compareDocumentPosition(tipElement) & document.DOCUMENT_POSITION_DISCONNECTED)) + return false; + + var defView = tipElement.ownerDocument.defaultView; + // XXX Work around bug 350679: + // "Tooltips can be fired in documents with no view". + if (!defView) + return false; + + const XLinkNS = "http://www.w3.org/1999/xlink"; + const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + + var titleText = null; + var XLinkTitleText = null; + var SVGTitleText = null; + var lookingForSVGTitle = true; + var direction = defView.getComputedStyle(tipElement, "") + .getPropertyValue("direction"); + + // If the element is invalid per HTML5 Forms specifications and has no title, + // show the constraint validation error message. + if ((tipElement instanceof HTMLInputElement || + tipElement instanceof HTMLTextAreaElement || + tipElement instanceof HTMLSelectElement || + tipElement instanceof HTMLButtonElement) && + !tipElement.hasAttribute("title") && + (!tipElement.form || !tipElement.form.noValidate)) { + // If the element is barred from constraint validation or is valid, + // the validation message will be the empty string. + titleText = tipElement.validationMessage || null; + } + + while ((titleText == null) && (XLinkTitleText == null) && + (SVGTitleText == null) && tipElement) { + if (tipElement.nodeType == Node.ELEMENT_NODE && + tipElement.namespaceURI != XULNS) { + titleText = tipElement.getAttribute("title"); + if ((tipElement instanceof HTMLAnchorElement || + tipElement instanceof HTMLAreaElement || + tipElement instanceof HTMLLinkElement || + tipElement instanceof SVGAElement) && tipElement.href) { + XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title"); + } + if (lookingForSVGTitle && + (!(tipElement instanceof SVGElement) || + tipElement.parentNode.nodeType == Node.DOCUMENT_NODE)) { + lookingForSVGTitle = false; + } + if (lookingForSVGTitle) { + let length = tipElement.childNodes.length; + for (let i = 0; i < length; i++) { + let childNode = tipElement.childNodes[i]; + if (childNode instanceof SVGTitleElement) { + SVGTitleText = childNode.textContent; + break; + } + } + } + direction = defView.getComputedStyle(tipElement, "") + .getPropertyValue("direction"); + } + tipElement = tipElement.parentNode; + } + + var tipNode = document.getElementById("aHTMLTooltip"); + tipNode.style.direction = direction; + + return [titleText, XLinkTitleText, SVGTitleText].some(function (t) { + if (t && /\S/.test(t)) { + // Make CRLF and CR render one line break each. + tipNode.setAttribute("label", t.replace(/\r\n?/g, "\n")); + return true; + } + return false; + }); +} + +function GetFileFromString(aString) +{ + // If empty string just return null. + if (!aString) + return null; + + let commandLine = Cc["@mozilla.org/toolkit/command-line;1"] + .createInstance(Ci.nsICommandLine); + let uri = commandLine.resolveURI(aString); + return uri instanceof Ci.nsIFileURL ? + uri.file.QueryInterface(Ci.nsIFile) : null; +} + +function CopyImage() +{ + var param = Cu.createCommandParams(); + param.setLongValue("imageCopy", + Ci.nsIContentViewerEdit.COPY_IMAGE_ALL); + document.commandDispatcher.getControllerForCommand("cmd_copyImage") + .QueryInterface(Ci.nsICommandController) + .doCommandWithParams("cmd_copyImage", param); +} + +/** + * Moved from toolkit/content/globalOverlay.js + */ +function goSetMenuValue(aCommand, aLabelAttribute) { + var commandNode = top.document.getElementById(aCommand); + if (commandNode) { + var label = commandNode.getAttribute(aLabelAttribute); + if (label) + commandNode.setAttribute("label", label); + } +} + +function goSetAccessKey(aCommand, aValueAttribute) { + var commandNode = top.document.getElementById(aCommand); + if (commandNode) { + var value = commandNode.getAttribute(aValueAttribute); + if (value) + commandNode.setAttribute("accesskey", value); + } +} + +// this function is used to inform all the controllers attached to a node that an event has occurred +// (e.g. the tree controllers need to be informed of blur events so that they can change some of the +// menu items back to their default values) +function goOnEvent(aNode, aEvent) { + var numControllers = aNode.controllers.getControllerCount(); + var controller; + + for (var controllerIndex = 0; controllerIndex < numControllers; controllerIndex++) { + controller = aNode.controllers.getControllerAt(controllerIndex); + if (controller) + controller.onEvent(aEvent); + } +} diff --git a/comm/suite/base/content/utilityOverlay.xul b/comm/suite/base/content/utilityOverlay.xul new file mode 100644 index 0000000000..c5d0ea5751 --- /dev/null +++ b/comm/suite/base/content/utilityOverlay.xul @@ -0,0 +1,719 @@ +<?xml version="1.0"?> +<!-- 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/. --> + +<!DOCTYPE overlay [ + +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> +%brandDTD; +<!ENTITY % utilityDTD SYSTEM "chrome://communicator/locale/utilityOverlay.dtd"> +%utilityDTD; +<!ENTITY % customizeToolbarDTD SYSTEM "chrome://communicator/locale/customizeToolbar.dtd"> +%customizeToolbarDTD; + +]> + +<overlay id="utilityOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://global/content/globalOverlay.js"/> + <script src="chrome://communicator/content/utilityOverlay.js"/> + <script src="chrome://communicator/content/sync/syncUI.js"/> + <script src="chrome://help/content/contextHelp.js"/> + + <stringbundleset> + <stringbundle id="bundle_utilityOverlay" + src="chrome://communicator/locale/utilityOverlay.properties"/> + </stringbundleset> + +<broadcasterset id="mainBroadcasterSet"> + <broadcaster id="cmd_CustomizeToolbars"/> +</broadcasterset> + + <!-- online/offline status indicators --> + <broadcaster id="Communicator:WorkMode" + label="&offlineGoOfflineCmd.label;" + type="checkbox" + oncommand="toggleOfflineStatus();"/> + + <menupopup id="networkProperties" onpopupshown="InitProxyMenu();"> + <menuitem id="network-proxy-no" + type="radio" + name="status" + label="&direct.label;" + accesskey="&direct.accesskey;" + oncommand="setNetworkStatus('0')"/> + <menuitem id="network-proxy-wpad" + type="radio" + name="status" + label="&wpad.label;" + accesskey="&wpad.accesskey;" + oncommand="setNetworkStatus('4')"/> + <menuitem id="network-proxy-system" + type="radio" + name="status" + label="&system.label;" + accesskey="&system.accesskey;" + hidden="true" + oncommand="setNetworkStatus('5')"/> + <menuitem id="network-proxy-pac" + type="radio" + name="status" + label="&pac.label;" + accesskey="&pac.accesskey;" + oncommand="setNetworkStatus('2')"/> + <menuitem id="network-proxy-manual" + type="radio" + name="status" + label="&manual.label;" + accesskey="&manual.accesskey;" + oncommand="setNetworkStatus('1')"/> + <menuseparator/> + <menuitem label="&proxy.label;" + accesskey="&proxy.accesskey;" + oncommand="goPreferences('proxies_pane');"/> + </menupopup> + + <menupopup id="toolbar-context-menu" + onpopupshowing="onViewToolbarsPopupShowing(event);" + oncommand="onViewToolbarCommand(event);"> + <menuseparator id="toolbarmode-sep"/> + <menu id="toolbarmode-context-menu" + label="&customizeToolbar.toolbarmode.label;" + accesskey="&customizeToolbar.toolbarmode.accesskey;"> + <menupopup id="toolbarModePopup" + onpopupshowing="onToolbarModePopupShowing(event);" + oncommand="goSetToolbarState(event);"> + <menuitem type="radio" name="mode" value="icons" + label="&customizeToolbar.icons.label;" + accesskey="&customizeToolbar.icons.accesskey;"/> + <menuitem type="radio" name="mode" value="full" + label="&customizeToolbar.iconsAndText.label;" + accesskey="&customizeToolbar.iconsAndText.accesskey;"/> + <menuitem type="radio" name="mode" value="text" + label="&customizeToolbar.text.label;" + accesskey="&customizeToolbar.text.accesskey;"/> + <menuseparator/> + <menuitem id="toolbarmode-smallicons" + type="checkbox" value="smallicons" + label="&customizeToolbar.useSmallIcons.label;" + accesskey="&customizeToolbar.useSmallIcons.accesskey;"/> + <menuitem id="toolbarmode-labelalign" + type="checkbox" value="end" + label="&customizeToolbar.labelAlignEnd.label;" + accesskey="&customizeToolbar.labelAlignEnd.accesskey;"/> + <menuseparator/> + <menuitem id="toolbarmode-default" + type="checkbox" value="default" + label="&customizeToolbar.useDefault.label;" + accesskey="&customizeToolbar.useDefault.accesskey;"/> + </menupopup> + </menu> + + <menuseparator id="toolbar-customize-sep"/> + <menuitem id = "customize_toolbars" + observes="cmd_CustomizeToolbars" + label="&customizeToolbarContext.label;" + accesskey="&customizeToolbarContext.accesskey;" + oncommand="return SuiteCustomizeToolbar(this);"/> + + </menupopup> + + <panel id="customizeToolbarSheetPopup" noautohide="true"> + <iframe id="customizeToolbarSheetIFrame" + style="&dialog.dimensions;" + hidden="true"/> + </panel> + + <statusbarpanel id="offline-status" context="networkProperties" + observes="Communicator:WorkMode"/> + + <menuitem id="offlineGoOfflineCmd" + label="&offlineGoOfflineCmd.label;" + accesskey="&offlineGoOfflineCmd.accesskey;" + observes="Communicator:WorkMode"/> + + <keyset id="tasksKeys"> +#ifndef XP_MACOSX + <key id="key_openHelp" + keycode="&openHelpCmd.key;" + command="cmd_openHelp"/> +#else + <key id="key_openHelp" + key="&openHelpCmdMac.key;" + modifiers="&openHelpCmdMac.modifiers;" + command="cmd_openHelp"/> + <key id="key_preferencesMac" + key="&preferencesCmdMac.key;" + modifiers="&preferencesCmdMac.modifiers;"/> + <key id="key_hideThisApp" + key="&hideThisAppCmd.key;" + modifiers="&hideThisAppCmd.modifiers;"/> + <key id="key_hideOtherApps" + key="&hideOtherAppsCmd.key;" + modifiers="&hideOtherAppsCmd.modifiers;"/> +#endif + <key id="key_quit" + key="&quitApplicationCmd.key;" + command="cmd_quit" + modifiers="accel"/> + </keyset> + + <!-- File Menu --> + <menu id="menu_File" + label="&fileMenu.label;" + accesskey="&fileMenu.accesskey;"/> + + <!-- New SubMenu (Under File Menu) --> + <command id="cmd_newNavigator" + oncommand="OpenBrowserWindow()"/> + <command id="cmd_newPrivateWindow" + oncommand="openNewPrivateWith('about:privatebrowsing');"/> + <command id="cmd_newEditor" + oncommand="NewEditorWindow();"/> + + <!-- XXX not implemented, temporarily disabled + <command id="cmd_newEditorTemplate" + disabled="true" + oncommand="NewEditorFromTemplate();"/> + <command id="cmd_newEditorDraft" + disabled="true" + oncommand="NewEditorFromDraft();"/> + --> + + <menuitem id="menu_newEditor" + label="&newBlankPageCmd.label;" + accesskey="&newBlankPageCmd.accesskey;" + key="key_newBlankPage" + command="cmd_newEditor"/> + <menuitem id="menu_newEditorTemplate" + label="&newPageFromTemplateCmd.label;" + accesskey="&newPageFromTemplateCmd.accesskey;" + command="cmd_newEditorTemplate"/> + <menuitem id="menu_newEditorDraft" + label="&newPageFromDraftCmd.label;" + accesskey="&newPageFromDraftCmd.accesskey;" + command="cmd_newEditorDraft"/> + + <menu id="menu_New" + label="&newMenu.label;" + accesskey="&newMenu.accesskey;"/> + + <menuitem id="menu_newNavigator" + label="&newNavigatorCmd.label;" + accesskey="&newNavigatorCmd.accesskey;" + key="key_newNavigator" + command="cmd_newNavigator"/> + <menuitem id="menu_newPrivateWindow" + label="&newPrivateWindowCmd.label;" + accesskey="&newPrivateWindowCmd.accesskey;" + key="key_newPrivateWindow" + command="cmd_newPrivateWindow"/> + <menuitem id="menu_close" + label="&closeCmd.label;" + accesskey="&closeCmd.accesskey;" + key="key_close" + command="cmd_close"/> + <menuitem id="menu_printSetup" + label="&printSetupCmd.label;" + accesskey="&printSetupCmd.accesskey;" + command="cmd_printSetup"/> + <menuitem id="menu_printPreview" + label="&printPreviewCmd.label;" + accesskey="&printPreviewCmd.accesskey;" +#ifdef XP_MACOSX + hidden="true" +#endif + command="cmd_printpreview"/> + <menuitem id="menu_print" + label="&printCmd.label;" + accesskey="&printCmd.accesskey;" + key="key_print" + command="cmd_print"/> + + <key id="key_newBlankPage" + key="&newBlankPageCmd.key;" + command="cmd_newEditor" + modifiers="accel, shift"/> + <key id="key_newNavigator" + key="&newNavigatorCmd.key;" + command="cmd_newNavigator" + modifiers="accel"/> + <key id="key_newPrivateWindow" + key="&newPrivateWindowCmd.key;" + command="cmd_newPrivateWindow" + modifiers="accel, shift"/> + <key id="key_close" + key="&closeCmd.key;" + command="cmd_close" + modifiers="accel"/> + <key id="key_closeWindow" + key="&closeCmd.key;" + command="cmd_closeWindow" + modifiers="accel,shift"/> + <key id="key_print" + key="&printCmd.key;" + command="cmd_print" + modifiers="accel"/> + <key id="printKb" + key="&printCmd.key;"/> + + <menupopup id="menu_FilePopup"> +#ifdef XP_MACOSX + <menuitem id="menu_mac_services" + label="&servicesMenu.label;"/> + <menuitem id="menu_mac_hide_app" + label="&hideThisAppCmd.label;" + key="key_hideThisApp"/> + <menuitem id="menu_mac_hide_others" + label="&hideOtherAppsCmd.label;" + key="key_hideOtherApps"/> + <menuitem id="menu_mac_show_all" + label="&showAllAppsCmd.label;"/> +#else + <menuseparator id="menu_FileQuitSeparator"/> +#endif + <menuitem id="menu_FileQuitItem" +#ifdef XP_WIN + label="&quitApplicationCmd.label;" + accesskey="&quitApplicationCmd.accesskey;" +#else +#ifdef XP_MACOSX + label="&quitApplicationCmdMac.label;" + accesskey="&quitApplicationCmdMac.accesskey;" +#else + label="&quitApplicationCmdUnix.label;" + accesskey="&quitApplicationCmdUnix.accesskey;" +#endif +#endif + key="key_quit" + command="cmd_quit"/> + </menupopup> + + <keyset id="findKeys"> + <key id="key_find" + key="&findBarCmd.key;" + command="cmd_find" + modifiers="accel"/> +#ifndef XP_MACOSX +#ifdef XP_UNIX + <key keycode="&findCmd.key2;" + command="cmd_find"/> +#endif + <key id="key_findReplace" + key="&findReplaceCmd.key;" + modifiers="accel" + command="cmd_findReplace"/> +#else + <key id="key_findReplace" + key="&findReplaceCmdMac.key;" + modifiers="accel,alt" + command="cmd_findReplace"/> +#endif + <key id="key_findNext" + key="&findAgainCmd.key;" + command="cmd_findNext" + modifiers="accel"/> + <key id="key_findPrev" + key="&findPrevCmd.key;" + command="cmd_findPrev" + modifiers="accel, shift"/> + <key keycode="&findAgainCmd.key2;" + command="cmd_findNext"/> + <key keycode="&findPrevCmd.key2;" + command="cmd_findPrev" + modifiers="shift"/> +#ifndef XP_MACOSX + <key id="key_findTypeText" + key="&findTypeTextCmd.key;"/> + <key id="key_findTypeLinks" + key="&findTypeLinksCmd.key;"/> +#endif + </keyset> + + <!-- Edit Menu --> + <menu id="menu_Edit" + label="&editMenu.label;" + accesskey="&editMenu.accesskey;"/> + <menuitem id="menu_undo" + key="key_undo" + accesskey="&undoCmd.accesskey;" + command="cmd_undo"/> + <menuitem id="menu_redo" + key="key_redo" + accesskey="&redoCmd.accesskey;" + command="cmd_redo"/> + <menuitem id="menu_cut" + key="key_cut" + accesskey="&cutCmd.accesskey;" + command="cmd_cut"/> + <menuitem id="menu_copy" + key="key_copy" + accesskey="©Cmd.accesskey;" + command="cmd_copy"/> + <menuitem id="menu_paste" + key="key_paste" + accesskey="&pasteCmd.accesskey;" + command="cmd_paste"/> + <menuitem id="menu_delete" + key="key_delete" + accesskey="&deleteCmd.accesskey;" + command="cmd_delete"/> + <menuitem id="menu_selectAll" + label="&selectAllCmd.label;" + key="key_selectAll" + accesskey="&selectAllCmd.accesskey;" + command="cmd_selectAll"/> + <menuitem id="menu_find" + accesskey="&findBarCmd.accesskey;" + key="key_find" + command="cmd_find"/> + <menuitem id="menu_findReplace" + accesskey="&findReplaceCmd.accesskey;" + key="key_findReplace" + command="cmd_findReplace"/> + <menuitem id="menu_findNext" + label="&findAgainCmd.label;" + accesskey="&findAgainCmd.accesskey;" + key="key_findNext" + command="cmd_findNext"/> + <menuitem id="menu_findPrev" + label="&findPrevCmd.label;" + accesskey="&findPrevCmd.accesskey;" + key="key_findPrev" + command="cmd_findPrev"/> + <menuitem id="menu_findTypeText" + label="&findTypeTextCmd.label;" + key="key_findTypeText" + accesskey="&findTypeTextCmd.accesskey;" + command="cmd_findTypeText"/> + <menuitem id="menu_findTypeLinks" + label="&findTypeLinksCmd.label;" + key="key_findTypeLinks" + accesskey="&findTypeLinksCmd.accesskey;" + command="cmd_findTypeLinks"/> + <menuitem id="textfieldDirection-swap" + label="&bidiSwitchTextDirectionItem.label;" + key="key_switchTextDirection" + accesskey="&bidiSwitchTextDirectionItem.accesskey;" + command="cmd_switchTextDirection"/> + + <!-- Context Menu Overlay --> + <menuitem id="context-undo" + accesskey="&undoCmd.accesskey;" + command="cmd_undo"/> + <menuitem id="context-redo" + accesskey="&redoCmd.accesskey;" + command="cmd_redo"/> + <menuitem id="context-cut" + accesskey="&cutCmd.accesskey;" + command="cmd_cut"/> + <menuitem id="context-copy" + accesskey="©Cmd.accesskey;" + command="cmd_copy"/> + <menuitem id="context-paste" + accesskey="&pasteCmd.accesskey;" + command="cmd_paste"/> + <menuitem id="context-delete" + accesskey="&deleteCmd.accesskey;" + command="cmd_delete"/> + <menuitem id="context-selectall" + label="&selectAllCmd.label;" + accesskey="&selectAllCmd.accesskey;" + command="cmd_selectAll"/> + + <!-- These key nodes are here only for show. The real bindings come from + XBL, in platformHTMLBindings.xml. See bugs 57078 and 71779. --> + + <key id="key_undo" + key="&undoCmd.key;" + modifiers="accel"/> + <key id="key_cut" + key="&cutCmd.key;" + modifiers="accel"/> + <key id="key_copy" + key="©Cmd.key;" + modifiers="accel"/> + <key id="key_paste" + key="&pasteCmd.key;" + modifiers="accel"/> + <key id="key_switchTextDirection" + command="cmd_switchTextDirection" + key="&bidiSwitchTextDirectionItem.commandkey;" + modifiers="accel,shift"/> + +#ifndef XP_MACOSX + <key id="key_redo" + key="&redoCmd.key;" + modifiers="accel"/> + <key id="key_delete" + keycode="VK_DELETE" + command="cmd_delete"/> +#ifdef XP_WIN + <key id="key_selectAll" + key="&selectAllCmd.key;" + modifiers="accel"/> +#else + <key id="key_selectAll" + key="&selectAllCmd.key;" + modifiers="alt"/> +#endif +#else + <key id="key_redo" + key="&redoCmdMac.key;" + modifiers="shift, accel"/> + <!-- not all Mac keyboards have a VK_DELETE key, so we use VK_BACK as + the primary and provide VK_DELETE as a secondary key definition --> + <key id="key_delete" + keycode="VK_BACK" + command="cmd_delete"/> + <key id="key_delete2" + keycode="VK_DELETE" + command="cmd_delete"/> + <key id="key_selectAll" + key="&selectAllCmd.key;" + modifiers="accel"/> +#endif + + <commandset id="globalEditMenuItems" + commandupdater="true" + events="focus" + oncommandupdate="goUpdateGlobalEditMenuItems()"/> + <commandset id="selectEditMenuItems" + commandupdater="true" + events="select" + oncommandupdate="goUpdateSelectEditMenuItems()"/> + <commandset id="undoEditMenuItems" + commandupdater="true" + events="undo" + oncommandupdate="goUpdateUndoEditMenuItems()"/> + <commandset id="clipboardEditMenuItems" + commandupdater="true" + events="clipboard" + oncommandupdate="goUpdatePasteMenuItems()"/> + <commandset id="findTypeMenuItems" + commandupdater="true" + events="focus" + oncommandupdate="goUpdateFindTypeMenuItems()"/> + + <commandset id="tasksCommands"> + <command id="cmd_quit" oncommand="goQuitApplication();"/> + <command id="cmd_openHelp" + oncommand="openHelp('welcome', 'chrome://communicator/locale/help/suitehelp.rdf');"/> + </commandset> + + <command id="cmd_copyLink" + oncommand="goDoCommand('cmd_copyLink')" + disabled="false"/> + <command id="cmd_copyImage" + oncommand="CopyImage();" + disabled="false"/> + <command id="cmd_undo" + label="&undoCmd.label;" + oncommand="goDoCommand('cmd_undo')" + disabled="true"/> + <command id="cmd_redo" + label="&redoCmd.label;" + oncommand="goDoCommand('cmd_redo')" + disabled="true"/> + <command id="cmd_cut" + label="&cutCmd.label;" + oncommand="goDoCommand('cmd_cut')" + disabled="true"/> + <command id="cmd_copy" + label="©Cmd.label;" + oncommand="goDoCommand('cmd_copy')" + disabled="true"/> + <command id="cmd_paste" + label="&pasteCmd.label;" + oncommand="goDoCommand('cmd_paste')" + disabled="true"/> + <command id="cmd_delete" + label="&deleteCmd.label;" + oncommand="goDoCommand('cmd_delete')" + valueDefault="&deleteCmd.label;" + valueDefaultAccessKey="&deleteCmd.accesskey;" + disabled="true"/> + <command id="cmd_selectAll" + oncommand="goDoCommand('cmd_selectAll')" + disabled="true"/> + <command id="cmd_switchTextDirection" + oncommand="goDoCommand('cmd_switchTextDirection');"/> + <command id="cmd_findTypeText" + oncommand="findTextAsYouType();"/> + <command id="cmd_findTypeLinks" + oncommand="findLinksAsYouType();"/> + + <!-- Not needed yet, window will need this: --> + <!-- broadcaster id="cmd_preferences"/ --> + +#ifndef XP_MACOSX + <menuitem id="menu_preferences" + label="&preferencesCmd.label;" + accesskey="&preferencesCmd.accesskey;"/> +#else + <menuitem id="menu_preferences" + label="&preferencesCmdMac.label;" + key="key_preferencesMac"/> +#endif + + <!-- View Menu --> + <menu id="menu_View" + label="&viewMenu.label;" + accesskey="&viewMenu.accesskey;"/> + <menu id="menu_Toolbars" + label="&viewToolbarsMenu.label;" + accesskey="&viewToolbarsMenu.accesskey;"/> + + <menuitem id="menu_showTaskbar" + label="&showTaskbarCmd.label;" + accesskey="&showTaskbarCmd.accesskey;" + oncommand="goToggleToolbar('status-bar', 'menu_showTaskbar')" + type="checkbox" + checked="true"/> + + <!-- Help Menu --> +#ifndef XP_WIN + <menu id="menu_Help" + label="&helpMenu.label;" + accesskey="&helpMenu.accesskey;"> +#else + <menu id="menu_Help" + label="&helpMenuWin.label;" + accesskey="&helpMenuWin.accesskey;"> +#endif + <menupopup id="helpPopup" onpopupshowing="updateCheckUpdatesItem();"> +#ifndef XP_MACOSX + <menuitem label="&openHelpCmd.label;" + accesskey="&openHelpCmd.accesskey;" + id="help" + key="key_openHelp" + command="cmd_openHelp"/> +#else + <menuitem label="&openHelpCmdMac.label;" + accesskey="&openHelpCmdMac.accesskey;" + id="help" + key="key_openHelp" + command="cmd_openHelp"/> +#endif + <menuitem id="troubleShooting" + accesskey="&helpTroubleshootingInfo.accesskey;" + label="&helpTroubleshootingInfo.label;" + oncommand="goTroubleshootingPage();"/> + <menuitem id="releaseUrl" + accesskey="&releaseCmd.accesskey;" + label="&releaseCmd.label;" + oncommand="goReleaseNotes();"/> + <menuitem id="helpSafeMode" + accesskey="&helpSafeMode.accesskey;" + label="&helpSafeMode.label;" + oncommand="safeModeRestart();"/> + + <menuseparator id="updateSeparator"/> + + <menuitem accesskey="&updateCmd.accesskey;" label="&updateCmd.label;" + id="checkForUpdates" oncommand="checkForUpdates();"/> + + <menuseparator id="menu_HelpAboutSeparator"/> + + <menuitem class="about" + accesskey="&aboutBuildConfigCmd.accesskey;" + label="&aboutBuildConfigCmd.label;" + id="BuildConfigInfo" + oncommand="goAbout('buildconfig');"/> + <menuitem accesskey="&aboutCmd.accesskey;" + label="&aboutCmd.label;" + id="aboutName" + oncommand="goAbout();"/> + </menupopup> + </menu> + + <menupopup id="popupNotificationMenu" + oncommand="popupBlockerMenuCommand(event.target);" + onpopupshowing="return popupNotificationMenuShowing(event);" + onpopuphiding="RemovePopupsItems(this);"> + <menuitem id="allowPopupsForSite" hidden="true" accesskey="&allowPopups.accesskey;" + oncommand="this.notificationbox.allowPopupsForSite(event);"/> + <menuitem id="showPopupManager" label="&showPopupManager.label;" + accesskey="&showPopupManager.accesskey;" + oncommand="toDataManager(hostUrl() + '|permissions|add|popup')"/> + <menuitem id="dontShowMessage" label="&dontShowMessage.label;" + accesskey="&dontShowMessage.accesskey;" + oncommand="disablePopupBlockerNotifications();"/> + <menuseparator id="popupNotificationMenuSeparator" hidden="true"/> + <!-- Additional items are generated, see popupNotificationMenuShowing() --> + </menupopup> + +#ifndef XP_MACOSX + <toolbar id="toolbar-menubar" + toolbarname="&menubarCmd.label;" + accesskey="&menubarCmd.accesskey;"/> + + <toolbar id="addrbook-toolbar-menubar2" + toolbarname="&menubarCmd.label;" + accesskey="&menubarCmd.accesskey;"/> + + <toolbar id="compose-toolbar-menubar2" + toolbarname="&menubarCmd.label;" + accesskey="&menubarCmd.accesskey;"/> + + <toolbar id="mail-toolbar-menubar2" + toolbarname="&menubarCmd.label;" + accesskey="&menubarCmd.accesskey;"/> + + <toolbar id="placesToolbar" + toolbarname="&menubarCmd.label;" + accesskey="&menubarCmd.accesskey;"/> +#else + <!-- Title bar elements --> + <vbox id="titlebar"> + <hbox id="titlebar-content"> + <spacer id="titlebar-spacer" flex="1"/> + <hbox id="titlebar-buttonbox-container"> + <hbox id="titlebar-buttonbox"/> + </hbox> + </hbox> + </vbox> +#endif + + <toolbarbutton id="print-button" + type="menu-button" + class="toolbarbutton-1 chromeclass-toolbar-additional" + command="cmd_print"> + <menupopup id="printMenu"> + <menuitem id="printMenuItemToolbar" + label="&printCmd.label;" + accesskey="&printCmd.accesskey;" + default="true" + command="cmd_print"/> +#ifndef XP_MACOSX + <menuitem id="printPreviewMenuItemToolbar" + label="&printPreviewCmd.label;" + accesskey="&printPreviewCmd.accesskey;" + command="cmd_printpreview"/> +#endif + <menuitem id="printSetupToolbar" + label="&printSetupCmd.label;" + accesskey="&printSetupCmd.accesskey;" + command="cmd_printSetup"/> + </menupopup> + </toolbarbutton> + + <toolbaritem id="throbber-box" + class="toolbaritem-noline" + title="&throbber.title;" + removable="true" + align="center"> + <button id="navigator-throbber" + onclick="checkForMiddleClick(this, event);" + oncommand="goClickThrobber('app.vendorURL', event);" + tooltiptext="&throbber.tooltip2;"/> + </toolbaritem> + + <!-- Sync toolbar button + <toolbarbutton id="sync-button" + class="toolbarbutton-1 chromeclass-toolbar-additional" + label="&syncToolbarButton.label;" + oncommand="gSyncUI.handleToolbarButton();"/> --> +</overlay> diff --git a/comm/suite/base/content/viewApplyThemeOverlay.js b/comm/suite/base/content/viewApplyThemeOverlay.js new file mode 100644 index 0000000000..557dc50a3b --- /dev/null +++ b/comm/suite/base/content/viewApplyThemeOverlay.js @@ -0,0 +1,170 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + + * 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/. */ + +const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm"); +ChromeUtils.defineModuleGetter(this, "LightweightThemeManager", + "resource://gre/modules/LightweightThemeManager.jsm"); + +var gThemes = []; +var gApplyThemeBundle; +var gBackgroundIsActive; + +function reloadThemes() +{ + AddonManager.getAddonsByTypes(["theme"], function(themes) { + gThemes = themes.sort(function(a, b) { + return a.name.localeCompare(b.name); + }); + }); +} + +var gAddonListener = { + onEnabling: function(val) {}, + onEnabled: function(val) {}, + onDisabling: function(val) {}, + onDisabled: function(val) {}, + onInstalling: function(val) {}, + onInstalled: reloadThemes, + onUninstalling: function(val) {}, + onUninstalled: reloadThemes, + onOperationCancelled: reloadThemes +}; + +function getNewThemes() +{ + // get URL for more themes from prefs + try { + openURL(Services.urlFormatter.formatURLPref("extensions.getMoreThemesURL")); + } + catch (e) { + dump(e); + } +} + +function getPersonas() +{ + // get URL for more themes from prefs + try { + openURL(Services.urlFormatter.formatURLPref("extensions.getPersonasURL")); + } + catch (e) { + dump(e); + } +} + +function checkTheme(popup) +{ + const ID_SUFFIX = "@personas.mozilla.org"; + gBackgroundIsActive = false; + + var usedThemes = LightweightThemeManager.usedThemes; + usedThemes = new Map(usedThemes.map( x => [x.id + ID_SUFFIX, x] )); + + while (popup.lastChild.localName != 'menuseparator') + popup.lastChild.remove(); + gThemes.forEach(function(theme) { + var menuitem = document.createElement('menuitem'); + menuitem.setAttribute("label", theme.name); + menuitem.setAttribute("type", "radio"); + menuitem.setAttribute("name", "themeGroup"); + if (theme.description) + menuitem.setAttribute("tooltiptext", theme.description); + if (!theme.userDisabled) + menuitem.setAttribute("checked", "true"); + else if (!(theme.permissions & AddonManager.PERM_CAN_ENABLE)) + menuitem.setAttribute("disabled", "true"); + menuitem.theme = theme; + + var persona = usedThemes.get(theme.id); + if (persona) { + menuitem.persona = persona; + if (theme.isActive) + gBackgroundIsActive = true; + } + + popup.appendChild(menuitem); + }); +} + +function previewTheme(event) +{ + if (!gBackgroundIsActive || !event.target.persona) + return; + + switch (event.type) { + case "DOMMenuItemActive": + LightweightThemeManager.previewTheme(event.target.persona); + break; + case "DOMMenuItemInactive": + LightweightThemeManager.resetPreview(); + break; + } +} + +function restartApp() +{ + // Notify all windows that an application quit has been requested. + var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"] + .createInstance(Ci.nsISupportsPRBool); + + Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); + + // Something aborted the quit process. + if (cancelQuit.data) + return; + + Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true); + const appStartup = Services.startup; + appStartup.quit(appStartup.eRestart | appStartup.eAttemptQuit); +} + +function applyTheme(menuitem) +{ + if (!menuitem.theme) + return; + + menuitem.theme.userDisabled = false; + if (!menuitem.theme.isActive) { + var promptTitle = gApplyThemeBundle.getString("switchskinstitle"); + // gBrandBundle: bundle_brand stringbundle from overlayed XUL file + var brandName = gBrandBundle.getString("brandShortName"); + var promptMsg = gApplyThemeBundle.getFormattedString("switchskins", [brandName]); + var promptNow = gApplyThemeBundle.getString("switchskinsnow"); + var promptLater = gApplyThemeBundle.getString("switchskinslater"); + var check = {value: false}; + var flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING + + Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_IS_STRING; + var pressedVal = Services.prompt.confirmEx(window, promptTitle, promptMsg, + flags, promptNow, promptLater, + null, null, check); + if (pressedVal == 0) + restartApp(); + } +} + +function applyThemeOnLoad() +{ + // init globals + gApplyThemeBundle = document.getElementById("bundle_viewApplyTheme"); + AddonManager.addAddonListener(gAddonListener); + reloadThemes(); + + removeEventListener("load", applyThemeOnLoad, false); + addEventListener("unload", applyThemeOnUnload, false); + var popup = document.getElementById("menu_viewApplyTheme_Popup"); + popup.addEventListener("DOMMenuItemActive", previewTheme); + popup.addEventListener("DOMMenuItemInactive", previewTheme); +} + +function applyThemeOnUnload() +{ + AddonManager.removeAddonListener(gAddonListener); + var popup = document.getElementById("menu_viewApplyTheme_Popup"); + popup.removeEventListener("DOMMenuItemActive", previewTheme); + popup.removeEventListener("DOMMenuItemInactive", previewTheme); +} + +addEventListener("load", applyThemeOnLoad, false); diff --git a/comm/suite/base/content/viewApplyThemeOverlay.xul b/comm/suite/base/content/viewApplyThemeOverlay.xul new file mode 100644 index 0000000000..0e0097776a --- /dev/null +++ b/comm/suite/base/content/viewApplyThemeOverlay.xul @@ -0,0 +1,34 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<!DOCTYPE overlay SYSTEM "chrome://communicator/locale/viewApplyThemeOverlay.dtd"> + +<overlay id="viewApplyThemeOverlay" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://communicator/content/viewApplyThemeOverlay.js"/> + + <stringbundle id="bundle_viewApplyTheme" + src="chrome://communicator/locale/viewApplyThemeOverlay.properties"/> + + <menu id="menu_viewApplyTheme" + label="&applyTheme.label;" + accesskey="&applyTheme.accesskey;"> + <menupopup id="menu_viewApplyTheme_Popup" + onpopupshowing="checkTheme(this);" + oncommand="applyTheme(event.target);"> + <menuitem label="&getMoreThemesCmd.label;" + accesskey="&getMoreThemesCmd.accesskey;" + oncommand="getNewThemes();"/> + <menuitem label="&getBackgroundsCmd.label;" + accesskey="&getBackgroundsCmd.accesskey;" + oncommand="getPersonas();"/> + <menuseparator/> + </menupopup> + </menu> + +</overlay> diff --git a/comm/suite/base/content/viewSourceOverlay.js b/comm/suite/base/content/viewSourceOverlay.js new file mode 100644 index 0000000000..8bcedb2087 --- /dev/null +++ b/comm/suite/base/content/viewSourceOverlay.js @@ -0,0 +1,32 @@ +/* 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/. */ + +// onload make sure we adapt what's needed for partial source +window.addEventListener("load", onLoadViewSourceOverlay); + +function onLoadViewSourceOverlay() { + if (/viewPartialSource\.xul$/.test(document.location)) { + // disable menu items that don't work since the selection is munged and + // the editor doesn't work for MathML + document.getElementById('cmd_savePage').setAttribute('disabled', 'true'); + document.getElementById('cmd_editPage').setAttribute('disabled', 'true'); + } +} + +// editPage() comes in from editorApplicationOverlay.js +function ViewSourceEditPage() { + editPage(window.content.location.href); +} + +// needed by findUtils.js +var gFindInstData; +function getFindInstData() +{ + if (!gFindInstData) { + gFindInstData = new nsFindInstData(); + gFindInstData.browser = getBrowser(); + // defaults for rootSearchWindow and currentSearchWindow are fine here + } + return gFindInstData; +} diff --git a/comm/suite/base/content/viewSourceOverlay.xul b/comm/suite/base/content/viewSourceOverlay.xul new file mode 100644 index 0000000000..f5ede156f0 --- /dev/null +++ b/comm/suite/base/content/viewSourceOverlay.xul @@ -0,0 +1,81 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://communicator/skin/viewSourceOverlay.css" type="text/css"?> + +<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?> +<?xul-overlay href="chrome://communicator/content/tasksOverlay.xul"?> + +<!DOCTYPE overlay [ + +<!ENTITY % navDTD SYSTEM "chrome://navigator/locale/navigator.dtd"> +%navDTD; + +]> + +<overlay id="viewSourceOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://communicator/content/viewSourceOverlay.js"/> + <script src="chrome://communicator/content/findUtils.js"/> + + <window id="viewSource"> + <commandset id="tasksCommands"/> + <command id="cmd_newNavigator"/> + <command id="cmd_newPrivateWindow"/> + <command id="cmd_newEditor"/> + <command id="cmd_editPage" oncommand="ViewSourceEditPage();"/> + <command id="cmd_find" + oncommand="findInPage(getFindInstData());"/> + <command id="cmd_findAgain" + oncommand="findAgainInPage(getFindInstData(), false);"/> + <command id="cmd_findPrevious" + oncommand="findAgainInPage(getFindInstData(), true);"/> + <stringbundle id="findBundle" + src="chrome://global/locale/finddialog.properties"/> + </window> + + <keyset id="viewSourceKeys"> + <keyset id="tasksKeys"/> + <key id="key_newBlankPage"/> + <key id="key_newNavigator"/> + <key id="key_newPrivateWindow"/> + <key id="key_editPage" key="&editPageCmd.commandkey;" + command="Browser:EditPage" modifiers="accel"/> + </keyset> + + <menubar id="viewSource-main-menubar" + class="chromeclass-menubar" + grippytooltiptext="&menuBar.tooltip;"> + <menu id="menu_file"> + <menupopup id="menu_FilePopup"> + <menu id="menu_New" position="1"> + <menupopup id="menu_NewPopup"> + <menuitem id="menu_newNavigator"/> + <menuitem id="menu_newPrivateWindow"/> + <menuitem id="menu_newEditor"/> + </menupopup> + </menu> + <menuitem id="menu_editPage" insertafter="menu_savePage" + key="key_editPage" command="cmd_editPage" + label="&editPageCmd.label;" + accesskey="&editPageCmd.accesskey;"/> + <menuseparator insertbefore="menu_pageSetup"/> + </menupopup> + </menu> + + <!-- tasks menu filled from tasksOverlay --> + <menu id="tasksMenu"/> + + <!-- window menu filled from tasksOverlay --> + <menu id="windowMenu"/> + + <!-- help menu filled from globalOverlay --> + <menu id="menu_Help"/> + </menubar> + +</overlay> diff --git a/comm/suite/base/content/viewZoomOverlay.js b/comm/suite/base/content/viewZoomOverlay.js new file mode 100644 index 0000000000..d14ef9d67d --- /dev/null +++ b/comm/suite/base/content/viewZoomOverlay.js @@ -0,0 +1,479 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + + * 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/. */ + +// One of the possible values for the mousewheel.* preferences. +// From nsEventStateManager.cpp. +const MOUSE_SCROLL_ZOOM = 3; + +/** + * Controls the "full zoom" setting and its site-specific preferences. + */ +var FullZoom = FullZoom || { + // Identifies the setting in the content prefs database. + name: "browser.content.full-zoom", + + // The global value (if any) for the setting. Asynchronously loaded from the + // service when first requested, then updated by the pref change listener as + // it changes. If there is no global value, then this should be undefined. + globalValue: undefined, + + // browser.zoom.siteSpecific preference cache + _siteSpecificPref: undefined, + + // browser.zoom.updateBackgroundTabs preference cache + updateBackgroundTabs: undefined, + + get siteSpecific() { + return this._siteSpecificPref; + }, + + //**************************************************************************// + // nsISupports + + QueryInterface: + XPCOMUtils.generateQI([Ci.nsIDOMEventListener, + Ci.nsIObserver, + Ci.nsIContentPrefObserver, + Ci.nsIContentPrefCallback2, + Ci.nsISupportsWeakReference]), + + //**************************************************************************// + // Initialization & Destruction + + init: function FullZoom_init() { + // Listen for scrollwheel events so we can save scrollwheel-based changes. + window.addEventListener("wheel", this, true); + + // Fetch the initial global value. + Services.contentPrefs2.getGlobal(this.name, null, this); + + // Register ourselves with the service so we know when our pref changes. + Services.contentPrefs2.addObserverForName(this.name, this); + + this._siteSpecificPref = + Services.prefs.getBoolPref("browser.zoom.siteSpecific"); + this.updateBackgroundTabs = + Services.prefs.getBoolPref("browser.zoom.updateBackgroundTabs"); + // Listen for changes to the browser.zoom branch so we can enable/disable + // updating background tabs and per-site saving and restoring of zoom levels. + Services.prefs.addObserver("browser.zoom.", this, true); + }, + + destroy: function FullZoom_destroy() { + Services.prefs.removeObserver("browser.zoom.", this); + Services.contentPrefs2.removeObserverForName(this.name, this); + window.removeEventListener("wheel", this, true); + }, + + + //**************************************************************************// + // Event Handlers + + // nsIDOMEventListener + + handleEvent: function FullZoom_handleEvent(event) { + switch (event.type) { + case "wheel": + this._handleMouseScrolled(event); + break; + } + }, + + _handleMouseScrolled: function FullZoom_handleMouseScrolled(event) { + // Construct the "mousewheel action" pref key corresponding to this event. + // Based on nsEventStateManager::WheelPrefs::GetIndexFor. + var modifiers = { + Alt: "mousewheel.with_alt.action", + Control: "mousewheel.with_control.action", + Meta: "mousewheel.with_meta.action", + Shift: "mousewheel.with_shift.action", + OS: "mousewheel.with_win.action" + }; + var pref = []; + for (var key in modifiers) + if (event.getModifierState(key)) + pref.push(modifiers[key]); + if (pref.length == 1) + pref = pref[0]; + else // Multiple or no modifiers, use default action + pref = "mousewheel.default.action"; + + // Don't do anything if this isn't a "zoom" scroll event. + if (Services.prefs.getIntPref(pref, 0) != MOUSE_SCROLL_ZOOM) + return; + + // XXX Lazily cache all the possible action prefs so we don't have to get + // them anew from the pref service for every scroll event? We'd have to + // make sure to observe them so we can update the cache when they change. + + // We have to call _applySettingToPref in a timeout because we handle + // the event before the event state manager has a chance to apply the zoom + // during nsEventStateManager::PostHandleEvent. + window.setTimeout(function (self) { self._applySettingToPref() }, 0, this); + }, + + // nsIObserver + + observe: function (aSubject, aTopic, aData) { + switch (aTopic) { + case "nsPref:changed": + switch (aData) { + case "browser.zoom.siteSpecific": + this._siteSpecificPref = + Services.prefs.getBoolPref("browser.zoom.siteSpecific"); + break; + case "browser.zoom.updateBackgroundTabs": + this.updateBackgroundTabs = + Services.prefs.getBoolPref("browser.zoom.updateBackgroundTabs"); + break; + } + break; + } + }, + + // nsIContentPrefObserver + + onContentPrefSet: function FullZoom_onContentPrefSet(aGroup, aName, aValue) { + if (aGroup == Services.contentPrefs2.extractDomain(getBrowser().currentURI.spec)) + this._applyPrefToSetting(aValue); + else if (aGroup == null) { + this.globalValue = this._ensureValid(aValue); + + // If the current page doesn't have a site-specific preference, + // then its zoom should be set to the new global preference now that + // the global preference has changed. + var zoomValue = Services.contentPrefs2.getCachedByDomainAndName(getBrowser().currentURI.spec, this.name, getBrowser().docShell); + if (zoomValue && !zoomValue.value) + this._applyPrefToSetting(); + } + }, + + onContentPrefRemoved: function FullZoom_onContentPrefRemoved(aGroup, aName) { + if (aGroup == Services.contentPrefs2.extractDomain(getBrowser().currentURI.spec)) + this._applyPrefToSetting(); + else if (aGroup == null) { + this.globalValue = undefined; + + // If the current page doesn't have a site-specific preference, + // then its zoom should be set to the default preference now that + // the global preference has changed. + var zoomValue = Services.contentPrefs2.getCachedByDomainAndName(getBrowser().currentURI.spec, this.name, getBrowser().docShell); + if (zoomValue && !zoomValue.value) + this._applyPrefToSetting(); + } + }, + + // nsIContentPrefCallback2 + + handleCompletion: function(aReason) {}, + handleError: function(aResult) {}, + handleResult: function(aPref) { + this.onContentPrefSet(null, this.name, aPref.value); + }, + + // location change observer + + /** + * Called when the location of a tab changes. + * When that happens, we need to update the current zoom level if appropriate. + * + * @param aURI + * A URI object representing the new location. + * @param aIsTabSwitch + * Whether this location change has happened because of a tab switch. + * @param aBrowser + * (optional) browser object displaying the document + */ + onLocationChange: function FullZoom_onLocationChange(aURI, aIsTabSwitch, aBrowser) { + if (!aURI || !this.siteSpecific) + return; + + // Avoid the cps roundtrip and apply the default/global pref. + if (aURI.spec == "about:blank") { + this._applyPrefToSetting(undefined, aBrowser); + return; + } + + // Image documents should always start at 1, and are not affected by prefs. + if (!aIsTabSwitch && aBrowser.contentDocument.mozSyntheticDocument) { + ZoomManager.setZoomForBrowser(aBrowser, this._ensureValid(1)); + return; + } + + var loadContext = aBrowser.docShell; + var zoomValue = Services.contentPrefs2.getCachedByDomainAndName(aURI.spec, this.name, loadContext); + if (zoomValue) { + this._applyPrefToSetting(zoomValue.value, aBrowser); + } else { + Services.contentPrefs2.getByDomainAndName(aURI.spec, this.name, loadContext, { + self: this, + value: undefined, + handleCompletion: function(aReason) { + // Check that we're still where we expect to be in case this took a + // while. Null check currentURI, since the window may have been + // destroyed before we were called. + if (aBrowser.currentURI && aURI.equals(aBrowser.currentURI)) + this.self._applyPrefToSetting(this.value, aBrowser); + }, + handleError: function(aResult) {}, + handleResult: function(aPref) { + this.value = aPref.value; + } + }); + } + }, + + //**************************************************************************// + // Setting & Pref Manipulation + + reduce: function FullZoom_reduce() { + ZoomManager.reduce(); + this._applySettingToPref(); + }, + + enlarge: function FullZoom_enlarge() { + ZoomManager.enlarge(); + this._applySettingToPref(); + }, + + zoom: function FullZoom_zoom(aZoomValue) { + ZoomManager.zoom = aZoomValue; + this._applySettingToPref(); + }, + + reset: function FullZoom_reset() { + if (typeof this.globalValue != "undefined") + ZoomManager.zoom = this.globalValue; + else + ZoomManager.zoom = this._ensureValid(1); + + this._removePref(); + }, + + setOther: function setZoomOther() { + if (openZoomDialog()) + this._applySettingToPref(); + }, + + /** + * Set the zoom level for the current tab. + * + * Per nsPresContext::setFullZoom, we can set the zoom to its current value + * without significant impact on performance, as the setting is only applied + * if it differs from the current setting. In fact getting the zoom and then + * checking ourselves if it differs costs more. + * + * And perhaps we should always set the zoom even if it was more expensive, + * since DocumentViewerImpl::SetTextZoom claims that child documents can have + * a different text zoom (although it would be unusual), and it implies that + * those child text zooms should get updated when the parent zoom gets set, + * and perhaps the same is true for full zoom + * (although DocumentViewerImpl::SetFullZoom doesn't mention it). + * + * So when we apply new zoom values to the browser, we simply set the zoom. + * We don't check first to see if the new value is the same as the current + * one. + **/ + _applyPrefToSetting: function FullZoom_applyPrefToSetting(aValue, aBrowser) { + var browser = aBrowser || getBrowser(); + + if (!this.siteSpecific || window.gInPrintPreviewMode || + browser.contentDocument.mozSyntheticDocument) + return; + + try { + if (typeof aValue != "undefined") + ZoomManager.setZoomForBrowser(browser, this._ensureValid(aValue)); + else if (typeof this.globalValue != "undefined") + ZoomManager.setZoomForBrowser(browser, this.globalValue); + else + ZoomManager.setZoomForBrowser(browser, this._ensureValid(1)); + } + catch(ex) {} + }, + + _applySettingToPref: function FullZoom_applySettingToPref() { + if (!this.siteSpecific || window.gInPrintPreviewMode || + content.document.mozSyntheticDocument) + return; + + var zoomLevel = ZoomManager.zoom; + Services.contentPrefs2.set(getBrowser().currentURI.spec, this.name, zoomLevel, getBrowser().docShell); + }, + + _removePref: function FullZoom_removePref() { + if (!content.document.mozSyntheticDocument) + Services.contentPrefs2.removeByDomainAndName(getBrowser().currentURI.spec, this.name, getBrowser().docShell); + }, + + + //**************************************************************************// + // Utilities + + _ensureValid: function FullZoom_ensureValid(aValue) { + if (isNaN(aValue)) + aValue = 1; + + if (aValue < ZoomManager.MIN) + return ZoomManager.MIN; + + if (aValue > ZoomManager.MAX) + return ZoomManager.MAX; + + return aValue; + } +}; + +/***** init and helper functions for viewZoomOverlay.xul *****/ +window.addEventListener("load", registerZoomManager); +window.addEventListener("unload", unregisterZoomManager); + +function registerZoomManager() { + FullZoom.init(); + + var zoomBundle = document.getElementById("bundle_viewZoom"); + var zoomMenu = document.getElementById("menu_zoom"); + var parentMenu = zoomMenu.parentNode; + parentMenu.addEventListener("popupshowing", updateViewMenu); + + // initialize menu from toolkit.zoomManager.zoomValues and assign accesskeys + var zoomFactors = ZoomManager.zoomValues; + var freeKeys = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]; + + var insertBefore = document.getElementById("menu_zoomInsertBefore"); + var popup = insertBefore.parentNode; + for (var i = 0; i < zoomFactors.length; ++i) { + var thisFactor = Math.round(zoomFactors[i] * 100); + var menuItem = document.createElement("menuitem"); + menuItem.setAttribute("type", "radio"); + menuItem.setAttribute("name", "zoom"); + + var label; + var accessKey = ""; + if (thisFactor == 100) { + label = zoomBundle.getString("zoom.100.label"); + accessKey = zoomBundle.getString("zoom.100.accesskey"); + menuItem.setAttribute("key", "key_zoomReset"); + } + else if (thisFactor == 200) { + label = zoomBundle.getString("zoom.200.label"); + accessKey = zoomBundle.getString("zoom.200.accesskey"); + } + else if (thisFactor == Math.round(ZoomManager.MIN * 100)) { + label = zoomBundle.getString("zoom.min.label") + .replace(/%zoom%/, thisFactor); + accessKey = zoomBundle.getString("zoom.min.accesskey"); + } + else if (thisFactor == Math.round(ZoomManager.MAX * 100)) { + label = zoomBundle.getString("zoom.max.label") + .replace(/%zoom%/, thisFactor); + accessKey = zoomBundle.getString("zoom.max.accesskey"); + } + else { + label = zoomBundle.getString("zoom.value.label") + .replace(/%zoom%/, thisFactor); + for (var j = 0; j < label.length; ++j) { + var testKey = label[j]; + var indexKey = freeKeys.indexOf(testKey); + if (indexKey >= 0) { + accessKey = testKey; + freeKeys.splice(indexKey, 1); + break; + } + } + } + + menuItem.setAttribute("label", label); + if (accessKey) + menuItem.setAttribute("accesskey", accessKey); + menuItem.setAttribute("value", thisFactor); + popup.insertBefore(menuItem, insertBefore); + } +} + +function unregisterZoomManager() { + FullZoom.destroy(); +} + +function updateViewMenu() { + var zoomBundle = document.getElementById("bundle_viewZoom"); + var zoomMenu = document.getElementById("menu_zoom"); + var zoomType = ZoomManager.useFullZoom ? "fullZoom" : "textZoom"; + var menuLabel = zoomBundle.getString(zoomType + ".label") + .replace(/%zoom%/, Math.round(ZoomManager.zoom * 100)); + var menuKey = zoomBundle.getString(zoomType + ".accesskey"); + zoomMenu.setAttribute("label", menuLabel); + zoomMenu.setAttribute("accesskey", menuKey); +} + +function updateZoomMenu() { + var zoomBundle = document.getElementById("bundle_viewZoom"); + var zoomOther = document.getElementById("menu_zoomOther"); + var label = zoomBundle.getString("zoom.other.label"); + var accesskey = zoomBundle.getString("zoom.other.accesskey"); + var factorOther = zoomOther.getAttribute("value") || + Math.round(ZoomManager.MAX * 100); + zoomOther.setAttribute("label", label.replace(/%zoom%/, factorOther)); + zoomOther.setAttribute("accesskey", accesskey); + zoomOther.setAttribute("value", factorOther); + + var popup = document.getElementById("menu_zoomPopup"); + var item = popup.lastChild; + while (item) { + if (item.getAttribute("name") == "zoom") { + if (item.getAttribute("value") == Math.round(ZoomManager.zoom * 100)) + item.setAttribute("checked","true"); + else + item.removeAttribute("checked"); + } + item = item.previousSibling; + } +} + +function openZoomDialog() { + var zoomOther = document.getElementById("menu_zoomOther"); + // open dialog and ask for new value + var o = {value: zoomOther.getAttribute("value"), + zoomMin: ZoomManager.MIN * 100, + zoomMax: ZoomManager.MAX * 100}; + window.openDialog("chrome://communicator/content/askViewZoom.xul", + "", "chrome,modal,centerscreen", o); + if (o.zoomOK) { + zoomOther.setAttribute("value", o.value); + ZoomManager.zoom = o.value / 100; + } + return o.zoomOK; +} + +function zoomEnlarge() { + FullZoom.enlarge(); + updateZoomStatus(); +} + +function zoomReduce() { + FullZoom.reduce(); + updateZoomStatus(); +} + +function zoomReset() { + FullZoom.reset(); + updateZoomStatus(); +} + +function zoomSetOther() { + FullZoom.setOther(); + updateZoomStatus(); +} + +function zoomToggle() { + ZoomManager.toggleZoom(); + updateZoomStatus(); +} + +function zoomSet(aValue) { + FullZoom.zoom(aValue) + updateZoomStatus(); +} diff --git a/comm/suite/base/content/viewZoomOverlay.xul b/comm/suite/base/content/viewZoomOverlay.xul new file mode 100644 index 0000000000..a22c853e3e --- /dev/null +++ b/comm/suite/base/content/viewZoomOverlay.xul @@ -0,0 +1,55 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<!DOCTYPE overlay SYSTEM "chrome://communicator/locale/viewZoomOverlay.dtd"> + +<overlay id="viewZoomOverlay" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://global/content/viewZoomOverlay.js"/> + <script src="chrome://communicator/content/viewZoomOverlay.js"/> + + <stringbundle id="bundle_viewZoom" src="chrome://communicator/locale/viewZoomOverlay.properties"/> + + <keyset id="viewZoomKeys"> + <key id="key_zoomReduce" key="&zoomReduceCmd.commandkey;" command="cmd_zoomReduce" modifiers="accel"/> + <key id="key_zoomEnlarge" key="&zoomEnlargeCmd.commandkey;" command="cmd_zoomEnlarge" modifiers="accel"/> + <key key="&zoomEnlargeCmd.commandkey;" command="cmd_zoomEnlarge" modifiers="accel,shift"/> + <key key="&zoomEnlargeCmd.commandkey2;" command="cmd_zoomEnlarge" modifiers="accel"/> + <key id="key_zoomReset" key="&zoomResetCmd.commandkey;" command="cmd_zoomReset" modifiers="accel"/> + </keyset> + + <commandset id="viewZoomCommands"> + <command id="cmd_zoomReduce" oncommand="zoomReduce();"/> + <command id="cmd_zoomEnlarge" oncommand="zoomEnlarge();"/> + <command id="cmd_zoomReset" oncommand="zoomReset();"/> + <command id="cmd_zoomOther" oncommand="zoomSetOther();"/> + <command id="cmd_fullZoomToggle" oncommand="zoomToggle();"/> + </commandset> + + <menu id="menu_zoom"> + <menupopup id="menu_zoomPopup" onpopupshowing="updateZoomMenu();" oncommand="zoomSet(event.target.value / 100);"> + <menuitem id="menu_zoomReduce" + key="key_zoomReduce" + label="&zoomReduceCmd.label;" + accesskey="&zoomReduceCmd.accesskey;" + command="cmd_zoomReduce"/> + <menuitem id="menu_zoomEnlarge" + key="key_zoomEnlarge" + label="&zoomEnlargeCmd.label;" + accesskey="&zoomEnlargeCmd.accesskey;" + command="cmd_zoomEnlarge"/> + <menuseparator/> + <menuseparator id="menu_zoomInsertBefore"/> + <menuitem id="menu_zoomOther" + type="radio" + name="zoom" + command="cmd_zoomOther"/> + </menupopup> + </menu> + +</overlay> |