diff options
Diffstat (limited to 'components/global/content')
64 files changed, 12256 insertions, 0 deletions
diff --git a/components/global/content/TopLevelVideoDocument.js b/components/global/content/TopLevelVideoDocument.js new file mode 100644 index 000000000..5a2b8a857 --- /dev/null +++ b/components/global/content/TopLevelVideoDocument.js @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// <video> is used for top-level audio documents as well +let videoElement = document.getElementsByTagName("video")[0]; + +// 1. Handle fullscreen mode; +// 2. Send keystrokes to the video element if the body element is focused, +// to be received by the event listener in videocontrols.xml. +document.addEventListener("keypress", ev => { + if (ev.synthetic) // prevent recursion + return; + + // Maximize the standalone video when pressing F11, + // but ignore audio elements + if (ev.key == "F11" && videoElement.videoWidth != 0 && videoElement.videoHeight != 0) { + // If we're in browser fullscreen mode, it means the user pressed F11 + // while browser chrome or another tab had focus. + // Don't break leaving that mode, so do nothing here. + if (window.fullScreen) { + return; + } + + // If we're not in broser fullscreen mode, prevent entering into that, + // so we don't end up there after pressing Esc. + ev.preventDefault(); + ev.stopPropagation(); + + if (!document.mozFullScreenElement) { + videoElement.mozRequestFullScreen(); + } else { + document.mozCancelFullScreen(); + } + return; + } + + // Check if the video element is focused, so it already receives + // keystrokes, and don't send it another one from here. + if (document.activeElement == videoElement) + return; + + let newEvent = new KeyboardEvent("keypress", ev); + newEvent.synthetic = true; + videoElement.dispatchEvent(newEvent); +}); diff --git a/components/global/content/XPCNativeWrapper.js b/components/global/content/XPCNativeWrapper.js new file mode 100644 index 000000000..7010a28ab --- /dev/null +++ b/components/global/content/XPCNativeWrapper.js @@ -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/. */ + +/* + * Moved to C++ implementation in XPConnect. See bug 281988. + */ diff --git a/components/global/content/about.js b/components/global/content/about.js new file mode 100644 index 000000000..c402ea685 --- /dev/null +++ b/components/global/content/about.js @@ -0,0 +1,54 @@ +/* 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/. */ + +// get release notes and vendor URL from prefs +var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"] + .getService(Components.interfaces.nsIURLFormatter); +var releaseNotesURL; +try { + releaseNotesURL = formatter.formatURLPref("app.releaseNotesURL"); +} catch(e) { + releaseNotesURL = "about:blank"; +} +if (releaseNotesURL != "about:blank") { + var relnotes = document.getElementById("releaseNotesURL"); + relnotes.setAttribute("href", releaseNotesURL); + relnotes.parentNode.removeAttribute("hidden"); +} + +var vendorURL; +try { + vendorURL = formatter.formatURLPref("app.vendorURL"); +} catch(e) { + vendorURL = "about:blank"; +} +if (vendorURL != "about:blank") { + var vendor = document.getElementById("vendorURL"); + vendor.setAttribute("href", vendorURL); +} + +// insert the version of the XUL application (!= XULRunner platform version) +var versionNum = Components.classes["@mozilla.org/xre/app-info;1"] + .getService(Components.interfaces.nsIXULAppInfo) + .version; +var version = document.getElementById("version"); +#ifdef HAVE_64BIT_BUILD +var versionStr = versionNum + " (64-bit)"; +#else +var versionStr = versionNum + " (32-bit)"; +#endif +version.textContent += " " + versionStr; + +// insert the buildid of the XUL application +var BuildIDVal = Components.classes["@mozilla.org/xre/app-info;1"] + .getService(Components.interfaces.nsIXULAppInfo) + .appBuildID; +var buildID = document.getElementById("buildID"); +buildID.textContent += " " + BuildIDVal.slice(0,-6); + +// append user agent +var ua = navigator.userAgent; +if (ua) { + document.getElementById("userAgent").textContent += " " + ua; +} diff --git a/components/global/content/about.xhtml b/components/global/content/about.xhtml new file mode 100644 index 000000000..1f57ddcc3 --- /dev/null +++ b/components/global/content/about.xhtml @@ -0,0 +1,42 @@ +<?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 % aboutDTD SYSTEM "chrome://global/locale/about.dtd" > +%aboutDTD; +<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> +%globalDTD; +]> + +<!-- 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"/> +</head> + +<body dir="&locale.dir;"> + <div id="aboutLogoContainer"> + <a id="vendorURL"> + <img src="about:logo" alt="&brandShortName;"/> + </a> + </div> + + <ul id="aboutPageList"> + <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 hidden="true">&about.relnotes.beforeTheLink;<a id="releaseNotesURL">&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="version">&about.version;</li> + <li id="buildID">&about.buildIdentifier;</li> + <li id="userAgent">&about.userAgent;</li> + <script type="application/javascript" src="chrome://global/content/about.js"/> + </ul> + +</body> +</html> diff --git a/components/global/content/aboutAbout.js b/components/global/content/aboutAbout.js new file mode 100644 index 000000000..13cb6bd6c --- /dev/null +++ b/components/global/content/aboutAbout.js @@ -0,0 +1,47 @@ +/* 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 Cc = Components.classes; +var Ci = Components.interfaces; +var gProtocols = []; +var gContainer; +window.onload = function () { + gContainer = document.getElementById("abouts"); + findAbouts(); +} + +function findAbouts() { + var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + for (var cid in Cc) { + var result = cid.match(/@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/); + if (result) { + var aboutType = result[1]; + var contract = "@mozilla.org/network/protocol/about;1?what=" + aboutType; + try { + var am = Cc[contract].getService(Ci.nsIAboutModule); + var uri = ios.newURI("about:"+aboutType, null, null); + var flags = am.getURIFlags(uri); + if (!(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT)) { + gProtocols.push(aboutType); + } + } catch (e) { + // getService might have thrown if the component doesn't actually + // implement nsIAboutModule + } + } + } + gProtocols.sort().forEach(createProtocolListing); +} + +function createProtocolListing(aProtocol) { + var uri = "about:" + aProtocol; + var li = document.createElement("li"); + var link = document.createElement("a"); + var text = document.createTextNode(uri); + + link.href = uri; + link.appendChild(text); + li.appendChild(link); + gContainer.appendChild(li); +} diff --git a/components/global/content/aboutAbout.xhtml b/components/global/content/aboutAbout.xhtml new file mode 100644 index 000000000..5ec038638 --- /dev/null +++ b/components/global/content/aboutAbout.xhtml @@ -0,0 +1,25 @@ +<?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 % aboutAboutDTD SYSTEM "chrome://global/locale/aboutAbout.dtd" > +%aboutAboutDTD; +<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> +%globalDTD; +]> + +<!-- 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>&aboutAbout.title;</title> + <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/> + <script type="application/javascript" src="chrome://global/content/aboutAbout.js"></script> +</head> + +<body dir="&locale.dir;"> + <h1>&aboutAbout.title;</h1> + <p><em>&aboutAbout.note;</em></p> + <ul id="abouts" class="columns"></ul> +</body> +</html> diff --git a/components/global/content/aboutNetworking.js b/components/global/content/aboutNetworking.js new file mode 100644 index 000000000..9400ae9d7 --- /dev/null +++ b/components/global/content/aboutNetworking.js @@ -0,0 +1,414 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +var Ci = Components.interfaces; +var Cc = Components.classes; +var Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); +const FileUtils = Cu.import("resource://gre/modules/FileUtils.jsm").FileUtils +const gEnv = Cc["@mozilla.org/process/environment;1"] + .getService(Ci.nsIEnvironment); +const gDashboard = Cc['@mozilla.org/network/dashboard;1'] + .getService(Ci.nsIDashboard); +const gDirServ = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIDirectoryServiceProvider); + +const gRequestNetworkingData = { + "http": gDashboard.requestHttpConnections, + "sockets": gDashboard.requestSockets, + "dns": gDashboard.requestDNSInfo, + "websockets": gDashboard.requestWebsocketConnections +}; +const gDashboardCallbacks = { + "http": displayHttp, + "sockets": displaySockets, + "dns": displayDns, + "websockets": displayWebsockets +}; + +const REFRESH_INTERVAL_MS = 3000; + +function col(element) { + let col = document.createElement('td'); + let content = document.createTextNode(element); + col.appendChild(content); + return col; +} + +function displayHttp(data) { + let cont = document.getElementById('http_content'); + let parent = cont.parentNode; + let new_cont = document.createElement('tbody'); + new_cont.setAttribute('id', 'http_content'); + + for (let i = 0; i < data.connections.length; i++) { + let row = document.createElement('tr'); + row.appendChild(col(data.connections[i].host)); + row.appendChild(col(data.connections[i].port)); + row.appendChild(col(data.connections[i].spdy)); + row.appendChild(col(data.connections[i].ssl)); + row.appendChild(col(data.connections[i].active.length)); + row.appendChild(col(data.connections[i].idle.length)); + new_cont.appendChild(row); + } + + parent.replaceChild(new_cont, cont); +} + +function displaySockets(data) { + let cont = document.getElementById('sockets_content'); + let parent = cont.parentNode; + let new_cont = document.createElement('tbody'); + new_cont.setAttribute('id', 'sockets_content'); + + for (let i = 0; i < data.sockets.length; i++) { + let row = document.createElement('tr'); + row.appendChild(col(data.sockets[i].host)); + row.appendChild(col(data.sockets[i].port)); + row.appendChild(col(data.sockets[i].tcp)); + row.appendChild(col(data.sockets[i].active)); + row.appendChild(col(data.sockets[i].sent)); + row.appendChild(col(data.sockets[i].received)); + new_cont.appendChild(row); + } + + parent.replaceChild(new_cont, cont); +} + +function displayDns(data) { + let cont = document.getElementById('dns_content'); + let parent = cont.parentNode; + let new_cont = document.createElement('tbody'); + new_cont.setAttribute('id', 'dns_content'); + + for (let i = 0; i < data.entries.length; i++) { + let row = document.createElement('tr'); + row.appendChild(col(data.entries[i].hostname)); + row.appendChild(col(data.entries[i].family)); + let column = document.createElement('td'); + + for (let j = 0; j < data.entries[i].hostaddr.length; j++) { + column.appendChild(document.createTextNode(data.entries[i].hostaddr[j])); + column.appendChild(document.createElement('br')); + } + + row.appendChild(column); + row.appendChild(col(data.entries[i].expiration)); + new_cont.appendChild(row); + } + + parent.replaceChild(new_cont, cont); +} + +function displayWebsockets(data) { + let cont = document.getElementById('websockets_content'); + let parent = cont.parentNode; + let new_cont = document.createElement('tbody'); + new_cont.setAttribute('id', 'websockets_content'); + + for (let i = 0; i < data.websockets.length; i++) { + let row = document.createElement('tr'); + row.appendChild(col(data.websockets[i].hostport)); + row.appendChild(col(data.websockets[i].encrypted)); + row.appendChild(col(data.websockets[i].msgsent)); + row.appendChild(col(data.websockets[i].msgreceived)); + row.appendChild(col(data.websockets[i].sentsize)); + row.appendChild(col(data.websockets[i].receivedsize)); + new_cont.appendChild(row); + } + + parent.replaceChild(new_cont, cont); +} + +function requestAllNetworkingData() { + for (let id in gRequestNetworkingData) + requestNetworkingDataForTab(id); +} + +function requestNetworkingDataForTab(id) { + gRequestNetworkingData[id](gDashboardCallbacks[id]); +} + +function init() { + gDashboard.enableLogging = true; + if (Services.prefs.getBoolPref("network.warnOnAboutNetworking")) { + let div = document.getElementById("warning_message"); + div.classList.add("active"); + div.hidden = false; + document.getElementById("confpref").addEventListener("click", confirm); + } + + requestAllNetworkingData(); + + let autoRefresh = document.getElementById("autorefcheck"); + if (autoRefresh.checked) + setAutoRefreshInterval(autoRefresh); + + autoRefresh.addEventListener("click", function() { + let refrButton = document.getElementById("refreshButton"); + if (this.checked) { + setAutoRefreshInterval(this); + refrButton.disabled = "disabled"; + } else { + clearInterval(this.interval); + refrButton.disabled = null; + } + }); + + let refr = document.getElementById("refreshButton"); + refr.addEventListener("click", requestAllNetworkingData); + if (document.getElementById("autorefcheck").checked) + refr.disabled = "disabled"; + + // Event delegation on #categories element + let menu = document.getElementById("categories"); + menu.addEventListener("click", function click(e) { + if (e.target && e.target.parentNode == menu) + show(e.target); + }); + + let dnsLookupButton = document.getElementById("dnsLookupButton"); + dnsLookupButton.addEventListener("click", function() { + doLookup(); + }); + + let setLogButton = document.getElementById("set-log-file-button"); + setLogButton.addEventListener("click", setLogFile); + + let setModulesButton = document.getElementById("set-log-modules-button"); + setModulesButton.addEventListener("click", setLogModules); + + let startLoggingButton = document.getElementById("start-logging-button"); + startLoggingButton.addEventListener("click", startLogging); + + let stopLoggingButton = document.getElementById("stop-logging-button"); + stopLoggingButton.addEventListener("click", stopLogging); + + try { + let file = gDirServ.getFile("TmpD", {}); + file.append("log.txt"); + document.getElementById("log-file").value = file.path; + } catch (e) { + console.error(e); + } + + // Update the value of the log file. + updateLogFile(); + + // Update the active log modules + updateLogModules(); + + // If we can't set the file and the modules at runtime, + // the start and stop buttons wouldn't really do anything. + if (setLogButton.disabled && setModulesButton.disabled) { + startLoggingButton.disabled = true; + stopLoggingButton.disabled = true; + } +} + +function updateLogFile() { + let logPath = ""; + + // Try to get the environment variable for the log file + logPath = gEnv.get("MOZ_LOG_FILE") || gEnv.get("NSPR_LOG_FILE"); + let currentLogFile = document.getElementById("current-log-file"); + let setLogFileButton = document.getElementById("set-log-file-button"); + + // If the log file was set from an env var, we disable the ability to set it + // at runtime. + if (logPath.length > 0) { + currentLogFile.innerText = logPath; + setLogFileButton.disabled = true; + } else { + // There may be a value set by a pref. + currentLogFile.innerText = gDashboard.getLogPath(); + } +} + +function updateLogModules() { + // Try to get the environment variable for the log file + let logModules = gEnv.get("MOZ_LOG") || + gEnv.get("MOZ_LOG_MODULES") || + gEnv.get("NSPR_LOG_MODULES"); + let currentLogModules = document.getElementById("current-log-modules"); + let setLogModulesButton = document.getElementById("set-log-modules-button"); + if (logModules.length > 0) { + currentLogModules.innerText = logModules; + // If the log modules are set by an environment variable at startup, do not + // allow changing them throught a pref. It would be difficult to figure out + // which ones are enabled and which ones are not. The user probably knows + // what he they are doing. + setLogModulesButton.disabled = true; + } else { + let activeLogModules = []; + try { + if (Services.prefs.getBoolPref("logging.config.add_timestamp")) { + activeLogModules.push("timestamp"); + } + } catch (e) {} + try { + if (Services.prefs.getBoolPref("logging.config.sync")) { + activeLogModules.push("sync"); + } + } catch (e) {} + + let children = Services.prefs.getBranch("logging.").getChildList("", {}); + + for (let pref of children) { + if (pref.startsWith("config.")) { + continue; + } + + try { + let value = Services.prefs.getIntPref(`logging.${pref}`); + activeLogModules.push(`${pref}:${value}`); + } catch (e) { + console.error(e); + } + } + + currentLogModules.innerText = activeLogModules.join(","); + } +} + +function setLogFile() { + let setLogButton = document.getElementById("set-log-file-button"); + if (setLogButton.disabled) { + // There's no point trying since it wouldn't work anyway. + return; + } + let logFile = document.getElementById("log-file").value.trim(); + Services.prefs.setCharPref("logging.config.LOG_FILE", logFile); + updateLogFile(); +} + +function clearLogModules() { + // Turn off all the modules. + let children = Services.prefs.getBranch("logging.").getChildList("", {}); + for (let pref of children) { + if (!pref.startsWith("config.")) { + Services.prefs.clearUserPref(`logging.${pref}`); + } + } + Services.prefs.clearUserPref("logging.config.add_timestamp"); + Services.prefs.clearUserPref("logging.config.sync"); + updateLogModules(); +} + +function setLogModules() { + let setLogModulesButton = document.getElementById("set-log-modules-button"); + if (setLogModulesButton.disabled) { + // The modules were set via env var, so we shouldn't try to change them. + return; + } + + let modules = document.getElementById("log-modules").value.trim(); + + // Clear previously set log modules. + clearLogModules(); + + let logModules = modules.split(","); + for (let module of logModules) { + if (module == "timestamp") { + Services.prefs.setBoolPref("logging.config.add_timestamp", true); + } else if (module == "rotate") { + // XXX: rotate is not yet supported. + } else if (module == "append") { + // XXX: append is not yet supported. + } else if (module == "sync") { + Services.prefs.setBoolPref("logging.config.sync", true); + } else { + let [key, value] = module.split(":"); + Services.prefs.setIntPref(`logging.${key}`, parseInt(value, 10)); + } + } + + updateLogModules(); +} + +function startLogging() { + setLogFile(); + setLogModules(); +} + +function stopLogging() { + clearLogModules(); + // clear the log file as well + Services.prefs.clearUserPref("logging.config.LOG_FILE"); + updateLogFile(); +} + +function confirm () { + let div = document.getElementById("warning_message"); + div.classList.remove("active"); + div.hidden = true; + let warnBox = document.getElementById("warncheck"); + Services.prefs.setBoolPref("network.warnOnAboutNetworking", warnBox.checked); +} + +function show(button) { + let current_tab = document.querySelector(".active"); + let content = document.getElementById(button.getAttribute("value")); + if (current_tab == content) + return; + current_tab.classList.remove("active"); + current_tab.hidden = true; + content.classList.add("active"); + content.hidden = false; + + let current_button = document.querySelector("[selected=true]"); + current_button.removeAttribute("selected"); + button.setAttribute("selected", "true"); + + let autoRefresh = document.getElementById("autorefcheck"); + if (autoRefresh.checked) { + clearInterval(autoRefresh.interval); + setAutoRefreshInterval(autoRefresh); + } + + let title = document.getElementById("sectionTitle"); + title.textContent = button.children[0].textContent; +} + +function setAutoRefreshInterval(checkBox) { + let active_tab = document.querySelector(".active"); + checkBox.interval = setInterval(function() { + requestNetworkingDataForTab(active_tab.id); + }, REFRESH_INTERVAL_MS); +} + +window.addEventListener("DOMContentLoaded", function load() { + window.removeEventListener("DOMContentLoaded", load); + init(); +}); + +function doLookup() { + let host = document.getElementById("host").value; + if (host) { + gDashboard.requestDNSLookup(host, displayDNSLookup); + } +} + +function displayDNSLookup(data) { + let cont = document.getElementById("dnslookuptool_content"); + let parent = cont.parentNode; + let new_cont = document.createElement("tbody"); + new_cont.setAttribute("id", "dnslookuptool_content"); + + if (data.answer) { + for (let address of data.address) { + let row = document.createElement("tr"); + row.appendChild(col(address)); + new_cont.appendChild(row); + } + } + else { + new_cont.appendChild(col(data.error)); + } + + parent.replaceChild(new_cont, cont); +} diff --git a/components/global/content/aboutNetworking.xhtml b/components/global/content/aboutNetworking.xhtml new file mode 100644 index 000000000..440ee7838 --- /dev/null +++ b/components/global/content/aboutNetworking.xhtml @@ -0,0 +1,168 @@ +<?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 [ +<!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 % networkingDTD SYSTEM "chrome://global/locale/aboutNetworking.dtd"> %networkingDTD; +]> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>&aboutNetworking.title;</title> + <link rel="stylesheet" href="chrome://mozapps/skin/aboutNetworking.css" type="text/css" /> + <script type="application/javascript;version=1.7" src="chrome://global/content/aboutNetworking.js" /> + </head> + <body id="body"> + <div id="warning_message" class="warningBackground" hidden="true"> + <div class="container"> + <h1 class="title">&aboutNetworking.warning;</h1> + <div class="toggle-container-with-text"> + <input id="warncheck" type="checkbox" checked="yes" /> + <label for="warncheck">&aboutNetworking.showNextTime;</label> + </div> + <div> + <button id="confpref" class="primary">&aboutNetworking.ok;</button> + </div> + </div> + </div> + <div id="categories"> + <div class="category" selected="true" value="http"> + <span class="category-name">&aboutNetworking.HTTP;</span> + </div> + <div class="category" value="sockets"> + <span class="category-name">&aboutNetworking.sockets;</span> + </div> + <div class="category" value="dns"> + <span class="category-name">&aboutNetworking.dns;</span> + </div> + <div class="category" value="websockets"> + <span class="category-name">&aboutNetworking.websockets;</span> + </div> + <hr></hr> + <div class="category" value="dnslookuptool"> + <span class="category-name">&aboutNetworking.dnsLookup;</span> + </div> + <div class="category" value="logging"> + <span class="category-name">&aboutNetworking.logging;</span> + </div> + </div> + <div class="main-content"> + <div class="header"> + <div id="sectionTitle" class="header-name"> + &aboutNetworking.HTTP; + </div> + <div id="refreshDiv" class="toggle-container-with-text"> + <button id="refreshButton">&aboutNetworking.refresh;</button> + <input id="autorefcheck" type="checkbox" name="Autorefresh" /> + <label for="autorefcheck">&aboutNetworking.autoRefresh;</label> + </div> + </div> + + <div id="http" class="tab active"> + <table> + <thead> + <tr> + <th>&aboutNetworking.hostname;</th> + <th>&aboutNetworking.port;</th> + <th>&aboutNetworking.spdy;</th> + <th>&aboutNetworking.ssl;</th> + <th>&aboutNetworking.active;</th> + <th>&aboutNetworking.idle;</th> + </tr> + </thead> + <tbody id="http_content" /> + </table> + </div> + + <div id="sockets" class="tab" hidden="true"> + <table> + <thead> + <tr> + <th>&aboutNetworking.host;</th> + <th>&aboutNetworking.port;</th> + <th>&aboutNetworking.tcp;</th> + <th>&aboutNetworking.active;</th> + <th>&aboutNetworking.sent;</th> + <th>&aboutNetworking.received;</th> + </tr> + </thead> + <tbody id="sockets_content" /> + </table> + </div> + + <div id="dns" class="tab" hidden="true"> + <table> + <thead> + <tr> + <th>&aboutNetworking.hostname;</th> + <th>&aboutNetworking.family;</th> + <th>&aboutNetworking.addresses;</th> + <th>&aboutNetworking.expires;</th> + </tr> + </thead> + <tbody id="dns_content" /> + </table> + </div> + + <div id="websockets" class="tab" hidden="true"> + <table> + <thead> + <tr> + <th>&aboutNetworking.hostname;</th> + <th>&aboutNetworking.ssl;</th> + <th>&aboutNetworking.messagesSent;</th> + <th>&aboutNetworking.messagesReceived;</th> + <th>&aboutNetworking.bytesSent;</th> + <th>&aboutNetworking.bytesReceived;</th> + </tr> + </thead> + <tbody id="websockets_content" /> + </table> + </div> + + <div id="dnslookuptool" class="tab" hidden="true"> + &aboutNetworking.dnsDomain;: <input type="text" name="host" id="host"></input> + <button id="dnsLookupButton">&aboutNetworking.dnsLookupButton;</button> + <hr/> + <table> + <thead> + <tr> + <th>&aboutNetworking.dnsLookupTableColumn;</th> + </tr> + </thead> + <tbody id="dnslookuptool_content" /> + </table> + </div> + + <div id="logging" class="tab" hidden="true"> + <div> + &aboutNetworking.logTutorial; + </div> + <br/> + <div> + <button id="start-logging-button"> &aboutNetworking.startLogging; </button> + <button id="stop-logging-button"> &aboutNetworking.stopLogging; </button> + </div> + <br/> + <br/> + <div> + &aboutNetworking.currentLogFile; <div id="current-log-file"></div><br/> + <input type="text" name="log-file" id="log-file"></input> + <button id="set-log-file-button"> &aboutNetworking.setLogFile; </button> + </div> + <div> + &aboutNetworking.currentLogModules; <div id="current-log-modules"></div><br/> + <input type="text" name="log-modules" id="log-modules" value="timestamp,sync,nsHttp:5,nsSocketTransport:5,nsStreamPump:5,nsHostResolver:5"></input> + <button id="set-log-modules-button"> &aboutNetworking.setLogModules; </button> + </div> + </div> + + </div> + </body> +</html> + diff --git a/components/global/content/aboutProfiles.js b/components/global/content/aboutProfiles.js new file mode 100644 index 000000000..0e548fd0f --- /dev/null +++ b/components/global/content/aboutProfiles.js @@ -0,0 +1,337 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import('resource://gre/modules/Services.jsm'); +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); + +XPCOMUtils.defineLazyServiceGetter( + this, + 'ProfileService', + '@mozilla.org/toolkit/profile-service;1', + 'nsIToolkitProfileService' +); + +const bundle = Services.strings.createBundle( + 'chrome://global/locale/aboutProfiles.properties'); + +// nsIToolkitProfileService.selectProfile can be used only during the selection +// of the profile in the ProfileManager. If we are showing about:profiles in a +// tab, the selectedProfile returns the default profile. +// In this function we use the ProfD to find the current profile. +function findCurrentProfile() { + let cpd; + try { + cpd = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + } catch (e) {} + + if (cpd) { + let itr = ProfileService.profiles; + while (itr.hasMoreElements()) { + let profile = itr.getNext().QueryInterface(Ci.nsIToolkitProfile); + if (profile.rootDir.path == cpd.path) { + return profile; + } + } + } + + // selectedProfile can trow if nothing is selected or if the selected profile + // has been deleted. + try { + return ProfileService.selectedProfile; + } catch (e) { + return null; + } +} + +function refreshUI() { + let parent = document.getElementById('profiles'); + while (parent.firstChild) { + parent.removeChild(parent.firstChild); + } + + let defaultProfile; + try { + defaultProfile = ProfileService.defaultProfile; + } catch (e) {} + + let currentProfile = findCurrentProfile() || defaultProfile; + + let iter = ProfileService.profiles; + while (iter.hasMoreElements()) { + let profile = iter.getNext().QueryInterface(Ci.nsIToolkitProfile); + display({ profile: profile, + isDefault: profile == defaultProfile, + isCurrentProfile: profile == currentProfile }); + } + + let createButton = document.getElementById('create-button'); + createButton.onclick = createProfileWizard; + + let restartSafeModeButton = document.getElementById('restart-in-safe-mode-button'); + restartSafeModeButton.onclick = function() { restart(true); } + + let restartNormalModeButton = document.getElementById('restart-button'); + restartNormalModeButton.onclick = function() { restart(false); } +} + +function openDirectory(dir) { + let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1", + "nsILocalFile", "initWithPath"); + new nsLocalFile(dir).reveal(); +} + +function display(profileData) { + let parent = document.getElementById('profiles'); + + let div = document.createElement('div'); + parent.appendChild(div); + + let nameStr = bundle.formatStringFromName('name', [profileData.profile.name], 1); + + let name = document.createElement('h2'); + name.appendChild(document.createTextNode(nameStr)); + + div.appendChild(name); + + if (profileData.isCurrentProfile) { + let currentProfile = document.createElement('h3'); + let currentProfileStr = bundle.GetStringFromName('currentProfile'); + currentProfile.appendChild(document.createTextNode(currentProfileStr)); + div.appendChild(currentProfile); + } + + let table = document.createElement('table'); + div.appendChild(table); + + let tbody = document.createElement('tbody'); + table.appendChild(tbody); + + function createItem(title, value, dir = false) { + let tr = document.createElement('tr'); + tbody.appendChild(tr); + + let th = document.createElement('th'); + th.setAttribute('class', 'column'); + th.appendChild(document.createTextNode(title)); + tr.appendChild(th); + + let td = document.createElement('td'); + td.appendChild(document.createTextNode(value)); + tr.appendChild(td); + + if (dir) { + td.appendChild(document.createTextNode(' ')); + let button = document.createElement('button'); +#ifdef XP_WIN + let string = 'winOpenDir2'; +#else + let string = 'openDir'; +#endif + let buttonText = document.createTextNode(bundle.GetStringFromName(string)); + button.appendChild(buttonText); + td.appendChild(button); + + button.addEventListener('click', function(e) { + openDirectory(value); + }); + } + } + + createItem(bundle.GetStringFromName('isDefault'), + profileData.isDefault ? bundle.GetStringFromName('yes') : bundle.GetStringFromName('no')); + + createItem(bundle.GetStringFromName('rootDir'), profileData.profile.rootDir.path, true); + + if (profileData.profile.localDir.path != profileData.profile.rootDir.path) { + createItem(bundle.GetStringFromName('localDir'), profileData.profile.localDir.path, true); + } + + let renameButton = document.createElement('button'); + renameButton.appendChild(document.createTextNode(bundle.GetStringFromName('rename'))); + renameButton.onclick = function() { + renameProfile(profileData.profile); + }; + div.appendChild(renameButton); + + if (!profileData.isCurrentProfile) { + let removeButton = document.createElement('button'); + removeButton.appendChild(document.createTextNode(bundle.GetStringFromName('remove'))); + removeButton.onclick = function() { + removeProfile(profileData.profile); + }; + + div.appendChild(removeButton); + } + + if (!profileData.isDefault) { + let defaultButton = document.createElement('button'); + defaultButton.appendChild(document.createTextNode(bundle.GetStringFromName('setAsDefault'))); + defaultButton.onclick = function() { + defaultProfile(profileData.profile); + }; + div.appendChild(defaultButton); + } + + if (!profileData.isCurrentProfile) { + let runButton = document.createElement('button'); + runButton.appendChild(document.createTextNode(bundle.GetStringFromName('launchProfile'))); + runButton.onclick = function() { + openProfile(profileData.profile); + }; + div.appendChild(runButton); + } + + let sep = document.createElement('hr'); + div.appendChild(sep); +} + +function CreateProfile(profile) { + ProfileService.selectedProfile = profile; + ProfileService.flush(); + refreshUI(); +} + +function createProfileWizard() { + // This should be rewritten in HTML eventually. + window.openDialog('chrome://mozapps/content/profile/createProfileWizard.xul', + '', 'centerscreen,chrome,modal,titlebar', + ProfileService); +} + +function renameProfile(profile) { + let title = bundle.GetStringFromName('renameProfileTitle'); + let msg = bundle.formatStringFromName('renameProfile', [profile.name], 1); + let newName = { value: profile.name }; + + if (Services.prompt.prompt(window, title, msg, newName, null, + { value: 0 })) { + newName = newName.value; + + if (newName == profile.name) { + return; + } + + try { + profile.name = newName; + } catch (e) { + let title = bundle.GetStringFromName('invalidProfileNameTitle'); + let msg = bundle.formatStringFromName('invalidProfileName', [newName], 1); + Services.prompt.alert(window, title, msg); + return; + } + + ProfileService.flush(); + refreshUI(); + } +} + +function removeProfile(profile) { + let deleteFiles = false; + + if (profile.rootDir.exists()) { + let title = bundle.GetStringFromName('deleteProfileTitle'); + let msg = bundle.formatStringFromName('deleteProfileConfirm', + [profile.rootDir.path], 1); + + let buttonPressed = Services.prompt.confirmEx(window, title, msg, + (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + + (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1) + + (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2), + bundle.GetStringFromName('dontDeleteFiles'), + null, + bundle.GetStringFromName('deleteFiles'), + null, {value:0}); + if (buttonPressed == 1) { + return; + } + + if (buttonPressed == 2) { + deleteFiles = true; + } + } + + // If we are deleting the selected or the default profile we must choose a + // different one. + let isSelected = false; + try { + isSelected = ProfileService.selectedProfile == profile; + } catch (e) {} + + let isDefault = false; + try { + isDefault = ProfileService.defaultProfile == profile; + } catch (e) {} + + if (isSelected || isDefault) { + let itr = ProfileService.profiles; + while (itr.hasMoreElements()) { + let p = itr.getNext().QueryInterface(Ci.nsIToolkitProfile); + if (profile == p) { + continue; + } + + if (isSelected) { + ProfileService.selectedProfile = p; + } + + if (isDefault) { + ProfileService.defaultProfile = p; + } + + break; + } + } + + profile.remove(deleteFiles); + ProfileService.flush(); + refreshUI(); +} + +function defaultProfile(profile) { + ProfileService.defaultProfile = profile; + ProfileService.selectedProfile = profile; + ProfileService.flush(); + refreshUI(); +} + +function openProfile(profile) { + let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"] + .createInstance(Ci.nsISupportsPRBool); + Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); + + if (cancelQuit.data) { + return; + } + + Services.startup.createInstanceWithProfile(profile); +} + +function restart(safeMode) { + let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"] + .createInstance(Ci.nsISupportsPRBool); + Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); + + if (cancelQuit.data) { + return; + } + + let flags = Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestartNotSameProfile; + + if (safeMode) { + Services.startup.restartInSafeMode(flags); + } else { + Services.startup.quit(flags); + } +} + +window.addEventListener('DOMContentLoaded', function load() { + window.removeEventListener('DOMContentLoaded', load); + refreshUI(); +}); diff --git a/components/global/content/aboutProfiles.xhtml b/components/global/content/aboutProfiles.xhtml new file mode 100644 index 000000000..ae2964932 --- /dev/null +++ b/components/global/content/aboutProfiles.xhtml @@ -0,0 +1,38 @@ +<?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 [ +<!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 % profilesDTD SYSTEM "chrome://global/locale/aboutProfiles.dtd"> %profilesDTD; +]> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>&aboutProfiles.title;</title> + <link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/> + <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" type="text/css"/> + <link rel="stylesheet" href="chrome://mozapps/skin/aboutProfiles.css" type="text/css" /> + <script type="application/javascript;version=1.7" src="chrome://global/content/aboutProfiles.js" /> + </head> + <body id="body"> + <div id="action-box"> + <h3>&aboutProfiles.restart.title;</h3> + <button id="restart-in-safe-mode-button">&aboutProfiles.restart.inSafeMode;</button> + <button id="restart-button">&aboutProfiles.restart.normal;</button> + </div> + + <h1>&aboutProfiles.title;</h1> + <div class="page-subtitle">&aboutProfiles.subtitle;</div> + + <div> + <button id="create-button">&aboutProfiles.create;</button> + </div> + + <div id="profiles" class="tab"></div> + </body> +</html> diff --git a/components/global/content/aboutRights-unbranded.xhtml b/components/global/content/aboutRights-unbranded.xhtml new file mode 100644 index 000000000..dfa12532d --- /dev/null +++ b/components/global/content/aboutRights-unbranded.xhtml @@ -0,0 +1,59 @@ +<?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 % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> + %brandDTD; + <!ENTITY % aboutRightsDTD SYSTEM "chrome://global/locale/aboutRights.dtd"> + %aboutRightsDTD; +]> + +<!-- 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>&rights.pagetitle;</title> + <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/> +</head> + +<body id="your-rights" dir="&rights.locale-direction;" class="aboutPageWideContainer"> + +<h1>&rights.intro-header;</h1> + +<p>&rights.intro;</p> + +<ul> + <li>&rights.intro-point1a;<a href="http://www.mozilla.org/MPL/">&rights.intro-point1b;</a>&rights.intro-point1c;</li> +<!-- Point 2 discusses Mozilla trademarks, and isn't needed when the build is unbranded. + - Point 3 discusses privacy policy, unbranded builds get a placeholder (for the vendor to replace) + - Point 4 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace) --> + <li>&rights.intro-point3-unbranded;</li> + <li>&rights.intro-point4a-unbranded;<a href="about:rights#webservices" onclick="showServices();">&rights.intro-point4b-unbranded;</a>&rights.intro-point4c-unbranded;</li> +</ul> + +<div id="webservices-container"> + <a name="webservices"/> + <h3>&rights2.webservices-header;</h3> + + <p>&rights.webservices-unbranded;</p> + + <ol> +<!-- Terms only apply to official builds, unbranded builds get a placeholder. --> + <li>&rights.webservices-term1-unbranded;</li> + </ol> +</div> + +<script type="application/javascript"><![CDATA[ + var servicesDiv = document.getElementById("webservices-container"); + servicesDiv.style.display = "none"; + + function showServices() { + servicesDiv.style.display = ""; + } +]]></script> + +</body> +</html> diff --git a/components/global/content/aboutRights.xhtml b/components/global/content/aboutRights.xhtml new file mode 100644 index 000000000..e7f50f2e6 --- /dev/null +++ b/components/global/content/aboutRights.xhtml @@ -0,0 +1,88 @@ +<?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 % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> + %brandDTD; + <!ENTITY % securityPrefsDTD SYSTEM "chrome://browser/locale/preferences/security.dtd"> + %securityPrefsDTD; + <!ENTITY % aboutRightsDTD SYSTEM "chrome://global/locale/aboutRights.dtd"> + %aboutRightsDTD; +]> + +<!-- 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>&rights.pagetitle;</title> + <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/> +</head> + +<body id="your-rights" dir="&rights.locale-direction;" class="aboutPageWideContainer"> + +<h1>&rights.intro-header;</h1> + +<p>&rights.intro;</p> + +<ul> + <li>&rights.intro-point1a;<a href="http://www.mozilla.org/MPL/">&rights.intro-point1b;</a>&rights.intro-point1c;</li> +<!-- Point 2 discusses Mozilla trademarks, and isn't needed when the build is unbranded. + - Point 3 discusses privacy policy, unbranded builds get a placeholder (for the vendor to replace) + - Point 4 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace) --> + <li>&rights.intro-point2-a;<a href="http://www.palemoon.org/branding.shtml">&rights.intro-point2-b;</a>&rights.intro-point2-c;</li> + <li>&rights.intro-point2.5;</li> + <li>&rights2.intro-point3a;<a href="http://www.palemoon.org/privacy.shtml">&rights2.intro-point3b;</a>&rights.intro-point3c;</li> + <li>&rights2.intro-point4a;<a href="about:rights#webservices" onclick="showServices();">&rights.intro-point4b;</a>&rights.intro-point4c;</li> + <li>&rights.intro-point5;</li> +</ul> + +<div id="webservices-container"> + <a name="webservices"/> + <h3>&rights2.webservices-header;</h3> + + <p>&rights2.webservices-a;<a href="about:rights#disabling-webservices" onclick="showDisablingServices();">&rights2.webservices-b;</a>&rights3.webservices-c;</p> + + <div id="disabling-webservices-container" style="margin-left:40px;"> + <a name="disabling-webservices"/> + <p><strong>&rights.locationawarebrowsing-a;</strong>&rights.locationawarebrowsing-b;</p> + <ul> + <li>&rights.locationawarebrowsing-term1a;<code>&rights.locationawarebrowsing-term1b;</code></li> + <li>&rights.locationawarebrowsing-term2;</li> + <li>&rights.locationawarebrowsing-term3;</li> + <li>&rights.locationawarebrowsing-term4;</li> + </ul> + </div> + + <ol> +<!-- Terms only apply to official builds, unbranded builds get a placeholder. --> + <li>&rights2.webservices-term1;</li> + <li>&rights.webservices-term2;</li> + <li>&rights2.webservices-term3;</li> + <li><strong>&rights.webservices-term4;</strong></li> + <li><strong>&rights.webservices-term5;</strong></li> + <li>&rights.webservices-term6;</li> + <li>&rights.webservices-term7;</li> + </ol> +</div> + +<script type="application/javascript"><![CDATA[ + var servicesDiv = document.getElementById("webservices-container"); + servicesDiv.style.display = "none"; + + function showServices() { + servicesDiv.style.display = ""; + } + + var disablingServicesDiv = document.getElementById("disabling-webservices-container"); + disablingServicesDiv.style.display = "none"; + + function showDisablingServices() { + disablingServicesDiv.style.display = ""; + } +]]></script> + +</body> +</html> diff --git a/components/global/content/aboutServiceWorkers.js b/components/global/content/aboutServiceWorkers.js new file mode 100644 index 000000000..1f0b67c17 --- /dev/null +++ b/components/global/content/aboutServiceWorkers.js @@ -0,0 +1,184 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); + +const bundle = Services.strings.createBundle( + "chrome://global/locale/aboutServiceWorkers.properties"); + +const brandBundle = Services.strings.createBundle( + "chrome://branding/locale/brand.properties"); + +var gSWM; +var gSWCount = 0; + +function init() { + let enabled = Services.prefs.getBoolPref("dom.serviceWorkers.enabled"); + if (!enabled) { + let div = document.getElementById("warning_not_enabled"); + div.classList.add("active"); + return; + } + + gSWM = Cc["@mozilla.org/serviceworkers/manager;1"] + .getService(Ci.nsIServiceWorkerManager); + if (!gSWM) { + dump("AboutServiceWorkers: Failed to get the ServiceWorkerManager service!\n"); + return; + } + + let data = gSWM.getAllRegistrations(); + if (!data) { + dump("AboutServiceWorkers: Failed to retrieve the registrations.\n"); + return; + } + + let length = data.length; + if (!length) { + let div = document.getElementById("warning_no_serviceworkers"); + div.classList.add("active"); + return; + } + + let ps = undefined; + try { + ps = Cc["@mozilla.org/push/Service;1"] + .getService(Ci.nsIPushService); + } catch (e) { + dump("Could not acquire PushService\n"); + } + + for (let i = 0; i < length; ++i) { + let info = data.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo); + if (!info) { + dump("AboutServiceWorkers: Invalid nsIServiceWorkerRegistrationInfo interface.\n"); + continue; + } + + display(info, ps); + } +} + +function display(info, pushService) { + let parent = document.getElementById("serviceworkers"); + + let div = document.createElement('div'); + parent.appendChild(div); + + let title = document.createElement('h2'); + let titleStr = bundle.formatStringFromName('title', [info.principal.origin], 1); + title.appendChild(document.createTextNode(titleStr)); + div.appendChild(title); + + if (info.principal.appId) { + let b2gtitle = document.createElement('h3'); + let trueFalse = bundle.GetStringFromName(info.principal.isInIsolatedMozBrowserElement ? 'true' : 'false'); + + let b2gtitleStr = + bundle.formatStringFromName('b2gtitle', [ brandBundle.getString("brandShortName"), + info.principal.appId, + trueFalse], 2); + b2gtitle.appendChild(document.createTextNode(b2gtitleStr)); + div.appendChild(b2gtitle); + } + + let list = document.createElement('ul'); + div.appendChild(list); + + function createItem(title, value, makeLink) { + let item = document.createElement('li'); + list.appendChild(item); + + let bold = document.createElement('strong'); + bold.appendChild(document.createTextNode(title + " ")); + item.appendChild(bold); + + let textNode = document.createTextNode(value); + + if (makeLink) { + let link = document.createElement("a"); + link.href = value; + link.target = "_blank"; + link.appendChild(textNode); + item.appendChild(link); + } else { + item.appendChild(textNode); + } + + return textNode; + } + + createItem(bundle.GetStringFromName('scope'), info.scope); + createItem(bundle.GetStringFromName('scriptSpec'), info.scriptSpec, true); + let currentWorkerURL = info.activeWorker ? info.activeWorker.scriptSpec : ""; + createItem(bundle.GetStringFromName('currentWorkerURL'), currentWorkerURL, true); + let activeCacheName = info.activeWorker ? info.activeWorker.cacheName : ""; + createItem(bundle.GetStringFromName('activeCacheName'), activeCacheName); + let waitingCacheName = info.waitingWorker ? info.waitingWorker.cacheName : ""; + createItem(bundle.GetStringFromName('waitingCacheName'), waitingCacheName); + + let pushItem = createItem(bundle.GetStringFromName('pushEndpoint'), bundle.GetStringFromName('waiting')); + if (pushService) { + pushService.getSubscription(info.scope, info.principal, (status, pushRecord) => { + if (Components.isSuccessCode(status)) { + pushItem.data = JSON.stringify(pushRecord); + } else { + dump("about:serviceworkers - retrieving push registration failed\n"); + } + }); + } + + let updateButton = document.createElement("button"); + updateButton.appendChild(document.createTextNode(bundle.GetStringFromName('update'))); + updateButton.onclick = function() { + gSWM.propagateSoftUpdate(info.principal.originAttributes, info.scope); + }; + div.appendChild(updateButton); + + let unregisterButton = document.createElement("button"); + unregisterButton.appendChild(document.createTextNode(bundle.GetStringFromName('unregister'))); + div.appendChild(unregisterButton); + + let loadingMessage = document.createElement('span'); + loadingMessage.appendChild(document.createTextNode(bundle.GetStringFromName('waiting'))); + loadingMessage.classList.add('inactive'); + div.appendChild(loadingMessage); + + unregisterButton.onclick = function() { + let cb = { + unregisterSucceeded: function() { + parent.removeChild(div); + + if (!--gSWCount) { + let div = document.getElementById("warning_no_serviceworkers"); + div.classList.add("active"); + } + }, + + unregisterFailed: function() { + alert(bundle.GetStringFromName('unregisterError')); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIServiceWorkerUnregisterCallback]) + }; + + loadingMessage.classList.remove('inactive'); + gSWM.propagateUnregister(info.principal, cb, info.scope); + }; + + let sep = document.createElement('hr'); + div.appendChild(sep); + + ++gSWCount; +} + +window.addEventListener("DOMContentLoaded", function load() { + window.removeEventListener("DOMContentLoaded", load); + init(); +}); diff --git a/components/global/content/aboutServiceWorkers.xhtml b/components/global/content/aboutServiceWorkers.xhtml new file mode 100644 index 000000000..8b7c86ba1 --- /dev/null +++ b/components/global/content/aboutServiceWorkers.xhtml @@ -0,0 +1,34 @@ +<?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 [ +<!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 % serviceworkersDTD SYSTEM "chrome://global/locale/aboutServiceWorkers.dtd"> %serviceworkersDTD; +]> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>&aboutServiceWorkers.title;</title> + <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css" /> + <link rel="stylesheet" href="chrome://mozapps/skin/aboutServiceWorkers.css" type="text/css" /> + <script type="application/javascript;version=1.7" src="chrome://global/content/aboutServiceWorkers.js" /> + </head> + <body id="body"> + <div id="warning_not_enabled" class="warningBackground"> + <div class="warningMessage">&aboutServiceWorkers.warning_not_enabled;</div> + </div> + + <div id="warning_no_serviceworkers" class="warningBackground"> + <div class="warningMessage">&aboutServiceWorkers.warning_no_serviceworkers;</div> + </div> + + <div id="serviceworkers" class="tab active"> + <h1>&aboutServiceWorkers.maintitle;</h1> + </div> + </body> +</html> diff --git a/components/global/content/aboutSupport.js b/components/global/content/aboutSupport.js new file mode 100644 index 000000000..86f1fa35b --- /dev/null +++ b/components/global/content/aboutSupport.js @@ -0,0 +1,997 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Troubleshoot.jsm"); +Cu.import("resource://gre/modules/ResetProfile.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", + "resource://gre/modules/PluralForm.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils", + "resource://gre/modules/PlacesDBUtils.jsm"); + +window.addEventListener("load", function onload(event) { + try { + window.removeEventListener("load", onload, false); + Troubleshoot.snapshot(function (snapshot) { + for (let prop in snapshotFormatters) + snapshotFormatters[prop](snapshot[prop]); + }); + populateActionBox(); + setupEventListeners(); + } catch (e) { + Cu.reportError("stack of load error for about:support: " + e + ": " + e.stack); + } +}, false); + +// Each property in this object corresponds to a property in Troubleshoot.jsm's +// snapshot data. Each function is passed its property's corresponding data, +// and it's the function's job to update the page with it. +var snapshotFormatters = { + + application: function application(data) { + $("application-box").textContent = data.name; + $("useragent-box").textContent = data.userAgent; + $("os-box").textContent = data.osVersion; + $("binary-box").textContent = Services.dirsvc.get("XREExeF", Ci.nsIFile).path; + $("supportLink").href = data.supportURL; + let version = Services.appinfo.version; + if (data.versionArch) { + version += " (" + data.versionArch + ")"; + } + if (data.vendor) + version += " (" + data.vendor + ")"; + $("version-box").textContent = version; + $("buildid-box").textContent = data.buildID; + if (data.updateChannel) + $("updatechannel-box").textContent = data.updateChannel; + + $("safemode-box").textContent = data.safeMode; + }, + + crashes: function crashes(data) { + return; + }, + + extensions: function extensions(data) { + $.append($("extensions-tbody"), data.map(function (extension) { + return $.new("tr", [ + $.new("td", extension.name), + $.new("td", extension.version), + $.new("td", extension.isActive), + $.new("td", extension.id), + ]); + })); + }, + + modifiedPreferences: function modifiedPreferences(data) { + $.append($("prefs-tbody"), sortedArrayFromObject(data).map( + function ([name, value]) { + return $.new("tr", [ + $.new("td", name, "pref-name"), + // Very long preference values can cause users problems when they + // copy and paste them into some text editors. Long values generally + // aren't useful anyway, so truncate them to a reasonable length. + $.new("td", String(value).substr(0, 120), "pref-value"), + ]); + } + )); + }, + + lockedPreferences: function lockedPreferences(data) { + $.append($("locked-prefs-tbody"), sortedArrayFromObject(data).map( + function ([name, value]) { + return $.new("tr", [ + $.new("td", name, "pref-name"), + $.new("td", String(value).substr(0, 120), "pref-value"), + ]); + } + )); + }, + + graphics: function graphics(data) { + let strings = stringBundle(); + + function localizedMsg(msgArray) { + let nameOrMsg = msgArray.shift(); + if (msgArray.length) { + // formatStringFromName logs an NS_ASSERTION failure otherwise that says + // "use GetStringFromName". Lame. + try { + return strings.formatStringFromName(nameOrMsg, msgArray, + msgArray.length); + } + catch (err) { + // Throws if nameOrMsg is not a name in the bundle. This shouldn't + // actually happen though, since msgArray.length > 1 => nameOrMsg is a + // name in the bundle, not a message, and the remaining msgArray + // elements are parameters. + return nameOrMsg; + } + } + try { + return strings.GetStringFromName(nameOrMsg); + } + catch (err) { + // Throws if nameOrMsg is not a name in the bundle. + } + return nameOrMsg; + } + + // Read APZ info out of data.info, stripping it out in the process. + let apzInfo = []; + let formatApzInfo = function (info) { + let out = []; + for (let type of ['Wheel', 'Touch', 'Drag']) { + let key = 'Apz' + type + 'Input'; + + if (!(key in info)) + continue; + + delete info[key]; + + let message = localizedMsg([type.toLowerCase() + 'Enabled']); + out.push(message); + } + + return out; + }; + + // Create a <tr> element with key and value columns. + // + // @key Text in the key column. Localized automatically, unless starts with "#". + // @value Text in the value column. Not localized. + function buildRow(key, value) { + let title; + if (key[0] == "#") { + title = key.substr(1); + } else { + try { + title = strings.GetStringFromName(key); + } catch (e) { + title = key; + } + } + let td = $.new("td", value); + td.style["white-space"] = "pre-wrap"; + + return $.new("tr", [ + $.new("th", title, "column"), + td, + ]); + } + + // @where The name in "graphics-<name>-tbody", of the element to append to. + // @trs Array of row elements. + function addRows(where, trs) { + $.append($("graphics-" + where + "-tbody"), trs); + } + + // Build and append a row. + // + // @where The name in "graphics-<name>-tbody", of the element to append to. + function addRow(where, key, value) { + addRows(where, [buildRow(key, value)]); + } + if (data.clearTypeParameters !== undefined) { + addRow("diagnostics", "clearTypeParameters", data.clearTypeParameters); + } + if ("info" in data) { + apzInfo = formatApzInfo(data.info); + + let trs = sortedArrayFromObject(data.info).map(function ([prop, val]) { + return $.new("tr", [ + $.new("th", prop, "column"), + $.new("td", String(val)), + ]); + }); + addRows("diagnostics", trs); + + delete data.info; + } + +#ifdef NIGHTLY_BUILD + let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + let gpuProcessPid = windowUtils.gpuProcessPid; + + if (gpuProcessPid != -1) { + let gpuProcessKillButton = $.new("button"); + + gpuProcessKillButton.addEventListener("click", function() { + windowUtils.terminateGPUProcess(); + }); + + gpuProcessKillButton.textContent = strings.GetStringFromName("gpuProcessKillButton"); + addRow("diagnostics", "GPUProcessPid", gpuProcessPid); + addRow("diagnostics", "GPUProcess", [gpuProcessKillButton]); + } +#endif + + // graphics-failures-tbody tbody + if ("failures" in data) { + // If indices is there, it should be the same length as failures, + // (see Troubleshoot.jsm) but we check anyway: + if ("indices" in data && data.failures.length == data.indices.length) { + let combined = []; + for (let i = 0; i < data.failures.length; i++) { + let assembled = assembleFromGraphicsFailure(i, data); + combined.push(assembled); + } + combined.sort(function(a, b) { + if (a.index < b.index) return -1; + if (a.index > b.index) return 1; + return 0; + }); + $.append($("graphics-failures-tbody"), + combined.map(function(val) { + return $.new("tr", [$.new("th", val.header, "column"), + $.new("td", val.message)]); + })); + delete data.indices; + } else { + $.append($("graphics-failures-tbody"), + [$.new("tr", [$.new("th", "LogFailure", "column"), + $.new("td", data.failures.map(function (val) { + return $.new("p", val); + }))])]); + } + } else { + $("graphics-failures-tbody").style.display = "none"; + } + + // Add a new row to the table, and take the key (or keys) out of data. + // + // @where Table section to add to. + // @key Data key to use. + // @colKey The localization key to use, if different from key. + function addRowFromKey(where, key, colKey) { + if (!(key in data)) + return; + colKey = colKey || key; + + let value; + let messageKey = key + "Message"; + if (messageKey in data) { + value = localizedMsg(data[messageKey]); + delete data[messageKey]; + } else { + value = data[key]; + } + delete data[key]; + + if (value) { + addRow(where, colKey, value); + } + } + + // graphics-features-tbody + + let compositor = data.windowLayerManagerRemote + ? data.windowLayerManagerType + : "BasicLayers (" + strings.GetStringFromName("mainThreadNoOMTC") + ")"; + addRow("features", "compositing", compositor); + + let acceleratedWindows = data.numAcceleratedWindows + "/" + data.numTotalWindows; + if (data.windowLayerManagerType) { + acceleratedWindows += " " + data.windowLayerManagerType; + } + if (data.windowLayerManagerRemote) { + acceleratedWindows += " (OMTC)"; + } + if (data.numAcceleratedWindowsMessage) { + acceleratedWindows += " " + localizedMsg(data.numAcceleratedWindowsMessage); + } + addRow("features", "acceleratedWindows", acceleratedWindows); + delete data.windowLayerManagerRemote; + delete data.windowLayerManagerType; + delete data.numTotalWindows; + delete data.numAcceleratedWindows; + delete data.numAcceleratedWindowsMessage; + + addRow("features", "asyncPanZoom", + apzInfo.length + ? apzInfo.join("; ") + : localizedMsg(["apzNone"])); + addRowFromKey("features", "webgl1WSIInfo"); + addRowFromKey("features", "webgl1Renderer"); + addRowFromKey("features", "webgl1Version"); + addRowFromKey("features", "webgl1DriverExtensions"); + addRowFromKey("features", "webgl1Extensions"); + addRowFromKey("features", "webgl2WSIInfo"); + addRowFromKey("features", "webgl2Renderer"); + addRowFromKey("features", "webgl2Version"); + addRowFromKey("features", "webgl2DriverExtensions"); + addRowFromKey("features", "webgl2Extensions"); + addRowFromKey("features", "supportsHardwareH264", "hardwareH264"); + addRowFromKey("features", "direct2DEnabled", "#Direct2D"); + + if ("directWriteEnabled" in data) { + let message = data.directWriteEnabled; + if ("directWriteVersion" in data) + message += " (" + data.directWriteVersion + ")"; + addRow("features", "#DirectWrite", message); + delete data.directWriteEnabled; + delete data.directWriteVersion; + } + + // Adapter tbodies. + let adapterKeys = [ + ["adapterDescription", "gpuDescription"], + ["adapterVendorID", "gpuVendorID"], + ["adapterDeviceID", "gpuDeviceID"], + ["driverVersion", "gpuDriverVersion"], + ["driverDate", "gpuDriverDate"], + ["adapterDrivers", "gpuDrivers"], + ["adapterSubsysID", "gpuSubsysID"], + ["adapterRAM", "gpuRAM"], + ]; + + function showGpu(id, suffix) { + function get(prop) { + return data[prop + suffix]; + } + + let trs = []; + for (let [prop, key] of adapterKeys) { + let value = get(prop); + if (value === undefined || value === "") + continue; + trs.push(buildRow(key, value)); + } + + if (trs.length == 0) { + $("graphics-" + id + "-tbody").style.display = "none"; + return; + } + + let active = "yes"; + if ("isGPU2Active" in data && ((suffix == "2") != data.isGPU2Active)) { + active = "no"; + } + addRow(id, "gpuActive", strings.GetStringFromName(active)); + addRows(id, trs); + } + showGpu("gpu-1", ""); + showGpu("gpu-2", "2"); + + // Remove adapter keys. + for (let [prop, key] of adapterKeys) { + delete data[prop]; + delete data[prop + "2"]; + } + delete data.isGPU2Active; + + let featureLog = data.featureLog; + delete data.featureLog; + + let features = []; + for (let feature of featureLog.features) { + // Only add interesting decisions - ones that were not automatic based on + // all.js/gfxPrefs defaults. + if (feature.log.length > 1 || feature.log[0].status != "available") { + features.push(feature); + } + } + + if (features.length) { + for (let feature of features) { + let trs = []; + for (let entry of feature.log) { + if (entry.type == "default" && entry.status == "available") + continue; + + let contents; + if (entry.message.length > 0 && entry.message[0] == "#") { + // This is a failure ID. See nsIGfxInfo.idl. + let m; + if (m = /#BLOCKLIST_FEATURE_FAILURE_BUG_(\d+)/.exec(entry.message)) { + let bugSpan = $.new("span"); + bugSpan.textContent = strings.GetStringFromName("blocklistedBug") + "; "; + + let bugHref = $.new("a"); + bugHref.href = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + m[1]; + bugHref.textContent = strings.formatStringFromName("bugLink", [m[1]], 1); + + contents = [bugSpan, bugHref]; + } else { + contents = strings.formatStringFromName( + "unknownFailure", [entry.message.substr(1)], 1); + } + } else { + contents = entry.status + " by " + entry.type + ": " + entry.message; + } + + trs.push($.new("tr", [ + $.new("td", contents), + ])); + } + addRow("decisions", feature.name, [$.new("table", trs)]); + } + } else { + $("graphics-decisions-tbody").style.display = "none"; + } + + if (featureLog.fallbacks.length) { + for (let fallback of featureLog.fallbacks) { + addRow("workarounds", fallback.name, fallback.message); + } + } else { + $("graphics-workarounds-tbody").style.display = "none"; + } + + let crashGuards = data.crashGuards; + delete data.crashGuards; + + if (crashGuards.length) { + for (let guard of crashGuards) { + let resetButton = $.new("button"); + let onClickReset = (function (guard) { + // Note - need this wrapper until bug 449811 fixes |guard| scoping. + return function () { + Services.prefs.setIntPref(guard.prefName, 0); + resetButton.removeEventListener("click", onClickReset); + resetButton.disabled = true; + }; + })(guard); + + resetButton.textContent = strings.GetStringFromName("resetOnNextRestart"); + resetButton.addEventListener("click", onClickReset); + + addRow("crashguards", guard.type + "CrashGuard", [resetButton]); + } + } else { + $("graphics-crashguards-tbody").style.display = "none"; + } + + // Now that we're done, grab any remaining keys in data and drop them into + // the diagnostics section. + for (let key in data) { + let value = data[key]; + if (Array.isArray(value)) { + value = localizedMsg(value); + } + addRow("diagnostics", key, value); + } + }, + + media: function media(data) { + let strings = stringBundle(); + + function insertBasicInfo(key, value) { + function createRow(key, value) { + let th = $.new("th", strings.GetStringFromName(key), "column"); + let td = $.new("td", value); + td.style["white-space"] = "pre-wrap"; + return $.new("tr", [th, td]); + } + $.append($("media-info-tbody"), [createRow(key, value)]); + } + + function createDeviceInfoRow(device) { + let deviceInfo = Ci.nsIAudioDeviceInfo; + + let states = {}; + states[deviceInfo.STATE_DISABLED] = "Disabled"; + states[deviceInfo.STATE_UNPLUGGED] = "Unplugged"; + states[deviceInfo.STATE_ENABLED] = "Enabled"; + + let preferreds = {}; + preferreds[deviceInfo.PREF_NONE] = "None"; + preferreds[deviceInfo.PREF_MULTIMEDIA] = "Multimedia"; + preferreds[deviceInfo.PREF_VOICE] = "Voice"; + preferreds[deviceInfo.PREF_NOTIFICATION] = "Notification"; + preferreds[deviceInfo.PREF_ALL] = "All"; + + let formats = {}; + formats[deviceInfo.FMT_S16LE] = "S16LE"; + formats[deviceInfo.FMT_S16BE] = "S16BE"; + formats[deviceInfo.FMT_F32LE] = "F32LE"; + formats[deviceInfo.FMT_F32BE] = "F32BE"; + + function toPreferredString(preferred) { + if (preferred == deviceInfo.PREF_NONE) { + return preferreds[deviceInfo.PREF_NONE]; + } else if (preferred & deviceInfo.PREF_ALL) { + return preferreds[deviceInfo.PREF_ALL]; + } + let str = ""; + for (let pref of [deviceInfo.PREF_MULTIMEDIA, + deviceInfo.PREF_VOICE, + deviceInfo.PREF_NOTIFICATION]) { + if (preferred & pref) { + str += " " + preferreds[pref]; + } + } + return str; + } + + function toFromatString(dev) { + let str = "default: " + formats[dev.defaultFormat] + ", support:"; + for (let fmt of [deviceInfo.FMT_S16LE, + deviceInfo.FMT_S16BE, + deviceInfo.FMT_F32LE, + deviceInfo.FMT_F32BE]) { + if (dev.supportedFormat & fmt) { + str += " " + formats[fmt]; + } + } + return str; + } + + function toRateString(dev) { + return "default: " + dev.defaultRate + + ", support: " + dev.minRate + " - " + dev.maxRate; + } + + function toLatencyString(dev) { + return dev.minLatency + " - " + dev.maxLatency; + } + + return $.new("tr", [$.new("td", device.name), + $.new("td", device.groupId), + $.new("td", device.vendor), + $.new("td", states[device.state]), + $.new("td", toPreferredString(device.preferred)), + $.new("td", toFromatString(device)), + $.new("td", device.maxChannels), + $.new("td", toRateString(device)), + $.new("td", toLatencyString(device))]); + } + + function insertDeviceInfo(side, devices) { + let rows = []; + for (let dev of devices) { + rows.push(createDeviceInfoRow(dev)); + } + $.append($("media-" + side + "-devices-tbody"), rows); + } + + // Basic information + insertBasicInfo("audioBackend", data.currentAudioBackend); + insertBasicInfo("maxAudioChannels", data.currentMaxAudioChannels); + insertBasicInfo("sampleRate", data.currentPreferredSampleRate); + + // Output devices information + insertDeviceInfo("output", data.audioOutputDevices); + + // Input devices information + insertDeviceInfo("input", data.audioInputDevices); + }, + + + javaScript: function javaScript(data) { + $("javascript-incremental-gc").textContent = data.incrementalGCEnabled; + }, + + accessibility: function accessibility(data) { + $("a11y-activated").textContent = data.isActive; + $("a11y-force-disabled").textContent = data.forceDisabled || 0; + }, + + libraryVersions: function libraryVersions(data) { + let strings = stringBundle(); + let trs = [ + $.new("tr", [ + $.new("th", ""), + $.new("th", strings.GetStringFromName("minLibVersions")), + $.new("th", strings.GetStringFromName("loadedLibVersions")), + ]) + ]; + sortedArrayFromObject(data).forEach( + function ([name, val]) { + trs.push($.new("tr", [ + $.new("td", name), + $.new("td", val.minVersion), + $.new("td", val.version), + ])); + } + ); + $.append($("libversions-tbody"), trs); + }, + + userJS: function userJS(data) { + if (!data.exists) + return; + let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile); + userJSFile.append("user.js"); + $("prefs-user-js-link").href = Services.io.newFileURI(userJSFile).spec; + $("prefs-user-js-section").style.display = ""; + // Clear the no-copy class + $("prefs-user-js-section").className = ""; + } +}; + +var $ = document.getElementById.bind(document); + +$.new = function $_new(tag, textContentOrChildren, className, attributes) { + let elt = document.createElement(tag); + if (className) + elt.className = className; + if (attributes) { + for (let attrName in attributes) + elt.setAttribute(attrName, attributes[attrName]); + } + if (Array.isArray(textContentOrChildren)) + this.append(elt, textContentOrChildren); + else + elt.textContent = String(textContentOrChildren); + return elt; +}; + +$.append = function $_append(parent, children) { + children.forEach(c => parent.appendChild(c)); +}; + +function stringBundle() { + return Services.strings.createBundle( + "chrome://global/locale/aboutSupport.properties"); +} + +function assembleFromGraphicsFailure(i, data) +{ + // Only cover the cases we have today; for example, we do not have + // log failures that assert and we assume the log level is 1/error. + let message = data.failures[i]; + let index = data.indices[i]; + let what = ""; + if (message.search(/\[GFX1-\]: \(LF\)/) == 0) { + // Non-asserting log failure - the message is substring(14) + what = "LogFailure"; + message = message.substring(14); + } else if (message.search(/\[GFX1-\]: /) == 0) { + // Non-asserting - the message is substring(9) + what = "Error"; + message = message.substring(9); + } else if (message.search(/\[GFX1\]: /) == 0) { + // Asserting - the message is substring(8) + what = "Assert"; + message = message.substring(8); + } + let assembled = {"index" : index, + "header" : ("(#" + index + ") " + what), + "message" : message}; + return assembled; +} + +function sortedArrayFromObject(obj) { + let tuples = []; + for (let prop in obj) + tuples.push([prop, obj[prop]]); + tuples.sort(([prop1, v1], [prop2, v2]) => prop1.localeCompare(prop2)); + return tuples; +} + +function getLoadContext() { + return window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsILoadContext); +} + +function copyContentsToClipboard() { + // Get the HTML and text representations for the important part of the page. + let contentsDiv = $("contents"); + let dataHtml = contentsDiv.innerHTML; + let dataText = createTextForElement(contentsDiv); + + // We can't use plain strings, we have to use nsSupportsString. + let supportsStringClass = Cc["@mozilla.org/supports-string;1"]; + let ssHtml = supportsStringClass.createInstance(Ci.nsISupportsString); + let ssText = supportsStringClass.createInstance(Ci.nsISupportsString); + + let transferable = Cc["@mozilla.org/widget/transferable;1"] + .createInstance(Ci.nsITransferable); + transferable.init(getLoadContext()); + + // Add the HTML flavor. + transferable.addDataFlavor("text/html"); + ssHtml.data = dataHtml; + transferable.setTransferData("text/html", ssHtml, dataHtml.length * 2); + + // Add the plain text flavor. + transferable.addDataFlavor("text/unicode"); + ssText.data = dataText; + transferable.setTransferData("text/unicode", ssText, dataText.length * 2); + + // Store the data into the clipboard. + let clipboard = Cc["@mozilla.org/widget/clipboard;1"] + .getService(Ci.nsIClipboard); + clipboard.setData(transferable, null, clipboard.kGlobalClipboard); +} + +// Return the plain text representation of an element. Do a little bit +// of pretty-printing to make it human-readable. +function createTextForElement(elem) { + let serializer = new Serializer(); + let text = serializer.serialize(elem); + +#ifdef XP_WIN + // Actual CR/LF pairs are needed for some Windows text editors. + text = text.replace(/\n/g, "\r\n"); +#endif + + return text; +} + +function Serializer() { +} + +Serializer.prototype = { + + serialize: function (rootElem) { + this._lines = []; + this._startNewLine(); + this._serializeElement(rootElem); + this._startNewLine(); + return this._lines.join("\n").trim() + "\n"; + }, + + // The current line is always the line that writing will start at next. When + // an element is serialized, the current line is updated to be the line at + // which the next element should be written. + get _currentLine() { + return this._lines.length ? this._lines[this._lines.length - 1] : null; + }, + + set _currentLine(val) { + return this._lines[this._lines.length - 1] = val; + }, + + _serializeElement: function (elem) { + if (this._ignoreElement(elem)) + return; + + // table + if (elem.localName == "table") { + this._serializeTable(elem); + return; + } + + // all other elements + + let hasText = false; + for (let child of elem.childNodes) { + if (child.nodeType == Node.TEXT_NODE) { + let text = this._nodeText( + child, (child.classList && child.classList.contains("endline"))); + this._appendText(text); + hasText = hasText || !!text.trim(); + } + else if (child.nodeType == Node.ELEMENT_NODE) + this._serializeElement(child); + } + + // For headings, draw a "line" underneath them so they stand out. + if (/^h[0-9]+$/.test(elem.localName)) { + let headerText = (this._currentLine || "").trim(); + if (headerText) { + this._startNewLine(); + this._appendText("-".repeat(headerText.length)); + } + } + + // Add a blank line underneath block elements but only if they contain text. + if (hasText) { + let display = window.getComputedStyle(elem).getPropertyValue("display"); + if (display == "block") { + this._startNewLine(); + this._startNewLine(); + } + } + }, + + _startNewLine: function () { + let currLine = this._currentLine; + if (currLine) { + // The current line is not empty. Trim it. + this._currentLine = currLine.trim(); + if (!this._currentLine) + // The current line became empty. Discard it. + this._lines.pop(); + } + this._lines.push(""); + }, + + _appendText: function (text) { + this._currentLine += text; + }, + + _isHiddenSubHeading: function (th) { + return th.parentNode.parentNode.style.display == "none"; + }, + + _serializeTable: function (table) { + // Collect the table's column headings if in fact there are any. First + // check thead. If there's no thead, check the first tr. + let colHeadings = {}; + let tableHeadingElem = table.querySelector("thead"); + if (!tableHeadingElem) + tableHeadingElem = table.querySelector("tr"); + if (tableHeadingElem) { + let tableHeadingCols = tableHeadingElem.querySelectorAll("th,td"); + // If there's a contiguous run of th's in the children starting from the + // rightmost child, then consider them to be column headings. + for (let i = tableHeadingCols.length - 1; i >= 0; i--) { + let col = tableHeadingCols[i]; + if (col.localName != "th" || col.classList.contains("title-column")) + break; + colHeadings[i] = this._nodeText( + col, (col.classList && col.classList.contains("endline"))).trim(); + } + } + let hasColHeadings = Object.keys(colHeadings).length > 0; + if (!hasColHeadings) + tableHeadingElem = null; + + let trs = table.querySelectorAll("table > tr, tbody > tr"); + let startRow = + tableHeadingElem && tableHeadingElem.localName == "tr" ? 1 : 0; + + if (startRow >= trs.length) + // The table's empty. + return; + + if (hasColHeadings && !this._ignoreElement(tableHeadingElem)) { + // Use column headings. Print each tr as a multi-line chunk like: + // Heading 1: Column 1 value + // Heading 2: Column 2 value + for (let i = startRow; i < trs.length; i++) { + if (this._ignoreElement(trs[i])) + continue; + let children = trs[i].querySelectorAll("td"); + for (let j = 0; j < children.length; j++) { + let text = ""; + if (colHeadings[j]) + text += colHeadings[j] + ": "; + text += this._nodeText( + children[j], + (children[j].classList && + children[j].classList.contains("endline"))).trim(); + this._appendText(text); + this._startNewLine(); + } + this._startNewLine(); + } + return; + } + + // Don't use column headings. Assume the table has only two columns and + // print each tr in a single line like: + // Column 1 value: Column 2 value + for (let i = startRow; i < trs.length; i++) { + if (this._ignoreElement(trs[i])) + continue; + let children = trs[i].querySelectorAll("th,td"); + let rowHeading = this._nodeText( + children[0], + (children[0].classList && + children[0].classList.contains("endline"))).trim(); + if (children[0].classList.contains("title-column")) { + if (!this._isHiddenSubHeading(children[0])) + this._appendText(rowHeading); + } else if (children.length == 1) { + // This is a single-cell row. + this._appendText(rowHeading); + } else { + let childTables = trs[i].querySelectorAll("table"); + if (childTables.length) { + // If we have child tables, don't use nodeText - its trs are already + // queued up from querySelectorAll earlier. + this._appendText(rowHeading + ": "); + } else { + this._appendText(rowHeading + ": " + this._nodeText( + children[1], + (children[1].classList && + children[1].classList.contains("endline"))).trim()); + } + } + this._startNewLine(); + } + this._startNewLine(); + }, + + _ignoreElement: function (elem) { + return elem.classList.contains("no-copy"); + }, + + _nodeText: function (node, endline) { + let whiteChars = /\s+/g + let whiteCharsButNoEndline = /(?!\n)[\s]+/g; + let _node = node.cloneNode(true); + if (_node.firstElementChild && + (_node.firstElementChild.nodeName.toLowerCase() == "button")) { + _node.removeChild(_node.firstElementChild); + } + return _node.textContent.replace( + endline ? whiteCharsButNoEndline : whiteChars, " "); + }, +}; + +function openProfileDirectory() { + // Get the profile directory. + let currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile); + let profileDir = currProfD.path; + + // Show the profile directory. + let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1", + "nsILocalFile", "initWithPath"); + new nsLocalFile(profileDir).reveal(); +} + +/** + * Profile reset is only supported for the default profile if the appropriate migrator exists. + */ +function populateActionBox() { + if (ResetProfile.resetSupported()) { + $("reset-box").style.display = "block"; + $("action-box").style.display = "block"; + } + if (!Services.appinfo.inSafeMode) { + $("safe-mode-box").style.display = "block"; + $("action-box").style.display = "block"; + } +} + +// Prompt user to restart the browser +function restart(safeMode) { + let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"] + .createInstance(Ci.nsISupportsPRBool); + Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); + + if (cancelQuit.data) { + return; + } + + let flags = Ci.nsIAppStartup.eAttemptQuit; + + if (safeMode) { + Services.startup.restartInSafeMode(flags); + } else { + Services.startup.quit(flags | Ci.nsIAppStartup.eRestart); + } +} + +/** + * Set up event listeners for buttons. + */ +function setupEventListeners() { +#ifdef MOZ_UPDATER + $("show-update-history-button").addEventListener("click", function(event) { + var prompter = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt); + prompter.showUpdateHistory(window); + }); +#endif + $("reset-box-button").addEventListener("click", function(event) { + ResetProfile.openConfirmationDialog(window); + }); + $("copy-to-clipboard").addEventListener("click", function(event) { + copyContentsToClipboard(); + }); + $("profile-dir-button").addEventListener("click", function(event) { + openProfileDirectory(); + }); + $("restart-in-safe-mode-button").addEventListener("click", function(event) { + if (Services.obs.enumerateObservers("restart-in-safe-mode").hasMoreElements()) { + Services.obs.notifyObservers(null, "restart-in-safe-mode", ""); + } else { + restart(true); + } + }); + $("restart-button").addEventListener("click", function(event) { + restart(false); + }); + $("verify-place-integrity-button").addEventListener("click", function(event) { + PlacesDBUtils.checkAndFixDatabase(function(aLog) { + let msg = aLog.join("\n"); + $("verify-place-result").style.display = "block"; + $("verify-place-result-parent").classList.remove("no-copy"); + $("verify-place-result").textContent = msg; + }); + }); +} diff --git a/components/global/content/aboutSupport.xhtml b/components/global/content/aboutSupport.xhtml new file mode 100644 index 000000000..7772f6497 --- /dev/null +++ b/components/global/content/aboutSupport.xhtml @@ -0,0 +1,557 @@ +<?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 [ + <!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 % aboutSupportDTD SYSTEM "chrome://global/locale/aboutSupport.dtd"> %aboutSupportDTD; + <!ENTITY % resetProfileDTD SYSTEM "chrome://global/locale/resetProfile.dtd"> %resetProfileDTD; +]> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>&aboutSupport.pageTitle;</title> + + <link rel="icon" type="image/png" id="favicon" + href="chrome://branding/content/icon32.png"/> + <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" + type="text/css"/> + <link rel="stylesheet" href="chrome://global/skin/aboutSupport.css" + type="text/css"/> + + <script type="application/javascript;version=1.7" + src="chrome://global/content/aboutSupport.js"/> + <script type="application/javascript;version=1.7" + src="chrome://global/content/resetProfile.js"/> + </head> + + <body dir="&locale.dir;"> + + <div id="action-box"> + <div id="reset-box"> + <h3>&refreshProfile.title;</h3> + <button id="reset-box-button"> + &refreshProfile.button.label; + </button> + </div> + <div id="safe-mode-box"> + <h3>&aboutSupport.safeModeTitle;</h3> + <button id="restart-in-safe-mode-button"> + &aboutSupport.restartInSafeMode.label; + </button> + </div> + <div id="restart-box"> + <h3>&aboutSupport.restartTitle;</h3> + <button id="restart-button"> + &aboutSupport.restartNormal.label; + </button> + </div> + + </div> + <h1> + &aboutSupport.pageTitle; + </h1> + + <div class="page-subtitle"> + &aboutSupport.pageSubtitle; + </div> + + <div> + <button id="copy-to-clipboard"> + &aboutSupport.copyTextToClipboard.label; + </button> + </div> + + <div id="contents"> + + <!-- - - - - - - - - - - - - - - - - - - - - --> + + <h2 class="major-section"> + &aboutSupport.appBasicsTitle; + </h2> + + <table> + <tbody> + <tr> + <th class="column"> + &aboutSupport.appBasicsName; + </th> + + <td id="application-box"> + </td> + </tr> + + <tr> + <th class="column"> + &aboutSupport.appBasicsVersion; + </th> + + <td id="version-box"> + </td> + </tr> + + <tr> + <th class="column"> + &aboutSupport.appBasicsBuildID; + </th> + <td id="buildid-box"></td> + </tr> + +#ifdef MOZ_UPDATER + <tr class="no-copy"> + <th class="column"> + &aboutSupport.appBasicsUpdateHistory; + </th> + + <td> + <button id="show-update-history-button"> + &aboutSupport.appBasicsShowUpdateHistory; + </button> + </td> + </tr> +#endif + +#ifdef MOZ_UPDATER + <tr> + <th class="column"> + &aboutSupport.appBasicsUpdateChannel; + </th> + <td id="updatechannel-box"></td> + </tr> +#endif + + <tr> + <th class="column"> + &aboutSupport.appBasicsUserAgent; + </th> + + <td id="useragent-box"> + </td> + </tr> + + <tr> + <th class="column"> + &aboutSupport.appBasicsOS; + </th> + + <td id="os-box"> + </td> + </tr> + + <tr class="no-copy"> + <th class="column"> + &aboutSupport.appBasicsBinary; + </th> + + <td id="binary-box"> + </td> + </tr> + + <tr id="profile-row" class="no-copy"> + <th class="column"> +#ifdef XP_WIN + &aboutSupport.appBasicsProfileDirWinMac; +#else + &aboutSupport.appBasicsProfileDir; +#endif + </th> + + <td> + <button id="profile-dir-button"> +#ifdef XP_WIN + &aboutSupport.showWin2.label; +#else + &aboutSupport.showDir.label; +#endif + </button> + </td> + </tr> + + <tr class="no-copy"> + <th class="column"> + &aboutSupport.appBasicsEnabledPlugins; + </th> + + <td> + <a href="about:plugins">about:plugins</a> + </td> + </tr> + + <tr class="no-copy"> + <th class="column"> + &aboutSupport.appBasicsBuildConfig; + </th> + + <td> + <a href="about:buildconfig">about:buildconfig</a> + </td> + </tr> + + <tr class="no-copy"> + <th class="column"> + &aboutSupport.appBasicsMemoryUse; + </th> + + <td> + <a href="about:memory">about:memory</a> + </td> + </tr> + + <tr class="no-copy"> + <th class="column"> + &aboutSupport.appBasicsPerformance; + </th> + + <td> + <a href="about:performance">about:performance</a> + </td> + </tr> + + <tr class="no-copy"> + <th class="column"> + &aboutSupport.appBasicsServiceWorkers; + </th> + + <td> + <a href="about:serviceworkers">about:serviceworkers</a> + </td> + </tr> + + <tr> + <th class="column"> + &aboutSupport.appBasicsSafeMode; + </th> + + <td id="safemode-box"> + </td> + </tr> + + <tr class="no-copy"> + <th class="column"> + &aboutSupport.appBasicsProfiles; + </th> + + <td> + <a href="about:profiles">about:profiles</a> + </td> + </tr> + </tbody> + </table> + + <!-- - - - - - - - - - - - - - - - - - - - - --> + <h2 class="major-section"> + &aboutSupport.extensionsTitle; + </h2> + + <table> + <thead> + <tr> + <th> + &aboutSupport.extensionName; + </th> + <th> + &aboutSupport.extensionVersion; + </th> + <th> + &aboutSupport.extensionEnabled; + </th> + <th> + &aboutSupport.extensionId; + </th> + </tr> + </thead> + <tbody id="extensions-tbody"> + </tbody> + </table> + + <!-- - - - - - - - - - - - - - - - - - - - - --> + + <h2 class="major-section"> + &aboutSupport.graphicsTitle; + </h2> + + <table> + <tbody id="graphics-features-tbody"> + <tr> + <th colspan="2" class="title-column"> + &aboutSupport.graphicsFeaturesTitle; + </th> + </tr> + </tbody> + + <tbody id="graphics-tbody"> + </tbody> + + <tbody id="graphics-gpu-1-tbody"> + <tr> + <th colspan="2" class="title-column"> + &aboutSupport.graphicsGPU1Title; + </th> + </tr> + </tbody> + + <tbody id="graphics-gpu-2-tbody"> + <tr> + <th colspan="2" class="title-column"> + &aboutSupport.graphicsGPU2Title; + </th> + </tr> + </tbody> + + <tbody id="graphics-diagnostics-tbody"> + <tr> + <th colspan="2" class="title-column"> + &aboutSupport.graphicsDiagnosticsTitle; + </th> + </tr> + </tbody> + + <tbody id="graphics-decisions-tbody"> + <tr> + <th colspan="2" class="title-column"> + &aboutSupport.graphicsDecisionLogTitle; + </th> + </tr> + </tbody> + + <tbody id="graphics-crashguards-tbody"> + <tr> + <th colspan="2" class="title-column"> + &aboutSupport.graphicsCrashGuardsTitle; + </th> + </tr> + </tbody> + + <tbody id="graphics-workarounds-tbody"> + <tr> + <th colspan="2" class="title-column"> + &aboutSupport.graphicsWorkaroundsTitle; + </th> + </tr> + </tbody> + + <tbody id="graphics-failures-tbody"> + <tr> + <th colspan="2" class="title-column"> + &aboutSupport.graphicsFailureLogTitle; + </th> + </tr> + </tbody> + </table> + + <!-- - - - - - - - - - - - - - - - - - - - - --> + + <h2 class="major-section"> + &aboutSupport.mediaTitle; + </h2> + <table> + <tbody id="media-info-tbody"> + </tbody> + + <tbody id="media-output-devices-tbody"> + <tr> + <th colspan="10" class="title-column"> + &aboutSupport.mediaOutputDevicesTitle; + </th> + </tr> + <tr> + <th> + &aboutSupport.mediaDeviceName; + </th> + <th> + &aboutSupport.mediaDeviceGroup; + </th> + <th> + &aboutSupport.mediaDeviceVendor; + </th> + <th> + &aboutSupport.mediaDeviceState; + </th> + <th> + &aboutSupport.mediaDevicePreferred; + </th> + <th> + &aboutSupport.mediaDeviceFormat; + </th> + <th> + &aboutSupport.mediaDeviceChannels; + </th> + <th> + &aboutSupport.mediaDeviceRate; + </th> + <th> + &aboutSupport.mediaDeviceLatency; + </th> + </tr> + </tbody> + + <tbody id="media-input-devices-tbody"> + <tr> + <th colspan="10" class="title-column"> + &aboutSupport.mediaInputDevicesTitle; + </th> + </tr> + <tr> + <th> + &aboutSupport.mediaDeviceName; + </th> + <th> + &aboutSupport.mediaDeviceGroup; + </th> + <th> + &aboutSupport.mediaDeviceVendor; + </th> + <th> + &aboutSupport.mediaDeviceState; + </th> + <th> + &aboutSupport.mediaDevicePreferred; + </th> + <th> + &aboutSupport.mediaDeviceFormat; + </th> + <th> + &aboutSupport.mediaDeviceChannels; + </th> + <th> + &aboutSupport.mediaDeviceRate; + </th> + <th> + &aboutSupport.mediaDeviceLatency; + </th> + </tr> + </tbody> + + </table> + + <!-- - - - - - - - - - - - - - - - - - - - - --> + + <h2 class="major-section"> + &aboutSupport.modifiedKeyPrefsTitle; + </h2> + + <table class="prefs-table"> + <thead class="no-copy"> + <th class="name"> + &aboutSupport.modifiedPrefsName; + </th> + + <th class="value"> + &aboutSupport.modifiedPrefsValue; + </th> + </thead> + + <tbody id="prefs-tbody"> + </tbody> + </table> + + <section id="prefs-user-js-section" class="hidden no-copy"> + <h3>&aboutSupport.userJSTitle;</h3> + <p>&aboutSupport.userJSDescription;</p> + </section> + + <!-- - - - - - - - - - - - - - - - - - - - - --> + + <h2 class="major-section"> + &aboutSupport.lockedKeyPrefsTitle; + </h2> + + <table class="prefs-table"> + <thead class="no-copy"> + <th class="name"> + &aboutSupport.lockedPrefsName; + </th> + + <th class="value"> + &aboutSupport.lockedPrefsValue; + </th> + </thead> + + <tbody id="locked-prefs-tbody"> + </tbody> + </table> + + <!-- - - - - - - - - - - - - - - - - - - - - --> + + <h2 class="major-section"> + &aboutSupport.placeDatabaseTitle; + </h2> + + <table> + <tr id="verify-place-result-parent" class="no-copy"> + <th class="column"> + &aboutSupport.placeDatabaseIntegrity; + </th> + + <td class="endline"> + <button id="verify-place-integrity-button"> + &aboutSupport.placeDatabaseVerifyIntegrity; + </button> + <pre id="verify-place-result" class="hidden"></pre> + </td> + </tr> + </table> + + <!-- - - - - - - - - - - - - - - - - - - - - --> + <h2 class="major-section"> + &aboutSupport.jsTitle; + </h2> + + <table> + <tbody> + <tr> + <th class="column"> + &aboutSupport.jsIncrementalGC; + </th> + + <td id="javascript-incremental-gc"> + </td> + </tr> + </tbody> + </table> + + <!-- - - - - - - - - - - - - - - - - - - - - --> + <h2 class="major-section"> + &aboutSupport.a11yTitle; + </h2> + + <table> + <tbody> + <tr> + <th class="column"> + &aboutSupport.a11yActivated; + </th> + + <td id="a11y-activated"> + </td> + </tr> + <tr> + <th class="column"> + &aboutSupport.a11yForceDisabled; + </th> + + <td id="a11y-force-disabled"> + </td> + </tr> + </tbody> + </table> + + <!-- - - - - - - - - - - - - - - - - - - - - --> + <h2 class="major-section"> + &aboutSupport.libraryVersionsTitle; + </h2> + + <table> + <tbody id="libversions-tbody"> + </tbody> + </table> + + </div> + + </body> + +</html> diff --git a/components/global/content/aboutwebrtc/aboutWebrtc.css b/components/global/content/aboutwebrtc/aboutWebrtc.css new file mode 100644 index 000000000..b9021dde6 --- /dev/null +++ b/components/global/content/aboutwebrtc/aboutWebrtc.css @@ -0,0 +1,97 @@ +/* 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 { + background-color: #edeceb; + font: message-box; +} + +.controls { + font-size: 1.1em; + display: inline-block; + margin: 0 0.5em; +} + +.control { + margin: 0.5em 0; +} + +.control > button { + margin: 0 0.25em; +} + +.message > p { + margin: 0.5em 0.5em; +} + +.log p { + font-family: monospace; + padding-left: 2em; + text-indent: -2em; + margin-top: 2px; + margin-bottom: 2px; +} + +#content > div { + padding: 1em 2em; + margin: 1em 0; + border: 1px solid #afaca9; + border-radius: 10px; + background: none repeat scroll 0% 0% #fff; +} + +.section-heading * +{ + display: inline-block; +} + +.section-heading > button { + margin-left: 1em; + margin-right: 1em; +} + +.peer-connection > h3 +{ + background-color: #ddd; +} + +.peer-connection table { + width: 100%; + text-align: center; + border: none; +} + +.peer-connection table th, +.peer-connection table td { + padding: 0.4em; +} + +.peer-connection table tr:nth-child(even) { + background-color: #ddd; +} + +.info-label { + font-weight: bold; +} + +.section-ctrl { + margin: 1em 1.5em; +} + +div.fold-trigger { + color: blue; + cursor: pointer; +} + +@media screen { + .fold-closed { + display: none !important; + } +} + +@media print { + .no-print { + display: none !important; + } +} diff --git a/components/global/content/aboutwebrtc/aboutWebrtc.html b/components/global/content/aboutwebrtc/aboutWebrtc.html new file mode 100644 index 000000000..42cda348e --- /dev/null +++ b/components/global/content/aboutwebrtc/aboutWebrtc.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> + +<!-- 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> +<head> + <meta charset="utf-8" /> + <title>about:webrtc</title> + <link rel="stylesheet" type="text/css" media="all" + href="chrome://global/content/aboutwebrtc/aboutWebrtc.css"/> + <script type="text/javascript;version=1.8" + src="chrome://global/content/aboutwebrtc/aboutWebrtc.js" + defer="defer"></script> +</head> +<body id="body" onload="onLoad()"> + <div id="controls" class="no-print"></div> + <div id="content"></div> +</body> +</html> diff --git a/components/global/content/aboutwebrtc/aboutWebrtc.js b/components/global/content/aboutwebrtc/aboutWebrtc.js new file mode 100644 index 000000000..f494c7b4d --- /dev/null +++ b/components/global/content/aboutwebrtc/aboutWebrtc.js @@ -0,0 +1,843 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +/* global WebrtcGlobalInformation, document */ + +var Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); +XPCOMUtils.defineLazyServiceGetter(this, "FilePicker", + "@mozilla.org/filepicker;1", "nsIFilePicker"); +XPCOMUtils.defineLazyGetter(this, "strings", () => { + return Services.strings.createBundle("chrome://global/locale/aboutWebrtc.properties"); +}); + +const getString = strings.GetStringFromName; +const formatString = strings.formatStringFromName; + +const LOGFILE_NAME_DEFAULT = "aboutWebrtc.html"; +const WEBRTC_TRACE_ALL = 65535; + +function getStats() { + return new Promise(resolve => + WebrtcGlobalInformation.getAllStats(stats => resolve(stats))); +} + +function getLog() { + return new Promise(resolve => + WebrtcGlobalInformation.getLogging("", log => resolve(log))); +} + +// Begin initial data queries as page loads. Store returned Promises for +// later use. +var reportsRetrieved = getStats(); +var logRetrieved = getLog(); + +function onLoad() { + document.title = getString("document_title"); + let controls = document.querySelector("#controls"); + if (controls) { + let set = ControlSet.render(); + ControlSet.add(new SavePage()); + ControlSet.add(new DebugMode()); + ControlSet.add(new AecLogging()); + controls.appendChild(set); + } + + let contentElem = document.querySelector("#content"); + if (!contentElem) { + return; + } + + let contentInit = function(data) { + AboutWebRTC.init(onClearStats, onClearLog); + AboutWebRTC.render(contentElem, data); + }; + + Promise.all([reportsRetrieved, logRetrieved]) + .then(([stats, log]) => contentInit({reports: stats.reports, log: log})) + .catch(error => contentInit({error: error})); +} + +function onClearLog() { + WebrtcGlobalInformation.clearLogging(); + getLog() + .then(log => AboutWebRTC.refresh({log: log})) + .catch(error => AboutWebRTC.refresh({logError: error})); +} + +function onClearStats() { + WebrtcGlobalInformation.clearAllStats(); + getStats() + .then(stats => AboutWebRTC.refresh({reports: stats.reports})) + .catch(error => AboutWebRTC.refresh({reportError: error})); +} + +var ControlSet = { + render: function() { + let controls = document.createElement("div"); + let control = document.createElement("div"); + let message = document.createElement("div"); + + controls.className = "controls"; + control.className = "control"; + message.className = "message"; + controls.appendChild(control); + controls.appendChild(message); + + this.controlSection = control; + this.messageSection = message; + return controls; + }, + + add: function(controlObj) { + let [controlElem, messageElem] = controlObj.render(); + this.controlSection.appendChild(controlElem); + this.messageSection.appendChild(messageElem); + } +}; + +function Control() { + this._label = null; + this._message = null; + this._messageHeader = null; +} + +Control.prototype = { + render: function () { + let controlElem = document.createElement("button"); + let messageElem = document.createElement("p"); + + this.ctrl = controlElem; + controlElem.onclick = this.onClick.bind(this); + this.msg = messageElem; + this.update(); + + return [controlElem, messageElem]; + }, + + set label(val) { + return this._labelVal = val || "\xA0"; + }, + + get label() { + return this._labelVal; + }, + + set message(val) { + return this._messageVal = val; + }, + + get message() { + return this._messageVal; + }, + + update: function() { + this.ctrl.textContent = this._label; + + this.msg.textContent = ""; + if (this._message) { + this.msg.appendChild(Object.assign(document.createElement("span"), { + className: "info-label", + textContent: `${this._messageHeader}: `, + })); + this.msg.appendChild(document.createTextNode(this._message)); + } + }, + + onClick: function(event) { + return true; + } +}; + +function SavePage() { + Control.call(this); + this._messageHeader = getString("save_page_label"); + this._label = getString("save_page_label"); +} + +SavePage.prototype = Object.create(Control.prototype); +SavePage.prototype.constructor = SavePage; + +SavePage.prototype.onClick = function() { + let content = document.querySelector("#content"); + + if (!content) + return; + + FoldEffect.expandAll(); + FilePicker.init(window, getString("save_page_dialog_title"), FilePicker.modeSave); + FilePicker.defaultString = LOGFILE_NAME_DEFAULT; + let rv = FilePicker.show(); + + if (rv == FilePicker.returnOK || rv == FilePicker.returnReplace) { + let fout = FileUtils.openAtomicFileOutputStream( + FilePicker.file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE); + + let nodes = content.querySelectorAll(".no-print"); + let noPrintList = []; + for (let node of nodes) { + noPrintList.push(node); + node.style.setProperty("display", "none"); + } + + fout.write(content.outerHTML, content.outerHTML.length); + FileUtils.closeAtomicFileOutputStream(fout); + + for (let node of noPrintList) { + node.style.removeProperty("display"); + } + + this._message = formatString("save_page_msg", [FilePicker.file.path], 1); + this.update(); + } +}; + +function DebugMode() { + Control.call(this); + this._messageHeader = getString("debug_mode_msg_label"); + + if (WebrtcGlobalInformation.debugLevel > 0) { + this.onState(); + } else { + this._label = getString("debug_mode_off_state_label"); + this._message = null; + } +} + +DebugMode.prototype = Object.create(Control.prototype); +DebugMode.prototype.constructor = DebugMode; + +DebugMode.prototype.onState = function() { + this._label = getString("debug_mode_on_state_label"); + try { + let file = Services.prefs.getCharPref("media.webrtc.debug.log_file"); + this._message = formatString("debug_mode_on_state_msg", [file], 1); + } catch (e) { + this._message = null; + } +}; + +DebugMode.prototype.offState = function() { + this._label = getString("debug_mode_off_state_label"); + try { + let file = Services.prefs.getCharPref("media.webrtc.debug.log_file"); + this._message = formatString("debug_mode_off_state_msg", [file], 1); + } catch (e) { + this._message = null; + } +}; + +DebugMode.prototype.onClick = function() { + if (WebrtcGlobalInformation.debugLevel > 0) { + WebrtcGlobalInformation.debugLevel = 0; + this.offState(); + } else { + WebrtcGlobalInformation.debugLevel = WEBRTC_TRACE_ALL; + this.onState(); + } + + this.update(); +}; + +function AecLogging() { + Control.call(this); + this._messageHeader = getString("aec_logging_msg_label"); + + if (WebrtcGlobalInformation.aecDebug) { + this.onState(); + } else { + this._label = getString("aec_logging_off_state_label"); + this._message = null; + } +} + +AecLogging.prototype = Object.create(Control.prototype); +AecLogging.prototype.constructor = AecLogging; + +AecLogging.prototype.offState = function () { + this._label = getString("aec_logging_off_state_label"); + try { + let file = Services.prefs.getCharPref("media.webrtc.debug.aec_log_dir"); + this._message = formatString("aec_logging_off_state_msg", [file], 1); + } catch (e) { + this._message = null; + } +}; + +AecLogging.prototype.onState = function () { + this._label = getString("aec_logging_on_state_label"); + try { + let file = Services.prefs.getCharPref("media.webrtc.debug.aec_log_dir"); + this._message = getString("aec_logging_on_state_msg"); + } catch (e) { + this._message = null; + } +}; + +AecLogging.prototype.onClick = function () { + if (WebrtcGlobalInformation.aecDebug) { + WebrtcGlobalInformation.aecDebug = false; + this.offState(); + } else { + WebrtcGlobalInformation.aecDebug = true; + this.onState(); + } + this.update(); +}; + +var AboutWebRTC = { + _reports: [], + _log: [], + + init: function(onClearStats, onClearLog) { + this._onClearStats = onClearStats; + this._onClearLog = onClearLog; + }, + + render: function(parent, data) { + this._content = parent; + this._setData(data); + + if (data.error) { + let msg = document.createElement("h3"); + msg.textContent = getString("cannot_retrieve_log"); + parent.appendChild(msg); + msg = document.createElement("p"); + msg.textContent = `${data.error.name}: ${data.error.message}`; + parent.appendChild(msg); + return; + } + + this._peerConnections = this.renderPeerConnections(); + this._connectionLog = this.renderConnectionLog(); + this._content.appendChild(this._peerConnections); + this._content.appendChild(this._connectionLog); + }, + + _setData: function(data) { + if (data.reports) { + this._reports = data.reports; + } + + if (data.log) { + this._log = data.log; + } + }, + + refresh: function(data) { + this._setData(data); + let pc = this._peerConnections; + this._peerConnections = this.renderPeerConnections(); + let log = this._connectionLog; + this._connectionLog = this.renderConnectionLog(); + this._content.replaceChild(this._peerConnections, pc); + this._content.replaceChild(this._connectionLog, log); + }, + + renderPeerConnections: function() { + let connections = document.createElement("div"); + connections.className = "stats"; + + let heading = document.createElement("span"); + heading.className = "section-heading"; + let elem = document.createElement("h3"); + elem.textContent = getString("stats_heading"); + heading.appendChild(elem); + + elem = document.createElement("button"); + elem.textContent = "Clear History"; + elem.className = "no-print"; + elem.onclick = this._onClearStats; + heading.appendChild(elem); + connections.appendChild(heading); + + if (!this._reports || !this._reports.length) { + return connections; + } + + let reports = [...this._reports]; + reports.sort((a, b) => b.timestamp - a.timestamp); + for (let report of reports) { + let peerConnection = new PeerConnection(report); + connections.appendChild(peerConnection.render()); + } + + return connections; + }, + + renderConnectionLog: function() { + let content = document.createElement("div"); + content.className = "log"; + + let heading = document.createElement("span"); + heading.className = "section-heading"; + let elem = document.createElement("h3"); + elem.textContent = getString("log_heading"); + heading.appendChild(elem); + elem = document.createElement("button"); + elem.textContent = "Clear Log"; + elem.className = "no-print"; + elem.onclick = this._onClearLog; + heading.appendChild(elem); + content.appendChild(heading); + + if (!this._log || !this._log.length) { + return content; + } + + let div = document.createElement("div"); + let sectionCtrl = document.createElement("div"); + sectionCtrl.className = "section-ctrl no-print"; + let foldEffect = new FoldEffect(div, { + showMsg: getString("log_show_msg"), + hideMsg: getString("log_hide_msg") + }); + sectionCtrl.appendChild(foldEffect.render()); + content.appendChild(sectionCtrl); + + for (let line of this._log) { + elem = document.createElement("p"); + elem.textContent = line; + div.appendChild(elem); + } + + content.appendChild(div); + return content; + } +}; + +function PeerConnection(report) { + this._report = report; +} + +PeerConnection.prototype = { + render: function() { + let pc = document.createElement("div"); + pc.className = "peer-connection"; + pc.appendChild(this.renderHeading()); + + let div = document.createElement("div"); + let sectionCtrl = document.createElement("div"); + sectionCtrl.className = "section-ctrl no-print"; + let foldEffect = new FoldEffect(div); + sectionCtrl.appendChild(foldEffect.render()); + pc.appendChild(sectionCtrl); + + div.appendChild(this.renderDesc()); + div.appendChild(new ICEStats(this._report).render()); + div.appendChild(new SDPStats(this._report).render()); + div.appendChild(new RTPStats(this._report).render()); + + pc.appendChild(div); + return pc; + }, + + renderHeading: function () { + let pcInfo = this.getPCInfo(this._report); + let heading = document.createElement("h3"); + let now = new Date(this._report.timestamp).toTimeString(); + heading.textContent = + `[ ${pcInfo.id} ] ${pcInfo.url} ${pcInfo.closed ? `(${getString("connection_closed")})` : ""} ${now}`; + return heading; + }, + + renderDesc: function() { + let info = document.createElement("div"); + let label = document.createElement("span"); + let body = document.createElement("span"); + + label.className = "info-label"; + label.textContent = `${getString("peer_connection_id_label")}: `; + info.appendChild(label); + + body.className = "info-body"; + body.textContent = this._report.pcid; + info.appendChild(body); + + return info; + }, + + getPCInfo: function(report) { + return { + id: report.pcid.match(/id=(\S+)/)[1], + url: report.pcid.match(/url=([^)]+)/)[1], + closed: report.closed + }; + } +}; + +function SDPStats(report) { + this._report = report; +} + +SDPStats.prototype = { + render: function() { + let div = document.createElement("div"); + let elem = document.createElement("h4"); + + elem.textContent = getString("sdp_heading"); + div.appendChild(elem); + + elem = document.createElement("h5"); + elem.textContent = getString("local_sdp_heading"); + div.appendChild(elem); + + elem = document.createElement("pre"); + elem.textContent = this._report.localSdp; + div.appendChild(elem); + + elem = document.createElement("h5"); + elem.textContent = getString("remote_sdp_heading"); + div.appendChild(elem); + + elem = document.createElement("pre"); + elem.textContent = this._report.remoteSdp; + div.appendChild(elem); + + return div; + } +}; + +function RTPStats(report) { + this._report = report; + this._stats = []; +} + +RTPStats.prototype = { + render: function() { + let div = document.createElement("div"); + let heading = document.createElement("h4"); + + heading.textContent = getString("rtp_stats_heading"); + div.appendChild(heading); + + this.generateRTPStats(); + + for (let statSet of this._stats) { + div.appendChild(this.renderRTPStatSet(statSet)); + } + + return div; + }, + + generateRTPStats: function() { + let remoteRtpStats = {}; + let rtpStats = [].concat((this._report.inboundRTPStreamStats || []), + (this._report.outboundRTPStreamStats || [])); + + // Generate an id-to-streamStat index for each streamStat that is marked + // as a remote. This will be used next to link the remote to its local side. + for (let stats of rtpStats) { + if (stats.isRemote) { + remoteRtpStats[stats.id] = stats; + } + } + + // If a streamStat has a remoteId attribute, create a remoteRtpStats + // attribute that references the remote streamStat entry directly. + // That is, the index generated above is merged into the returned list. + for (let stats of rtpStats) { + if (stats.remoteId) { + stats.remoteRtpStats = remoteRtpStats[stats.remoteId]; + } + } + + this._stats = rtpStats; + }, + + renderAvStats: function(stats) { + let statsString = ""; + + if (stats.mozAvSyncDelay) { + statsString += `${getString("av_sync_label")}: ${stats.mozAvSyncDelay} ms `; + } + if (stats.mozJitterBufferDelay) { + statsString += `${getString("jitter_buffer_delay_label")}: ${stats.mozJitterBufferDelay} ms`; + } + + let line = document.createElement("p"); + line.textContent = statsString; + return line; + }, + + renderCoderStats: function(stats) { + let statsString = ""; + let label; + + if (stats.bitrateMean) { + statsString += ` ${getString("avg_bitrate_label")}: ${(stats.bitrateMean / 1000000).toFixed(2)} Mbps`; + if (stats.bitrateStdDev) { + statsString += ` (${(stats.bitrateStdDev / 1000000).toFixed(2)} SD)`; + } + } + + if (stats.framerateMean) { + statsString += ` ${getString("avg_framerate_label")}: ${(stats.framerateMean).toFixed(2)} fps`; + if (stats.framerateStdDev) { + statsString += ` (${stats.framerateStdDev.toFixed(2)} SD)`; + } + } + + if (stats.droppedFrames) { + statsString += ` ${getString("dropped_frames_label")}: ${stats.droppedFrames}`; + } + if (stats.discardedPackets) { + statsString += ` ${getString("discarded_packets_label")}: ${stats.discardedPackets}`; + } + + if (statsString) { + label = (stats.packetsReceived ? ` ${getString("decoder_label")}:` : ` ${getString("encoder_label")}:`); + statsString = label + statsString; + } + + let line = document.createElement("p"); + line.textContent = statsString; + return line; + }, + + renderTransportStats: function(stats, typeLabel) { + let time = new Date(stats.timestamp).toTimeString(); + let statsString = `${typeLabel}: ${time} ${stats.type} SSRC: ${stats.ssrc}`; + + if (stats.packetsReceived) { + statsString += ` ${getString("received_label")}: ${stats.packetsReceived} ${getString("packets")}`; + + if (stats.bytesReceived) { + statsString += ` (${(stats.bytesReceived / 1024).toFixed(2)} Kb)`; + } + + statsString += ` ${getString("lost_label")}: ${stats.packetsLost} ${getString("jitter_label")}: ${stats.jitter}`; + + if (stats.mozRtt) { + statsString += ` RTT: ${stats.mozRtt} ms`; + } + } else if (stats.packetsSent) { + statsString += ` ${getString("sent_label")}: ${stats.packetsSent} ${getString("packets")}`; + if (stats.bytesSent) { + statsString += ` (${(stats.bytesSent / 1024).toFixed(2)} Kb)`; + } + } + + let line = document.createElement("p"); + line.textContent = statsString; + return line; + }, + + renderRTPStatSet: function(stats) { + let div = document.createElement("div"); + let heading = document.createElement("h5"); + + heading.textContent = stats.id; + div.appendChild(heading); + + if (stats.MozAvSyncDelay || stats.mozJitterBufferDelay) { + div.appendChild(this.renderAvStats(stats)); + } + + div.appendChild(this.renderCoderStats(stats)); + div.appendChild(this.renderTransportStats(stats, getString("typeLocal"))); + + if (stats.remoteId && stats.remoteRtpStats) { + div.appendChild(this.renderTransportStats(stats.remoteRtpStats, getString("typeRemote"))); + } + + return div; + }, +}; + +function ICEStats(report) { + this._report = report; +} + +ICEStats.prototype = { + render: function() { + let tbody = []; + for (let stat of this.generateICEStats()) { + tbody.push([ + stat.localcandidate || "", + stat.remotecandidate || "", + stat.state || "", + stat.priority || "", + stat.nominated || "", + stat.selected || "" + ]); + } + + let statsTable = new SimpleTable( + [getString("local_candidate"), getString("remote_candidate"), getString("ice_state"), + getString("priority"), getString("nominated"), getString("selected")], + tbody); + + let div = document.createElement("div"); + let heading = document.createElement("h4"); + + heading.textContent = getString("ice_stats_heading"); + div.appendChild(heading); + div.appendChild(statsTable.render()); + + return div; + }, + + generateICEStats: function() { + // Create an index based on candidate ID for each element in the + // iceCandidateStats array. + let candidates = new Map(); + + for (let candidate of this._report.iceCandidateStats) { + candidates.set(candidate.id, candidate); + } + + // A component may have a remote or local candidate address or both. + // Combine those with both; these will be the peer candidates. + let matched = {}; + let stats = []; + let stat; + + for (let pair of this._report.iceCandidatePairStats) { + let local = candidates.get(pair.localCandidateId); + let remote = candidates.get(pair.remoteCandidateId); + + if (local) { + stat = { + localcandidate: this.candidateToString(local), + state: pair.state, + priority: pair.priority, + nominated: pair.nominated, + selected: pair.selected + }; + matched[local.id] = true; + + if (remote) { + stat.remotecandidate = this.candidateToString(remote); + matched[remote.id] = true; + } + stats.push(stat); + } + } + + for (let c of candidates.values()) { + if (matched[c.id]) + continue; + + stat = {}; + stat[c.type] = this.candidateToString(c); + stats.push(stat); + } + + return stats.sort((a, b) => (b.priority || 0) - (a.priority || 0)); + }, + + candidateToString: function(c) { + if (!c) { + return "*"; + } + + var type = c.candidateType; + + if (c.type == "localcandidate" && c.candidateType == "relayed") { + type = `${c.candidateType}-${c.mozLocalTransport}`; + } + + return `${c.ipAddress}:${c.portNumber}/${c.transport}(${type})`; + } +}; + +function SimpleTable(heading, data) { + this._heading = heading || []; + this._data = data; +} + +SimpleTable.prototype = { + renderRow: function(list) { + let row = document.createElement("tr"); + + for (let elem of list) { + let cell = document.createElement("td"); + cell.textContent = elem; + row.appendChild(cell); + } + + return row; + }, + + render: function() { + let table = document.createElement("table"); + + if (this._heading) { + table.appendChild(this.renderRow(this._heading)); + } + + for (let row of this._data) { + table.appendChild(this.renderRow(row)); + } + + return table; + } +}; + +function FoldEffect(targetElem, options = {}) { + if (targetElem) { + this._showMsg = "\u25BC " + (options.showMsg || getString("fold_show_msg")); + this._showHint = options.showHint || getString("fold_show_hint"); + this._hideMsg = "\u25B2 " + (options.hideMsg || getString("fold_hide_msg")); + this._hideHint = options.hideHint || getString("fold_hide_hint"); + this._target = targetElem; + } +} + +FoldEffect.prototype = { + render: function() { + this._target.classList.add("fold-target"); + + let ctrl = document.createElement("div"); + this._trigger = ctrl; + ctrl.className = "fold-trigger"; + ctrl.addEventListener("click", this.onClick.bind(this)); + this.close(); + + FoldEffect._sections.push(this); + return ctrl; + }, + + onClick: function() { + if (this._target.classList.contains("fold-closed")) { + this.open(); + } else { + this.close(); + } + return true; + }, + + open: function() { + this._target.classList.remove("fold-closed"); + this._trigger.setAttribute("title", this._hideHint); + this._trigger.textContent = this._hideMsg; + }, + + close: function() { + this._target.classList.add("fold-closed"); + this._trigger.setAttribute("title", this._showHint); + this._trigger.textContent = this._showMsg; + } +}; + +FoldEffect._sections = []; + +FoldEffect.expandAll = function() { + for (let section of this._sections) { + section.open(); + } +}; + +FoldEffect.collapseAll = function() { + for (let section of this._sections) { + section.close(); + } +}; diff --git a/components/global/content/autocomplete.css b/components/global/content/autocomplete.css new file mode 100644 index 000000000..11b36ddac --- /dev/null +++ b/components/global/content/autocomplete.css @@ -0,0 +1,40 @@ +/* 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"); +@namespace html url("http://www.w3.org/1999/xhtml"); + +/* Apply crisp rendering for favicons at exactly 2dppx resolution */ +@media (resolution: 2dppx) { + .ac-site-icon { + image-rendering: -moz-crisp-edges; + } +} + +richlistitem { + -moz-box-orient: horizontal; + overflow: hidden; +} + +.ac-title-text, +.ac-tags-text, +.ac-url-text, +.ac-action-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ac-tags[empty] { + display: none; +} + +.ac-action[actiontype=searchengine]:not([selected]), +.ac-separator[actiontype=searchengine]:not([selected]) { + display: none; +} + +.ac-separator[type=keyword] { + display: none; +} diff --git a/components/global/content/browser-child.js b/components/global/content/browser-child.js new file mode 100644 index 000000000..ffb07dde2 --- /dev/null +++ b/components/global/content/browser-child.js @@ -0,0 +1,598 @@ +/* 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 Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; + +Cu.import("resource://gre/modules/BrowserUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import("resource://gre/modules/RemoteAddonsChild.jsm"); +Cu.import("resource://gre/modules/Timer.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PageThumbUtils", + "resource://gre/modules/PageThumbUtils.jsm"); + +function makeInputStream(aString) { + let stream = Cc["@mozilla.org/io/string-input-stream;1"]. + createInstance(Ci.nsISupportsCString); + stream.data = aString; + return stream; // XPConnect will QI this to nsIInputStream for us. +} + +var WebProgressListener = { + init: function() { + this._filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"] + .createInstance(Ci.nsIWebProgress); + this._filter.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL); + + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + webProgress.addProgressListener(this._filter, Ci.nsIWebProgress.NOTIFY_ALL); + }, + + uninit() { + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + webProgress.removeProgressListener(this._filter); + + this._filter.removeProgressListener(this); + this._filter = null; + }, + + _requestSpec: function (aRequest, aPropertyName) { + if (!aRequest || !(aRequest instanceof Ci.nsIChannel)) + return null; + return aRequest.QueryInterface(Ci.nsIChannel)[aPropertyName].spec; + }, + + _setupJSON: function setupJSON(aWebProgress, aRequest) { + let innerWindowID = null; + if (aWebProgress) { + let domWindowID = null; + try { + let utils = aWebProgress.DOMWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + domWindowID = utils.outerWindowID; + innerWindowID = utils.currentInnerWindowID; + } catch (e) { + // If nsDocShell::Destroy has already been called, then we'll + // get NS_NOINTERFACE when trying to get the DOM window. + // If there is no current inner window, we'll get + // NS_ERROR_NOT_AVAILABLE. + } + + aWebProgress = { + isTopLevel: aWebProgress.isTopLevel, + isLoadingDocument: aWebProgress.isLoadingDocument, + loadType: aWebProgress.loadType, + DOMWindowID: domWindowID + }; + } + + return { + webProgress: aWebProgress || null, + requestURI: this._requestSpec(aRequest, "URI"), + originalRequestURI: this._requestSpec(aRequest, "originalURI"), + documentContentType: content.document && content.document.contentType, + innerWindowID, + }; + }, + + _setupObjects: function setupObjects(aWebProgress, aRequest) { + let domWindow; + try { + domWindow = aWebProgress && aWebProgress.DOMWindow; + } catch (e) { + // If nsDocShell::Destroy has already been called, then we'll + // get NS_NOINTERFACE when trying to get the DOM window. Ignore + // that here. + domWindow = null; + } + + return { + contentWindow: content, + // DOMWindow is not necessarily the content-window with subframes. + DOMWindow: domWindow, + webProgress: aWebProgress, + request: aRequest, + }; + }, + + _send(name, data, objects) { + if (RemoteAddonsChild.useSyncWebProgress) { + sendRpcMessage(name, data, objects); + } else { + sendAsyncMessage(name, data, objects); + } + }, + + onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { + let json = this._setupJSON(aWebProgress, aRequest); + let objects = this._setupObjects(aWebProgress, aRequest); + + json.stateFlags = aStateFlags; + json.status = aStatus; + + // It's possible that this state change was triggered by + // loading an internal error page, for which the parent + // will want to know some details, so we'll update it with + // the documentURI. + if (aWebProgress && aWebProgress.isTopLevel) { + json.documentURI = content.document.documentURIObject.spec; + json.charset = content.document.characterSet; + json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu; + json.inLoadURI = WebNavigation.inLoadURI; + } + + this._send("Content:StateChange", json, objects); + }, + + onProgressChange: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) { + let json = this._setupJSON(aWebProgress, aRequest); + let objects = this._setupObjects(aWebProgress, aRequest); + + json.curSelf = aCurSelf; + json.maxSelf = aMaxSelf; + json.curTotal = aCurTotal; + json.maxTotal = aMaxTotal; + + this._send("Content:ProgressChange", json, objects); + }, + + onProgressChange64: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) { + this.onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal); + }, + + onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) { + let json = this._setupJSON(aWebProgress, aRequest); + let objects = this._setupObjects(aWebProgress, aRequest); + + json.location = aLocationURI ? aLocationURI.spec : ""; + json.flags = aFlags; + + // These properties can change even for a sub-frame navigation. + let webNav = docShell.QueryInterface(Ci.nsIWebNavigation); + json.canGoBack = webNav.canGoBack; + json.canGoForward = webNav.canGoForward; + + if (aWebProgress && aWebProgress.isTopLevel) { + json.documentURI = content.document.documentURIObject.spec; + json.title = content.document.title; + json.charset = content.document.characterSet; + json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu; + json.principal = content.document.nodePrincipal; + json.synthetic = content.document.mozSyntheticDocument; + json.inLoadURI = WebNavigation.inLoadURI; + } + + this._send("Content:LocationChange", json, objects); + }, + + onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) { + let json = this._setupJSON(aWebProgress, aRequest); + let objects = this._setupObjects(aWebProgress, aRequest); + + json.status = aStatus; + json.message = aMessage; + + this._send("Content:StatusChange", json, objects); + }, + + onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) { + let json = this._setupJSON(aWebProgress, aRequest); + let objects = this._setupObjects(aWebProgress, aRequest); + + json.state = aState; + json.status = SecurityUI.getSSLStatusAsString(); + + this._send("Content:SecurityChange", json, objects); + }, + + onRefreshAttempted: function onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) { + return true; + }, + + sendLoadCallResult() { + sendAsyncMessage("Content:LoadURIResult"); + }, + + QueryInterface: function QueryInterface(aIID) { + if (aIID.equals(Ci.nsIWebProgressListener) || + aIID.equals(Ci.nsIWebProgressListener2) || + aIID.equals(Ci.nsISupportsWeakReference) || + aIID.equals(Ci.nsISupports)) { + return this; + } + + throw Components.results.NS_ERROR_NO_INTERFACE; + } +}; + +WebProgressListener.init(); +addEventListener("unload", () => { + WebProgressListener.uninit(); +}); + +var WebNavigation = { + init: function() { + addMessageListener("WebNavigation:GoBack", this); + addMessageListener("WebNavigation:GoForward", this); + addMessageListener("WebNavigation:GotoIndex", this); + addMessageListener("WebNavigation:LoadURI", this); + addMessageListener("WebNavigation:SetOriginAttributes", this); + addMessageListener("WebNavigation:Reload", this); + addMessageListener("WebNavigation:Stop", this); + }, + + get webNavigation() { + return docShell.QueryInterface(Ci.nsIWebNavigation); + }, + + _inLoadURI: false, + + get inLoadURI() { + return this._inLoadURI; + }, + + receiveMessage: function(message) { + switch (message.name) { + case "WebNavigation:GoBack": + this.goBack(); + break; + case "WebNavigation:GoForward": + this.goForward(); + break; + case "WebNavigation:GotoIndex": + this.gotoIndex(message.data.index); + break; + case "WebNavigation:LoadURI": + this.loadURI(message.data.uri, message.data.flags, + message.data.referrer, message.data.referrerPolicy, + message.data.postData, message.data.headers, + message.data.baseURI); + break; + case "WebNavigation:SetOriginAttributes": + this.setOriginAttributes(message.data.originAttributes); + break; + case "WebNavigation:Reload": + this.reload(message.data.flags); + break; + case "WebNavigation:Stop": + this.stop(message.data.flags); + break; + } + }, + + _wrapURIChangeCall(fn) { + this._inLoadURI = true; + try { + fn(); + } finally { + this._inLoadURI = false; + WebProgressListener.sendLoadCallResult(); + } + }, + + goBack: function() { + if (this.webNavigation.canGoBack) { + this._wrapURIChangeCall(() => this.webNavigation.goBack()); + } + }, + + goForward: function() { + if (this.webNavigation.canGoForward) { + this._wrapURIChangeCall(() => this.webNavigation.goForward()); + } + }, + + gotoIndex: function(index) { + this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(index)); + }, + + loadURI: function(uri, flags, referrer, referrerPolicy, postData, headers, baseURI) { + if (referrer) + referrer = Services.io.newURI(referrer, null, null); + if (postData) + postData = makeInputStream(postData); + if (headers) + headers = makeInputStream(headers); + if (baseURI) + baseURI = Services.io.newURI(baseURI, null, null); + this._wrapURIChangeCall(() => { + return this.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy, + postData, headers, baseURI); + }); + }, + + setOriginAttributes: function(originAttributes) { + if (originAttributes) { + this.webNavigation.setOriginAttributesBeforeLoading(originAttributes); + } + }, + + reload: function(flags) { + this.webNavigation.reload(flags); + }, + + stop: function(flags) { + this.webNavigation.stop(flags); + } +}; + +WebNavigation.init(); + +var SecurityUI = { + getSSLStatusAsString: function() { + let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus; + + if (status) { + let helper = Cc["@mozilla.org/network/serialization-helper;1"] + .getService(Ci.nsISerializationHelper); + + status.QueryInterface(Ci.nsISerializable); + return helper.serializeToString(status); + } + + return null; + } +}; + +var ControllerCommands = { + init: function () { + addMessageListener("ControllerCommands:Do", this); + addMessageListener("ControllerCommands:DoWithParams", this); + }, + + receiveMessage: function(message) { + switch (message.name) { + case "ControllerCommands:Do": + if (docShell.isCommandEnabled(message.data)) + docShell.doCommand(message.data); + break; + + case "ControllerCommands:DoWithParams": + var data = message.data; + if (docShell.isCommandEnabled(data.cmd)) { + var params = Cc["@mozilla.org/embedcomp/command-params;1"]. + createInstance(Ci.nsICommandParams); + for (var name in data.params) { + var value = data.params[name]; + if (value.type == "long") { + params.setLongValue(name, parseInt(value.value)); + } else { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + } + } + docShell.doCommandWithParams(data.cmd, params); + } + break; + } + } +} + +ControllerCommands.init() + +addEventListener("DOMTitleChanged", function (aEvent) { + let document = content.document; + switch (aEvent.type) { + case "DOMTitleChanged": + if (!aEvent.isTrusted || aEvent.target.defaultView != content) + return; + + sendAsyncMessage("DOMTitleChanged", { title: document.title }); + break; + } +}, false); + +addEventListener("DOMWindowClose", function (aEvent) { + if (!aEvent.isTrusted) + return; + sendAsyncMessage("DOMWindowClose"); +}, false); + +addEventListener("ImageContentLoaded", function (aEvent) { + if (content.document instanceof Ci.nsIImageDocument) { + let req = content.document.imageRequest; + if (!req.image) + return; + sendAsyncMessage("ImageDocumentLoaded", { width: req.image.width, + height: req.image.height }); + } +}, false); + +const ZoomManager = { + get fullZoom() { + return this._cache.fullZoom; + }, + + get textZoom() { + return this._cache.textZoom; + }, + + set fullZoom(value) { + this._cache.fullZoom = value; + this._markupViewer.fullZoom = value; + }, + + set textZoom(value) { + this._cache.textZoom = value; + this._markupViewer.textZoom = value; + }, + + refreshFullZoom: function() { + return this._refreshZoomValue('fullZoom'); + }, + + refreshTextZoom: function() { + return this._refreshZoomValue('textZoom'); + }, + + /** + * Retrieves specified zoom property value from markupViewer and refreshes + * cache if needed. + * @param valueName Either 'fullZoom' or 'textZoom'. + * @returns Returns true if cached value was actually refreshed. + * @private + */ + _refreshZoomValue: function(valueName) { + let actualZoomValue = this._markupViewer[valueName]; + // Round to remove any floating-point error. + actualZoomValue = Number(actualZoomValue.toFixed(2)); + if (actualZoomValue != this._cache[valueName]) { + this._cache[valueName] = actualZoomValue; + return true; + } + return false; + }, + + get _markupViewer() { + return docShell.contentViewer; + }, + + _cache: { + fullZoom: NaN, + textZoom: NaN + } +}; + +addMessageListener("FullZoom", function (aMessage) { + ZoomManager.fullZoom = aMessage.data.value; +}); + +addMessageListener("TextZoom", function (aMessage) { + ZoomManager.textZoom = aMessage.data.value; +}); + +addEventListener("FullZoomChange", function () { + if (ZoomManager.refreshFullZoom()) { + sendAsyncMessage("FullZoomChange", { value: ZoomManager.fullZoom }); + } +}, false); + +addEventListener("TextZoomChange", function (aEvent) { + if (ZoomManager.refreshTextZoom()) { + sendAsyncMessage("TextZoomChange", { value: ZoomManager.textZoom }); + } +}, false); + +addEventListener("ZoomChangeUsingMouseWheel", function () { + sendAsyncMessage("ZoomChangeUsingMouseWheel", {}); +}, false); + +addMessageListener("UpdateCharacterSet", function (aMessage) { + docShell.charset = aMessage.data.value; + docShell.gatherCharsetMenuTelemetry(); +}); + +/** + * Remote thumbnail request handler for PageThumbs thumbnails. + */ +addMessageListener("Browser:Thumbnail:Request", function (aMessage) { + let snapshot; + let args = aMessage.data.additionalArgs; + let fullScale = args ? args.fullScale : false; + if (fullScale) { + snapshot = PageThumbUtils.createSnapshotThumbnail(content, null, args); + } else { + let snapshotWidth = aMessage.data.canvasWidth; + let snapshotHeight = aMessage.data.canvasHeight; + snapshot = + PageThumbUtils.createCanvas(content, snapshotWidth, snapshotHeight); + PageThumbUtils.createSnapshotThumbnail(content, snapshot, args); + } + + snapshot.toBlob(function (aBlob) { + sendAsyncMessage("Browser:Thumbnail:Response", { + thumbnail: aBlob, + id: aMessage.data.id + }); + }); +}); + +/** + * Remote isSafeForCapture request handler for PageThumbs. + */ +addMessageListener("Browser:Thumbnail:CheckState", function (aMessage) { + let result = PageThumbUtils.shouldStoreContentThumbnail(content, docShell); + sendAsyncMessage("Browser:Thumbnail:CheckState:Response", { + result: result + }); +}); + +/** + * Remote GetOriginalURL request handler for PageThumbs. + */ +addMessageListener("Browser:Thumbnail:GetOriginalURL", function (aMessage) { + let channel = docShell.currentDocumentChannel; + let channelError = PageThumbUtils.isChannelErrorResponse(channel); + let originalURL; + try { + originalURL = channel.originalURI.spec; + } catch (ex) {} + sendAsyncMessage("Browser:Thumbnail:GetOriginalURL:Response", { + channelError: channelError, + originalURL: originalURL, + }); +}); + +/** + * Remote createAboutBlankContentViewer request handler. + */ +addMessageListener("Browser:CreateAboutBlank", function(aMessage) { + if (!content.document || content.document.documentURI != "about:blank") { + throw new Error("Can't create a content viewer unless on about:blank"); + } + let principal = aMessage.data; + principal = BrowserUtils.principalWithMatchingOA(principal, content.document.nodePrincipal); + docShell.createAboutBlankContentViewer(principal); +}); + +// The AddonsChild needs to be rooted so that it stays alive as long as +// the tab. +var AddonsChild = RemoteAddonsChild.init(this); +if (AddonsChild) { + addEventListener("unload", () => { + RemoteAddonsChild.uninit(AddonsChild); + }); +} + +addMessageListener("NetworkPrioritizer:AdjustPriority", (msg) => { + let webNav = docShell.QueryInterface(Ci.nsIWebNavigation); + let loadGroup = webNav.QueryInterface(Ci.nsIDocumentLoader) + .loadGroup.QueryInterface(Ci.nsISupportsPriority); + loadGroup.adjustPriority(msg.data.adjustment); +}); + +addMessageListener("NetworkPrioritizer:SetPriority", (msg) => { + let webNav = docShell.QueryInterface(Ci.nsIWebNavigation); + let loadGroup = webNav.QueryInterface(Ci.nsIDocumentLoader) + .loadGroup.QueryInterface(Ci.nsISupportsPriority); + loadGroup.priority = msg.data.priority; +}); + +addMessageListener("InPermitUnload", msg => { + let inPermitUnload = docShell.contentViewer && docShell.contentViewer.inPermitUnload; + sendAsyncMessage("InPermitUnload", {id: msg.data.id, inPermitUnload}); +}); + +addMessageListener("PermitUnload", msg => { + sendAsyncMessage("PermitUnload", {id: msg.data.id, kind: "start"}); + + let permitUnload = true; + if (docShell && docShell.contentViewer) { + permitUnload = docShell.contentViewer.permitUnload(); + } + + sendAsyncMessage("PermitUnload", {id: msg.data.id, kind: "end", permitUnload}); +}); + +// We may not get any responses to Browser:Init if the browser element +// is torn down too quickly. +var outerWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .outerWindowID; +sendAsyncMessage("Browser:Init", {outerWindowID: outerWindowID}); diff --git a/components/global/content/browser-content.js b/components/global/content/browser-content.js new file mode 100644 index 000000000..d6185c5dd --- /dev/null +++ b/components/global/content/browser-content.js @@ -0,0 +1,1777 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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 Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", + "resource://gre/modules/ReaderMode.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", + "resource://gre/modules/BrowserUtils.jsm"); + +var global = this; + + +// Lazily load the finder code +addMessageListener("Finder:Initialize", function () { + let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {}); + new RemoteFinderListener(global); +}); + +var ClickEventHandler = { + init: function init() { + this._scrollable = null; + this._scrolldir = ""; + this._startX = null; + this._startY = null; + this._screenX = null; + this._screenY = null; + this._lastFrame = null; + this.autoscrollLoop = this.autoscrollLoop.bind(this); + + Services.els.addSystemEventListener(global, "mousedown", this, true); + + addMessageListener("Autoscroll:Stop", this); + }, + + isAutoscrollBlocker: function(node) { + let mmPaste = Services.prefs.getBoolPref("middlemouse.paste"); + let mmScrollbarPosition = Services.prefs.getBoolPref("middlemouse.scrollbarPosition"); + + while (node) { + if ((node instanceof content.HTMLAnchorElement || node instanceof content.HTMLAreaElement) && + node.hasAttribute("href")) { + return true; + } + + if (mmPaste && (node instanceof content.HTMLInputElement || + node instanceof content.HTMLTextAreaElement)) { + return true; + } + + if (node instanceof content.XULElement && mmScrollbarPosition + && (node.localName == "scrollbar" || node.localName == "scrollcorner")) { + return true; + } + + node = node.parentNode; + } + return false; + }, + + findNearestScrollableElement: function(aNode) { + // this is a list of overflow property values that allow scrolling + const scrollingAllowed = ['scroll', 'auto']; + + // go upward in the DOM and find any parent element that has a overflow + // area and can therefore be scrolled + for (this._scrollable = aNode; this._scrollable; + this._scrollable = this._scrollable.parentNode) { + // do not use overflow based autoscroll for <html> and <body> + // Elements or non-html elements such as svg or Document nodes + // also make sure to skip select elements that are not multiline + if (!(this._scrollable instanceof content.HTMLElement) || + ((this._scrollable instanceof content.HTMLSelectElement) && !this._scrollable.multiple)) { + continue; + } + + var overflowx = this._scrollable.ownerDocument.defaultView + .getComputedStyle(this._scrollable, '') + .getPropertyValue('overflow-x'); + var overflowy = this._scrollable.ownerDocument.defaultView + .getComputedStyle(this._scrollable, '') + .getPropertyValue('overflow-y'); + // we already discarded non-multiline selects so allow vertical + // scroll for multiline ones directly without checking for a + // overflow property + var scrollVert = this._scrollable.scrollTopMax && + (this._scrollable instanceof content.HTMLSelectElement || + scrollingAllowed.indexOf(overflowy) >= 0); + + // do not allow horizontal scrolling for select elements, it leads + // to visual artifacts and is not the expected behavior anyway + if (!(this._scrollable instanceof content.HTMLSelectElement) && + this._scrollable.scrollLeftMin != this._scrollable.scrollLeftMax && + scrollingAllowed.indexOf(overflowx) >= 0) { + this._scrolldir = scrollVert ? "NSEW" : "EW"; + break; + } else if (scrollVert) { + this._scrolldir = "NS"; + break; + } + } + + if (!this._scrollable) { + this._scrollable = aNode.ownerDocument.defaultView; + if (this._scrollable.scrollMaxX != this._scrollable.scrollMinX) { + this._scrolldir = this._scrollable.scrollMaxY != + this._scrollable.scrollMinY ? "NSEW" : "EW"; + } else if (this._scrollable.scrollMaxY != this._scrollable.scrollMinY) { + this._scrolldir = "NS"; + } else if (this._scrollable.frameElement) { + this.findNearestScrollableElement(this._scrollable.frameElement); + } else { + this._scrollable = null; // abort scrolling + } + } + }, + + startScroll: function(event) { + + this.findNearestScrollableElement(event.originalTarget); + + if (!this._scrollable) + return; + + let [enabled] = sendSyncMessage("Autoscroll:Start", + {scrolldir: this._scrolldir, + screenX: event.screenX, + screenY: event.screenY}); + if (!enabled) { + this._scrollable = null; + return; + } + + Services.els.addSystemEventListener(global, "mousemove", this, true); + addEventListener("pagehide", this, true); + + this._ignoreMouseEvents = true; + this._startX = event.screenX; + this._startY = event.screenY; + this._screenX = event.screenX; + this._screenY = event.screenY; + this._scrollErrorX = 0; + this._scrollErrorY = 0; + this._lastFrame = content.performance.now(); + + content.requestAnimationFrame(this.autoscrollLoop); + }, + + stopScroll: function() { + if (this._scrollable) { + this._scrollable.mozScrollSnap(); + this._scrollable = null; + + Services.els.removeSystemEventListener(global, "mousemove", this, true); + removeEventListener("pagehide", this, true); + } + }, + + accelerate: function(curr, start) { + const speed = 12; + var val = (curr - start) / speed; + + if (val > 1) + return val * Math.sqrt(val) - 1; + if (val < -1) + return val * Math.sqrt(-val) + 1; + return 0; + }, + + roundToZero: function(num) { + if (num > 0) + return Math.floor(num); + return Math.ceil(num); + }, + + autoscrollLoop: function(timestamp) { + if (!this._scrollable) { + // Scrolling has been canceled + return; + } + + // avoid long jumps when the browser hangs for more than + // |maxTimeDelta| ms + const maxTimeDelta = 100; + var timeDelta = Math.min(maxTimeDelta, timestamp - this._lastFrame); + // we used to scroll |accelerate()| pixels every 20ms (50fps) + var timeCompensation = timeDelta / 20; + this._lastFrame = timestamp; + + var actualScrollX = 0; + var actualScrollY = 0; + // don't bother scrolling vertically when the scrolldir is only horizontal + // and the other way around + if (this._scrolldir != 'EW') { + var y = this.accelerate(this._screenY, this._startY) * timeCompensation; + var desiredScrollY = this._scrollErrorY + y; + actualScrollY = this.roundToZero(desiredScrollY); + this._scrollErrorY = (desiredScrollY - actualScrollY); + } + if (this._scrolldir != 'NS') { + var x = this.accelerate(this._screenX, this._startX) * timeCompensation; + var desiredScrollX = this._scrollErrorX + x; + actualScrollX = this.roundToZero(desiredScrollX); + this._scrollErrorX = (desiredScrollX - actualScrollX); + } + + const kAutoscroll = 15; // defined in mozilla/layers/ScrollInputMethods.h + Services.telemetry.getHistogramById("SCROLL_INPUT_METHODS").add(kAutoscroll); + + this._scrollable.scrollBy({ + left: actualScrollX, + top: actualScrollY, + behavior: "instant" + }); + content.requestAnimationFrame(this.autoscrollLoop); + }, + + handleEvent: function(event) { + if (event.type == "mousemove") { + this._screenX = event.screenX; + this._screenY = event.screenY; + } else if (event.type == "mousedown") { + if (event.isTrusted & + !event.defaultPrevented && + event.button == 1 && + !this._scrollable && + !this.isAutoscrollBlocker(event.originalTarget)) { + this.startScroll(event); + } + } else if (event.type == "pagehide") { + if (this._scrollable) { + var doc = + this._scrollable.ownerDocument || this._scrollable.document; + if (doc == event.target) { + sendAsyncMessage("Autoscroll:Cancel"); + } + } + } + }, + + receiveMessage: function(msg) { + switch (msg.name) { + case "Autoscroll:Stop": { + this.stopScroll(); + break; + } + } + }, +}; +ClickEventHandler.init(); + +var PopupBlocking = { + popupData: null, + popupDataInternal: null, + + init: function() { + addEventListener("DOMPopupBlocked", this, true); + addEventListener("pageshow", this, true); + addEventListener("pagehide", this, true); + + addMessageListener("PopupBlocking:UnblockPopup", this); + addMessageListener("PopupBlocking:GetBlockedPopupList", this); + }, + + receiveMessage: function(msg) { + switch (msg.name) { + case "PopupBlocking:UnblockPopup": { + let i = msg.data.index; + if (this.popupData && this.popupData[i]) { + let data = this.popupData[i]; + let internals = this.popupDataInternal[i]; + let dwi = internals.requestingWindow; + + // If we have a requesting window and the requesting document is + // still the current document, open the popup. + if (dwi && dwi.document == internals.requestingDocument) { + dwi.open(data.popupWindowURIspec, data.popupWindowName, data.popupWindowFeatures); + } + } + break; + } + + case "PopupBlocking:GetBlockedPopupList": { + let popupData = []; + let length = this.popupData ? this.popupData.length : 0; + + // Limit 15 popup URLs to be reported through the UI + length = Math.min(length, 15); + + for (let i = 0; i < length; i++) { + let popupWindowURIspec = this.popupData[i].popupWindowURIspec; + + if (popupWindowURIspec == global.content.location.href) { + popupWindowURIspec = "<self>"; + } else { + // Limit 500 chars to be sent because the URI will be cropped + // by the UI anyway, and data: URIs can be significantly larger. + popupWindowURIspec = popupWindowURIspec.substring(0, 500) + } + + popupData.push({popupWindowURIspec}); + } + + sendAsyncMessage("PopupBlocking:ReplyGetBlockedPopupList", {popupData}); + break; + } + } + }, + + handleEvent: function(ev) { + switch (ev.type) { + case "DOMPopupBlocked": + return this.onPopupBlocked(ev); + case "pageshow": + return this.onPageShow(ev); + case "pagehide": + return this.onPageHide(ev); + } + return undefined; + }, + + onPopupBlocked: function(ev) { + if (!this.popupData) { + this.popupData = new Array(); + this.popupDataInternal = new Array(); + } + + let obj = { + popupWindowURIspec: ev.popupWindowURI ? ev.popupWindowURI.spec : "about:blank", + popupWindowFeatures: ev.popupWindowFeatures, + popupWindowName: ev.popupWindowName + }; + + let internals = { + requestingWindow: ev.requestingWindow, + requestingDocument: ev.requestingWindow.document, + }; + + this.popupData.push(obj); + this.popupDataInternal.push(internals); + this.updateBlockedPopups(true); + }, + + onPageShow: function(ev) { + if (this.popupData) { + let i = 0; + while (i < this.popupData.length) { + // Filter out irrelevant reports. + if (this.popupDataInternal[i].requestingWindow && + (this.popupDataInternal[i].requestingWindow.document == + this.popupDataInternal[i].requestingDocument)) { + i++; + } else { + this.popupData.splice(i, 1); + this.popupDataInternal.splice(i, 1); + } + } + if (this.popupData.length == 0) { + this.popupData = null; + this.popupDataInternal = null; + } + this.updateBlockedPopups(false); + } + }, + + onPageHide: function(ev) { + if (this.popupData) { + this.popupData = null; + this.popupDataInternal = null; + this.updateBlockedPopups(false); + } + }, + + updateBlockedPopups: function(freshPopup) { + sendAsyncMessage("PopupBlocking:UpdateBlockedPopups", + { + count: this.popupData ? this.popupData.length : 0, + freshPopup + }); + }, +}; +PopupBlocking.init(); + +XPCOMUtils.defineLazyGetter(this, "console", () => { + // Set up console.* for frame scripts. + let Console = Components.utils.import("resource://gre/modules/Console.jsm", {}); + return new Console.ConsoleAPI(); +}); + +var Printing = { + // Bug 1088061: nsPrintEngine's DoCommonPrint currently expects the + // progress listener passed to it to QI to an nsIPrintingPromptService + // in order to know that a printing progress dialog has been shown. That's + // really all the interface is used for, hence the fact that I don't actually + // implement the interface here. Bug 1088061 has been filed to remove + // this hackery. + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, + Ci.nsIPrintingPromptService]), + + MESSAGES: [ + "Printing:Preview:Enter", + "Printing:Preview:Exit", + "Printing:Preview:Navigate", + "Printing:Preview:ParseDocument", + "Printing:Preview:UpdatePageCount", + "Printing:Print", + ], + + init() { + this.MESSAGES.forEach(msgName => addMessageListener(msgName, this)); + addEventListener("PrintingError", this, true); + }, + + get shouldSavePrintSettings() { + return Services.prefs.getBoolPref("print.use_global_printsettings", false) && + Services.prefs.getBoolPref("print.save_print_settings", false); + }, + + handleEvent(event) { + if (event.type == "PrintingError") { + let win = event.target.defaultView; + let wbp = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebBrowserPrint); + let nsresult = event.detail; + sendAsyncMessage("Printing:Error", { + isPrinting: wbp.doingPrint, + nsresult: nsresult, + }); + } + }, + + receiveMessage(message) { + let objects = message.objects; + let data = message.data; + switch (message.name) { + case "Printing:Preview:Enter": { + this.enterPrintPreview(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode); + break; + } + + case "Printing:Preview:Exit": { + this.exitPrintPreview(); + break; + } + + case "Printing:Preview:Navigate": { + this.navigate(data.navType, data.pageNum); + break; + } + + case "Printing:Preview:ParseDocument": { + this.parseDocument(data.URL, Services.wm.getOuterWindowWithId(data.windowID)); + break; + } + + case "Printing:Preview:UpdatePageCount": { + this.updatePageCount(); + break; + } + + case "Printing:Print": { + this.print(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode); + break; + } + } + }, + + getPrintSettings() { + try { + let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"] + .getService(Ci.nsIPrintSettingsService); + + let printSettings = PSSVC.globalPrintSettings; + if (!printSettings.printerName) { + printSettings.printerName = PSSVC.defaultPrinterName; + } + // First get any defaults from the printer + try { + PSSVC.initPrintSettingsFromPrinter(printSettings.printerName, + printSettings); + } catch(e) { + // The printer name specified was invalid or there was an O.S. error. + Components.utils.reportError("Invalid printer: " + printSettings.printerName); + Services.prefs.clearUserPref("print.print_printer"); + // Try again with default + printSettings.printerName = PSSVC.defaultPrinterName; + PSSVC.initPrintSettingsFromPrinter(printSettings.printerName, + printSettings); + } + // now augment them with any values from last time + PSSVC.initPrintSettingsFromPrefs(printSettings, true, + printSettings.kInitSaveAll); + + return printSettings; + } catch (e) { + Components.utils.reportError(e); + } + + return null; + }, + + parseDocument(URL, contentWindow) { + // By using ReaderMode primitives, we parse given document and place the + // resulting JS object into the DOM of current browser. + let articlePromise = ReaderMode.parseDocument(contentWindow.document).catch(Cu.reportError); + articlePromise.then(function (article) { + // We make use of a web progress listener in order to know when the content we inject + // into the DOM has finished rendering. If our layout engine is still painting, we + // will wait for MozAfterPaint event to be fired. + let webProgressListener = { + onStateChange: function (webProgress, req, flags, status) { + if (flags & Ci.nsIWebProgressListener.STATE_STOP) { + webProgress.removeProgressListener(webProgressListener); + let domUtils = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + // Here we tell the parent that we have parsed the document successfully + // using ReaderMode primitives and we are able to enter on preview mode. + if (domUtils.isMozAfterPaintPending) { + addEventListener("MozAfterPaint", function onPaint() { + removeEventListener("MozAfterPaint", onPaint); + sendAsyncMessage("Printing:Preview:ReaderModeReady"); + }); + } else { + sendAsyncMessage("Printing:Preview:ReaderModeReady"); + } + } + }, + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIWebProgressListener, + Ci.nsISupportsWeakReference, + Ci.nsIObserver, + ]), + }; + + // Here we QI the docShell into a nsIWebProgress passing our web progress listener in. + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + webProgress.addProgressListener(webProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST); + + content.document.head.innerHTML = ""; + + // Set title of document + content.document.title = article.title; + + // Set base URI of document. Print preview code will read this value to + // populate the URL field in print settings so that it doesn't show + // "about:blank" as its URI. + let headBaseElement = content.document.createElement("base"); + headBaseElement.setAttribute("href", URL); + content.document.head.appendChild(headBaseElement); + + // Create link element referencing aboutReader.css and append it to head + let headStyleElement = content.document.createElement("link"); + headStyleElement.setAttribute("rel", "stylesheet"); + headStyleElement.setAttribute("href", "chrome://global/skin/aboutReader.css"); + headStyleElement.setAttribute("type", "text/css"); + content.document.head.appendChild(headStyleElement); + + content.document.body.innerHTML = ""; + + // Create container div (main element) and append it to body + let containerElement = content.document.createElement("div"); + containerElement.setAttribute("id", "container"); + content.document.body.appendChild(containerElement); + + // Create header div and append it to container + let headerElement = content.document.createElement("div"); + headerElement.setAttribute("id", "reader-header"); + headerElement.setAttribute("class", "header"); + containerElement.appendChild(headerElement); + + // Create style element for header div and import simplifyMode.css + let controlHeaderStyle = content.document.createElement("style"); + controlHeaderStyle.setAttribute("scoped", ""); + controlHeaderStyle.textContent = "@import url(\"chrome://global/content/simplifyMode.css\");"; + headerElement.appendChild(controlHeaderStyle); + + // Jam the article's title and byline into header div + let titleElement = content.document.createElement("h1"); + titleElement.setAttribute("id", "reader-title"); + titleElement.textContent = article.title; + headerElement.appendChild(titleElement); + + let bylineElement = content.document.createElement("div"); + bylineElement.setAttribute("id", "reader-credits"); + bylineElement.setAttribute("class", "credits"); + bylineElement.textContent = article.byline; + headerElement.appendChild(bylineElement); + + // Display header element + headerElement.style.display = "block"; + + // Create content div and append it to container + let contentElement = content.document.createElement("div"); + contentElement.setAttribute("class", "content"); + containerElement.appendChild(contentElement); + + // Jam the article's content into content div + let readerContent = content.document.createElement("div"); + readerContent.setAttribute("id", "moz-reader-content"); + contentElement.appendChild(readerContent); + + let articleUri = Services.io.newURI(article.url, null, null); + let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils); + let contentFragment = parserUtils.parseFragment(article.content, + Ci.nsIParserUtils.SanitizerDropForms | Ci.nsIParserUtils.SanitizerAllowStyle, + false, articleUri, readerContent); + + readerContent.appendChild(contentFragment); + + // Display reader content element + readerContent.style.display = "block"; + }); + }, + + enterPrintPreview(contentWindow, simplifiedMode) { + // We'll call this whenever we've finished reflowing the document, or if + // we errored out while attempting to print preview (in which case, we'll + // notify the parent that we've failed). + let notifyEntered = (error) => { + removeEventListener("printPreviewUpdate", onPrintPreviewReady); + sendAsyncMessage("Printing:Preview:Entered", { + failed: !!error, + }); + }; + + let onPrintPreviewReady = () => { + notifyEntered(); + }; + + // We have to wait for the print engine to finish reflowing all of the + // documents and subdocuments before we can tell the parent to flip to + // the print preview UI - otherwise, the print preview UI might ask for + // information (like the number of pages in the document) before we have + // our PresShells set up. + addEventListener("printPreviewUpdate", onPrintPreviewReady); + + try { + let printSettings = this.getPrintSettings(); + + // If we happen to be on simplified mode, we need to set docURL in order + // to generate header/footer content correctly, since simplified tab has + // "about:blank" as its URI. + if (printSettings && simplifiedMode) + printSettings.docURL = contentWindow.document.baseURI; + + docShell.printPreview.printPreview(printSettings, contentWindow, this); + } catch (error) { + // This might fail if we, for example, attempt to print a XUL document. + // In that case, we inform the parent to bail out of print preview. + Components.utils.reportError(error); + notifyEntered(error); + } + }, + + exitPrintPreview() { + docShell.printPreview.exitPrintPreview(); + }, + + print(contentWindow, simplifiedMode) { + let printSettings = this.getPrintSettings(); + let rv = Cr.NS_OK; + + // If we happen to be on simplified mode, we need to set docURL in order + // to generate header/footer content correctly, since simplified tab has + // "about:blank" as its URI. + if (printSettings && simplifiedMode) { + printSettings.docURL = contentWindow.document.baseURI; + } + + try { + let print = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebBrowserPrint); + + if (print.doingPrintPreview) { + this.logKeyedTelemetry("PRINT_DIALOG_OPENED_COUNT", "FROM_PREVIEW"); + } else { + this.logKeyedTelemetry("PRINT_DIALOG_OPENED_COUNT", "FROM_PAGE"); + } + + print.print(printSettings, null); + + if (print.doingPrintPreview) { + if (simplifiedMode) { + this.logKeyedTelemetry("PRINT_COUNT", "SIMPLIFIED"); + } else { + this.logKeyedTelemetry("PRINT_COUNT", "WITH_PREVIEW"); + } + } else { + this.logKeyedTelemetry("PRINT_COUNT", "WITHOUT_PREVIEW"); + } + } catch (e) { + // Pressing cancel is expressed as an NS_ERROR_ABORT return value, + // causing an exception to be thrown which we catch here. + if (e.result != Cr.NS_ERROR_ABORT) { + Cu.reportError(`In Printing:Print:Done handler, got unexpected rv + ${e.result}.`); + sendAsyncMessage("Printing:Error", { + isPrinting: true, + nsresult: e.result, + }); + } + } + + if (this.shouldSavePrintSettings) { + let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"] + .getService(Ci.nsIPrintSettingsService); + + PSSVC.savePrintSettingsToPrefs(printSettings, true, + printSettings.kInitSaveAll); + PSSVC.savePrintSettingsToPrefs(printSettings, false, + printSettings.kInitSavePrinterName); + } + }, + + logKeyedTelemetry(id, key) { + let histogram = Services.telemetry.getKeyedHistogramById(id); + histogram.add(key); + }, + + updatePageCount() { + let numPages = docShell.printPreview.printPreviewNumPages; + sendAsyncMessage("Printing:Preview:UpdatePageCount", { + numPages: numPages, + }); + }, + + navigate(navType, pageNum) { + docShell.printPreview.printPreviewNavigate(navType, pageNum); + }, + + /* nsIWebProgressListener for print preview */ + + onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { + sendAsyncMessage("Printing:Preview:StateChange", { + stateFlags: aStateFlags, + status: aStatus, + }); + }, + + onProgressChange(aWebProgress, aRequest, aCurSelfProgress, + aMaxSelfProgress, aCurTotalProgress, + aMaxTotalProgress) { + sendAsyncMessage("Printing:Preview:ProgressChange", { + curSelfProgress: aCurSelfProgress, + maxSelfProgress: aMaxSelfProgress, + curTotalProgress: aCurTotalProgress, + maxTotalProgress: aMaxTotalProgress, + }); + }, + + onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {}, + onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {}, + onSecurityChange(aWebProgress, aRequest, aState) {}, +} +Printing.init(); + +function SwitchDocumentDirection(aWindow) { + // document.dir can also be "auto", in which case it won't change + if (aWindow.document.dir == "ltr" || aWindow.document.dir == "") { + aWindow.document.dir = "rtl"; + } else if (aWindow.document.dir == "rtl") { + aWindow.document.dir = "ltr"; + } + for (let run = 0; run < aWindow.frames.length; run++) { + SwitchDocumentDirection(aWindow.frames[run]); + } +} + +addMessageListener("SwitchDocumentDirection", () => { + SwitchDocumentDirection(content.window); +}); + +var FindBar = { + /* Please keep in sync with toolkit/content/widgets/findbar.xml */ + FIND_NORMAL: 0, + FIND_TYPEAHEAD: 1, + FIND_LINKS: 2, + + _findMode: 0, + + init() { + addMessageListener("Findbar:UpdateState", this); + Services.els.addSystemEventListener(global, "keypress", this, false); + Services.els.addSystemEventListener(global, "mouseup", this, false); + }, + + receiveMessage(msg) { + switch (msg.name) { + case "Findbar:UpdateState": + this._findMode = msg.data.findMode; + break; + } + }, + + handleEvent(event) { + switch (event.type) { + case "keypress": + this._onKeypress(event); + break; + case "mouseup": + this._onMouseup(event); + break; + } + }, + + /** + * Returns whether FAYT can be used for the given event in + * the current content state. + */ + _canAndShouldFastFind() { + let should = false; + let can = BrowserUtils.canFastFind(content); + if (can) { + // XXXgijs: why all these shenanigans? Why not use the event's target? + let focusedWindow = {}; + let elt = Services.focus.getFocusedElementForWindow(content, true, focusedWindow); + let win = focusedWindow.value; + should = BrowserUtils.shouldFastFind(elt, win); + } + return { can, should } + }, + + _onKeypress(event) { + // Useless keys: + if (event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented) { + return undefined; + } + + // Check the focused element etc. + let fastFind = this._canAndShouldFastFind(); + + // Can we even use find in this page at all? + if (!fastFind.can) { + return undefined; + } + + let fakeEvent = {}; + for (let k in event) { + if (typeof event[k] != "object" && typeof event[k] != "function" && + !(k in content.KeyboardEvent)) { + fakeEvent[k] = event[k]; + } + } + + // sendSyncMessage returns an array of the responses from all listeners + let rv = sendSyncMessage("Findbar:Keypress", { + fakeEvent: fakeEvent, + shouldFastFind: fastFind.should + }); + if (rv.indexOf(false) !== -1) { + event.preventDefault(); + return false; + } + return undefined; + }, + + _onMouseup(event) { + if (this._findMode != this.FIND_NORMAL) + sendAsyncMessage("Findbar:Mouseup"); + }, +}; +FindBar.init(); + +let WebChannelMessageToChromeListener = { + // Preference containing the list (space separated) of origins that are + // allowed to send non-string values through a WebChannel, mainly for + // backwards compatability. See bug 1238128 for more information. + URL_WHITELIST_PREF: "webchannel.allowObject.urlWhitelist", + + // Cached list of whitelisted principals, we avoid constructing this if the + // value in `_lastWhitelistValue` hasn't changed since we constructed it last. + _cachedWhitelist: [], + _lastWhitelistValue: "", + + init() { + addEventListener("WebChannelMessageToChrome", e => { + this._onMessageToChrome(e); + }, true, true); + }, + + _getWhitelistedPrincipals() { + let whitelist = Services.prefs.getCharPref(this.URL_WHITELIST_PREF); + if (whitelist != this._lastWhitelistValue) { + let urls = whitelist.split(/\s+/); + this._cachedWhitelist = urls.map(origin => + Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin)); + } + return this._cachedWhitelist; + }, + + _onMessageToChrome(e) { + // If target is window then we want the document principal, otherwise fallback to target itself. + let principal = e.target.nodePrincipal ? e.target.nodePrincipal : e.target.document.nodePrincipal; + + if (e.detail) { + if (typeof e.detail != 'string') { + // Check if the principal is one of the ones that's allowed to send + // non-string values for e.detail. + let objectsAllowed = this._getWhitelistedPrincipals().some(whitelisted => + principal.originNoSuffix == whitelisted.originNoSuffix); + if (!objectsAllowed) { + Cu.reportError("WebChannelMessageToChrome sent with an object from a non-whitelisted principal"); + return; + } + } + sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal); + } else { + Cu.reportError("WebChannel message failed. No message detail."); + } + } +}; + +WebChannelMessageToChromeListener.init(); + +// This should be kept in sync with /browser/base/content.js. +// Add message listener for "WebChannelMessageToContent" messages from chrome scripts. +addMessageListener("WebChannelMessageToContent", function (e) { + if (e.data) { + // e.objects.eventTarget will be defined if sending a response to + // a WebChannelMessageToChrome event. An unsolicited send + // may not have an eventTarget defined, in this case send to the + // main content window. + let eventTarget = e.objects.eventTarget || content; + + // Use nodePrincipal if available, otherwise fallback to document principal. + let targetPrincipal = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget.document.nodePrincipal : eventTarget.nodePrincipal; + + if (e.principal.subsumes(targetPrincipal)) { + // If eventTarget is a window, use it as the targetWindow, otherwise + // find the window that owns the eventTarget. + let targetWindow = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget : eventTarget.ownerDocument.defaultView; + + eventTarget.dispatchEvent(new targetWindow.CustomEvent("WebChannelMessageToContent", { + detail: Cu.cloneInto({ + id: e.data.id, + message: e.data.message, + }, targetWindow), + })); + } else { + Cu.reportError("WebChannel message failed. Principal mismatch."); + } + } else { + Cu.reportError("WebChannel message failed. No message data."); + } +}); + +var AudioPlaybackListener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), + + init() { + Services.obs.addObserver(this, "audio-playback", false); + Services.obs.addObserver(this, "AudioFocusChanged", false); + Services.obs.addObserver(this, "MediaControl", false); + + addMessageListener("AudioPlayback", this); + addEventListener("unload", () => { + AudioPlaybackListener.uninit(); + }); + }, + + uninit() { + Services.obs.removeObserver(this, "audio-playback"); + Services.obs.removeObserver(this, "AudioFocusChanged"); + Services.obs.removeObserver(this, "MediaControl"); + + removeMessageListener("AudioPlayback", this); + }, + + handleMediaControlMessage(msg) { + let utils = global.content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + let suspendTypes = Ci.nsISuspendedTypes; + switch (msg) { + case "mute": + utils.audioMuted = true; + break; + case "unmute": + utils.audioMuted = false; + break; + case "lostAudioFocus": + utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE_DISPOSABLE; + break; + case "lostAudioFocusTransiently": + utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE; + break; + case "gainAudioFocus": + utils.mediaSuspend = suspendTypes.NONE_SUSPENDED; + break; + case "mediaControlPaused": + utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE_DISPOSABLE; + break; + case "mediaControlStopped": + utils.mediaSuspend = suspendTypes.SUSPENDED_STOP_DISPOSABLE; + break; + case "blockInactivePageMedia": + utils.mediaSuspend = suspendTypes.SUSPENDED_BLOCK; + break; + case "resumeMedia": + utils.mediaSuspend = suspendTypes.NONE_SUSPENDED; + break; + default: + dump("Error : wrong media control msg!\n"); + break; + } + }, + + observe(subject, topic, data) { + if (topic === "audio-playback") { + if (subject && subject.top == global.content) { + let name = "AudioPlayback:"; + if (data === "block") { + name += "Block"; + } else { + name += (data === "active") ? "Start" : "Stop"; + } + sendAsyncMessage(name); + } + } else if (topic == "AudioFocusChanged" || topic == "MediaControl") { + this.handleMediaControlMessage(data); + } + }, + + receiveMessage(msg) { + if (msg.name == "AudioPlayback") { + this.handleMediaControlMessage(msg.data.type); + } + }, +}; +AudioPlaybackListener.init(); + +addMessageListener("Browser:PurgeSessionHistory", function BrowserPurgeHistory() { + let sessionHistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory; + if (!sessionHistory) { + return; + } + + // place the entry at current index at the end of the history list, so it won't get removed + if (sessionHistory.index < sessionHistory.count - 1) { + let indexEntry = sessionHistory.getEntryAtIndex(sessionHistory.index, false); + sessionHistory.QueryInterface(Components.interfaces.nsISHistoryInternal); + indexEntry.QueryInterface(Components.interfaces.nsISHEntry); + sessionHistory.addEntry(indexEntry, true); + } + + let purge = sessionHistory.count; + if (global.content.location.href != "about:blank") { + --purge; // Don't remove the page the user's staring at from shistory + } + + if (purge > 0) { + sessionHistory.PurgeHistory(purge); + } +}); + +var ViewSelectionSource = { + init: function () { + addMessageListener("ViewSource:GetSelection", this); + }, + + receiveMessage: function(message) { + if (message.name == "ViewSource:GetSelection") { + let selectionDetails; + try { + selectionDetails = message.objects.target ? this.getMathMLSelection(message.objects.target) + : this.getSelection(); + } finally { + sendAsyncMessage("ViewSource:GetSelectionDone", selectionDetails); + } + } + }, + + /** + * A helper to get a path like FIXptr, but with an array instead of the + * "tumbler" notation. + * See FIXptr: http://lists.w3.org/Archives/Public/www-xml-linking-comments/2001AprJun/att-0074/01-NOTE-FIXptr-20010425.htm + */ + getPath: function(ancestor, node) { + var n = node; + var p = n.parentNode; + if (n == ancestor || !p) + return null; + var path = new Array(); + if (!path) + return null; + do { + for (var i = 0; i < p.childNodes.length; i++) { + if (p.childNodes.item(i) == n) { + path.push(i); + break; + } + } + n = p; + p = n.parentNode; + } while (n != ancestor && p); + return path; + }, + + getSelection: function () { + // These are markers used to delimit the selection during processing. They + // are removed from the final rendering. + // We use noncharacter Unicode codepoints to minimize the risk of clashing + // with anything that might legitimately be present in the document. + // U+FDD0..FDEF <noncharacters> + const MARK_SELECTION_START = "\uFDD0"; + const MARK_SELECTION_END = "\uFDEF"; + + var focusedWindow = Services.focus.focusedWindow || content; + var selection = focusedWindow.getSelection(); + + var range = selection.getRangeAt(0); + var ancestorContainer = range.commonAncestorContainer; + var doc = ancestorContainer.ownerDocument; + + var startContainer = range.startContainer; + var endContainer = range.endContainer; + var startOffset = range.startOffset; + var endOffset = range.endOffset; + + // let the ancestor be an element + var Node = doc.defaultView.Node; + if (ancestorContainer.nodeType == Node.TEXT_NODE || + ancestorContainer.nodeType == Node.CDATA_SECTION_NODE) + ancestorContainer = ancestorContainer.parentNode; + + // for selectAll, let's use the entire document, including <html>...</html> + // @see nsDocumentViewer::SelectAll() for how selectAll is implemented + try { + if (ancestorContainer == doc.body) + ancestorContainer = doc.documentElement; + } catch (e) { } + + // each path is a "child sequence" (a.k.a. "tumbler") that + // descends from the ancestor down to the boundary point + var startPath = this.getPath(ancestorContainer, startContainer); + var endPath = this.getPath(ancestorContainer, endContainer); + + // clone the fragment of interest and reset everything to be relative to it + // note: it is with the clone that we operate/munge from now on. Also note + // that we clone into a data document to prevent images in the fragment from + // loading and the like. The use of importNode here, as opposed to adoptNode, + // is _very_ important. + // XXXbz wish there were a less hacky way to create an untrusted document here + var isHTML = (doc.createElement("div").tagName == "DIV"); + var dataDoc = isHTML ? + ancestorContainer.ownerDocument.implementation.createHTMLDocument("") : + ancestorContainer.ownerDocument.implementation.createDocument("", "", null); + ancestorContainer = dataDoc.importNode(ancestorContainer, true); + startContainer = ancestorContainer; + endContainer = ancestorContainer; + + // Only bother with the selection if it can be remapped. Don't mess with + // leaf elements (such as <isindex>) that secretly use anynomous content + // for their display appearance. + var canDrawSelection = ancestorContainer.hasChildNodes(); + var tmpNode; + if (canDrawSelection) { + var i; + for (i = startPath ? startPath.length-1 : -1; i >= 0; i--) { + startContainer = startContainer.childNodes.item(startPath[i]); + } + for (i = endPath ? endPath.length-1 : -1; i >= 0; i--) { + endContainer = endContainer.childNodes.item(endPath[i]); + } + + // add special markers to record the extent of the selection + // note: |startOffset| and |endOffset| are interpreted either as + // offsets in the text data or as child indices (see the Range spec) + // (here, munging the end point first to keep the start point safe...) + if (endContainer.nodeType == Node.TEXT_NODE || + endContainer.nodeType == Node.CDATA_SECTION_NODE) { + // do some extra tweaks to try to avoid the view-source output to look like + // ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection). + // To get a neat output, the idea here is to remap the end point from: + // 1. ...<tag>]... to ...]<tag>... + // 2. ...]</tag>... to ...</tag>]... + if ((endOffset > 0 && endOffset < endContainer.data.length) || + !endContainer.parentNode || !endContainer.parentNode.parentNode) + endContainer.insertData(endOffset, MARK_SELECTION_END); + else { + tmpNode = dataDoc.createTextNode(MARK_SELECTION_END); + endContainer = endContainer.parentNode; + if (endOffset === 0) + endContainer.parentNode.insertBefore(tmpNode, endContainer); + else + endContainer.parentNode.insertBefore(tmpNode, endContainer.nextSibling); + } + } + else { + tmpNode = dataDoc.createTextNode(MARK_SELECTION_END); + endContainer.insertBefore(tmpNode, endContainer.childNodes.item(endOffset)); + } + + if (startContainer.nodeType == Node.TEXT_NODE || + startContainer.nodeType == Node.CDATA_SECTION_NODE) { + // do some extra tweaks to try to avoid the view-source output to look like + // ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection). + // To get a neat output, the idea here is to remap the start point from: + // 1. ...<tag>[... to ...[<tag>... + // 2. ...[</tag>... to ...</tag>[... + if ((startOffset > 0 && startOffset < startContainer.data.length) || + !startContainer.parentNode || !startContainer.parentNode.parentNode || + startContainer != startContainer.parentNode.lastChild) + startContainer.insertData(startOffset, MARK_SELECTION_START); + else { + tmpNode = dataDoc.createTextNode(MARK_SELECTION_START); + startContainer = startContainer.parentNode; + if (startOffset === 0) + startContainer.parentNode.insertBefore(tmpNode, startContainer); + else + startContainer.parentNode.insertBefore(tmpNode, startContainer.nextSibling); + } + } + else { + tmpNode = dataDoc.createTextNode(MARK_SELECTION_START); + startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset)); + } + } + + // now extract and display the syntax highlighted source + tmpNode = dataDoc.createElementNS("http://www.w3.org/1999/xhtml", "div"); + tmpNode.appendChild(ancestorContainer); + + return { uri: (isHTML ? "view-source:data:text/html;charset=utf-8," : + "view-source:data:application/xml;charset=utf-8,") + + encodeURIComponent(tmpNode.innerHTML), + drawSelection: canDrawSelection, + baseURI: doc.baseURI }; + }, + + /** + * Reformat the source of a MathML node to highlight the node that was targetted. + * + * @param node + * Some element within the fragment of interest. + */ + getMathMLSelection: function(node) { + var Node = node.ownerDocument.defaultView.Node; + this._lineCount = 0; + this._startTargetLine = 0; + this._endTargetLine = 0; + this._targetNode = node; + if (this._targetNode && this._targetNode.nodeType == Node.TEXT_NODE) + this._targetNode = this._targetNode.parentNode; + + // walk up the tree to the top-level element (e.g., <math>, <svg>) + var topTag = "math"; + var topNode = this._targetNode; + while (topNode && topNode.localName != topTag) { + topNode = topNode.parentNode; + } + if (!topNode) + return undefined; + + // serialize + const VIEW_SOURCE_CSS = "resource://gre-resources/viewsource.css"; + const BUNDLE_URL = "chrome://global/locale/viewSource.properties"; + + let bundle = Services.strings.createBundle(BUNDLE_URL); + var title = bundle.GetStringFromName("viewMathMLSourceTitle"); + var wrapClass = this.wrapLongLines ? ' class="wrap"' : ''; + var source = + '<!DOCTYPE html>' + + '<html>' + + '<head><title>' + title + '</title>' + + '<link rel="stylesheet" type="text/css" href="' + VIEW_SOURCE_CSS + '">' + + '<style type="text/css">' + + '#target { border: dashed 1px; background-color: lightyellow; }' + + '</style>' + + '</head>' + + '<body id="viewsource"' + wrapClass + + ' onload="document.title=\''+title+'\'; document.getElementById(\'target\').scrollIntoView(true)">' + + '<pre>' + + this.getOuterMarkup(topNode, 0) + + '</pre></body></html>' + ; // end + + return { uri: "data:text/html;charset=utf-8," + encodeURIComponent(source), + drawSelection: false, baseURI: node.ownerDocument.baseURI }; + }, + + get wrapLongLines() { + return Services.prefs.getBoolPref("view_source.wrap_long_lines"); + }, + + getInnerMarkup: function(node, indent) { + var str = ''; + for (var i = 0; i < node.childNodes.length; i++) { + str += this.getOuterMarkup(node.childNodes.item(i), indent); + } + return str; + }, + + getOuterMarkup: function(node, indent) { + var Node = node.ownerDocument.defaultView.Node; + var newline = ""; + var padding = ""; + var str = ""; + if (node == this._targetNode) { + this._startTargetLine = this._lineCount; + str += '</pre><pre id="target">'; + } + + switch (node.nodeType) { + case Node.ELEMENT_NODE: // Element + // to avoid the wide gap problem, '\n' is not emitted on the first + // line and the lines before & after the <pre id="target">...</pre> + if (this._lineCount > 0 && + this._lineCount != this._startTargetLine && + this._lineCount != this._endTargetLine) { + newline = "\n"; + } + this._lineCount++; + for (var k = 0; k < indent; k++) { + padding += " "; + } + str += newline + padding + + '<<span class="start-tag">' + node.nodeName + '</span>'; + for (var i = 0; i < node.attributes.length; i++) { + var attr = node.attributes.item(i); + if (attr.nodeName.match(/^[-_]moz/)) { + continue; + } + str += ' <span class="attribute-name">' + + attr.nodeName + + '</span>=<span class="attribute-value">"' + + this.unicodeToEntity(attr.nodeValue) + + '"</span>'; + } + if (!node.hasChildNodes()) { + str += "/>"; + } + else { + str += ">"; + var oldLine = this._lineCount; + str += this.getInnerMarkup(node, indent + 2); + if (oldLine == this._lineCount) { + newline = ""; + padding = ""; + } + else { + newline = (this._lineCount == this._endTargetLine) ? "" : "\n"; + this._lineCount++; + } + str += newline + padding + + '</<span class="end-tag">' + node.nodeName + '</span>>'; + } + break; + case Node.TEXT_NODE: // Text + var tmp = node.nodeValue; + tmp = tmp.replace(/(\n|\r|\t)+/g, " "); + tmp = tmp.replace(/^ +/, ""); + tmp = tmp.replace(/ +$/, ""); + if (tmp.length != 0) { + str += '<span class="text">' + this.unicodeToEntity(tmp) + '</span>'; + } + break; + default: + break; + } + + if (node == this._targetNode) { + this._endTargetLine = this._lineCount; + str += '</pre><pre>'; + } + return str; + }, + + unicodeToEntity: function(text) { + const charTable = { + '&': '&<span class="entity">amp;</span>', + '<': '&<span class="entity">lt;</span>', + '>': '&<span class="entity">gt;</span>', + '"': '&<span class="entity">quot;</span>' + }; + + function charTableLookup(letter) { + return charTable[letter]; + } + + function convertEntity(letter) { + try { + var unichar = this._entityConverter + .ConvertToEntity(letter, entityVersion); + var entity = unichar.substring(1); // extract '&' + return '&<span class="entity">' + entity + '</span>'; + } catch (ex) { + return letter; + } + } + + if (!this._entityConverter) { + try { + this._entityConverter = Cc["@mozilla.org/intl/entityconverter;1"] + .createInstance(Ci.nsIEntityConverter); + } catch (e) { } + } + + const entityVersion = Ci.nsIEntityConverter.entityW3C; + + var str = text; + + // replace chars in our charTable + str = str.replace(/[<>&"]/g, charTableLookup); + + // replace chars > 0x7f via nsIEntityConverter + str = str.replace(/[^\0-\u007f]/g, convertEntity); + + return str; + } +}; + +ViewSelectionSource.init(); + +addEventListener("MozApplicationManifest", function(e) { + let doc = e.target; + let info = { + uri: doc.documentURI, + characterSet: doc.characterSet, + manifest: doc.documentElement.getAttribute("manifest"), + principal: doc.nodePrincipal, + }; + sendAsyncMessage("MozApplicationManifest", info); +}, false); + +let AutoCompletePopup = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompletePopup]), + + _connected: false, + + MESSAGES: [ + "FormAutoComplete:HandleEnter", + "FormAutoComplete:PopupClosed", + "FormAutoComplete:PopupOpened", + "FormAutoComplete:RequestFocus", + ], + + init: function() { + addEventListener("unload", this); + addEventListener("DOMContentLoaded", this); + + for (let messageName of this.MESSAGES) { + addMessageListener(messageName, this); + } + + this._input = null; + this._popupOpen = false; + }, + + destroy: function() { + if (this._connected) { + let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"] + .getService(Ci.nsIFormFillController); + controller.detachFromBrowser(docShell); + this._connected = false; + } + + removeEventListener("unload", this); + removeEventListener("DOMContentLoaded", this); + + for (let messageName of this.MESSAGES) { + removeMessageListener(messageName, this); + } + }, + + handleEvent(event) { + switch (event.type) { + case "DOMContentLoaded": { + removeEventListener("DOMContentLoaded", this); + + // We need to wait for a content viewer to be available + // before we can attach our AutoCompletePopup handler, + // since nsFormFillController assumes one will exist + // when we call attachToBrowser. + + // Hook up the form fill autocomplete controller. + let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"] + .getService(Ci.nsIFormFillController); + controller.attachToBrowser(docShell, + this.QueryInterface(Ci.nsIAutoCompletePopup)); + this._connected = true; + break; + } + + case "unload": { + this.destroy(); + break; + } + } + }, + + receiveMessage(message) { + switch (message.name) { + case "FormAutoComplete:HandleEnter": { + this.selectedIndex = message.data.selectedIndex; + + let controller = Cc["@mozilla.org/autocomplete/controller;1"] + .getService(Ci.nsIAutoCompleteController); + controller.handleEnter(message.data.isPopupSelection); + break; + } + + case "FormAutoComplete:PopupClosed": { + this._popupOpen = false; + break; + } + + case "FormAutoComplete:PopupOpened": { + this._popupOpen = true; + break; + } + + case "FormAutoComplete:RequestFocus": { + if (this._input) { + this._input.focus(); + } + break; + } + } + }, + + get input () { return this._input; }, + get overrideValue () { return null; }, + set selectedIndex (index) { + sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index }); + }, + get selectedIndex () { + // selectedIndex getter must be synchronous because we need the + // correct value when the controller is in controller::HandleEnter. + // We can't easily just let the parent inform us the new value every + // time it changes because not every action that can change the + // selectedIndex is trivial to catch (e.g. moving the mouse over the + // list). + return sendSyncMessage("FormAutoComplete:GetSelectedIndex", {}); + }, + get popupOpen () { + return this._popupOpen; + }, + + openAutocompletePopup: function (input, element) { + if (this._popupOpen || !input) { + return; + } + + let rect = BrowserUtils.getElementBoundingScreenRect(element); + let window = element.ownerDocument.defaultView; + let dir = window.getComputedStyle(element).direction; + let results = this.getResultsFromController(input); + + sendAsyncMessage("FormAutoComplete:MaybeOpenPopup", + { results, rect, dir }); + this._input = input; + }, + + closePopup: function () { + // We set this here instead of just waiting for the + // PopupClosed message to do it so that we don't end + // up in a state where the content thinks that a popup + // is open when it isn't (or soon won't be). + this._popupOpen = false; + sendAsyncMessage("FormAutoComplete:ClosePopup", {}); + }, + + invalidate: function () { + if (this._popupOpen) { + let results = this.getResultsFromController(this._input); + sendAsyncMessage("FormAutoComplete:Invalidate", { results }); + } + }, + + selectBy: function(reverse, page) { + this._index = sendSyncMessage("FormAutoComplete:SelectBy", { + reverse: reverse, + page: page + }); + }, + + getResultsFromController(inputField) { + let results = []; + + if (!inputField) { + return results; + } + + let controller = inputField.controller; + if (!(controller instanceof Ci.nsIAutoCompleteController)) { + return results; + } + + for (let i = 0; i < controller.matchCount; ++i) { + let result = {}; + result.value = controller.getValueAt(i); + result.label = controller.getLabelAt(i); + result.comment = controller.getCommentAt(i); + result.style = controller.getStyleAt(i); + result.image = controller.getImageAt(i); + results.push(result); + } + + return results; + }, +} + +AutoCompletePopup.init(); + +/** + * DateTimePickerListener is the communication channel between the input box + * (content) for date/time input types and its picker (chrome). + */ +let DateTimePickerListener = { + /** + * On init, just listen for the event to open the picker, once the picker is + * opened, we'll listen for update and close events. + */ + init: function() { + addEventListener("MozOpenDateTimePicker", this); + this._inputElement = null; + + addEventListener("unload", () => { + this.uninit(); + }); + }, + + uninit: function() { + removeEventListener("MozOpenDateTimePicker", this); + this._inputElement = null; + }, + + /** + * Cleanup function called when picker is closed. + */ + close: function() { + this.removeListeners(); + this._inputElement.setDateTimePickerState(false); + this._inputElement = null; + }, + + /** + * Called after picker is opened to start listening for input box update + * events. + */ + addListeners: function() { + addEventListener("MozUpdateDateTimePicker", this); + addEventListener("MozCloseDateTimePicker", this); + addEventListener("pagehide", this); + + addMessageListener("FormDateTime:PickerValueChanged", this); + addMessageListener("FormDateTime:PickerClosed", this); + }, + + /** + * Stop listeneing for events when picker is closed. + */ + removeListeners: function() { + removeEventListener("MozUpdateDateTimePicker", this); + removeEventListener("MozCloseDateTimePicker", this); + removeEventListener("pagehide", this); + + removeMessageListener("FormDateTime:PickerValueChanged", this); + removeMessageListener("FormDateTime:PickerClosed", this); + }, + + /** + * Helper function that returns the CSS direction property of the element. + */ + getComputedDirection: function(aElement) { + return aElement.ownerDocument.defaultView.getComputedStyle(aElement) + .getPropertyValue("direction"); + }, + + /** + * Helper function that returns the rect of the element, which is the position + * relative to the left/top of the content area. + */ + getBoundingContentRect: function(aElement) { + return BrowserUtils.getElementBoundingRect(aElement); + }, + + getTimePickerPref: function() { + return Services.prefs.getBoolPref("dom.forms.datetime.timepicker"); + }, + + /** + * nsIMessageListener. + */ + receiveMessage: function(aMessage) { + switch (aMessage.name) { + case "FormDateTime:PickerClosed": { + this.close(); + break; + } + case "FormDateTime:PickerValueChanged": { + this._inputElement.updateDateTimeInputBox(aMessage.data); + break; + } + default: + break; + } + }, + + /** + * nsIDOMEventListener, for chrome events sent by the input element and other + * DOM events. + */ + handleEvent: function(aEvent) { + switch (aEvent.type) { + case "MozOpenDateTimePicker": { + // Time picker is disabled when preffed off + if (!(aEvent.originalTarget instanceof content.HTMLInputElement) || + (aEvent.originalTarget.type == "time" && !this.getTimePickerPref())) { + return; + } + + if (this._inputElement) { + // This happens when we're trying to open a picker when another picker + // is still open. We ignore this request to let the first picker + // close gracefully. + return; + } + + this._inputElement = aEvent.originalTarget; + this._inputElement.setDateTimePickerState(true); + this.addListeners(); + + let value = this._inputElement.getDateTimeInputBoxValue(); + sendAsyncMessage("FormDateTime:OpenPicker", { + rect: this.getBoundingContentRect(this._inputElement), + dir: this.getComputedDirection(this._inputElement), + type: this._inputElement.type, + detail: { + // Pass partial value if it's available, otherwise pass input + // element's value. + value: Object.keys(value).length > 0 ? value + : this._inputElement.value, + min: this._inputElement.getMinimum(), + max: this._inputElement.getMaximum(), + step: this._inputElement.getStep(), + stepBase: this._inputElement.getStepBase(), + }, + }); + break; + } + case "MozUpdateDateTimePicker": { + let value = this._inputElement.getDateTimeInputBoxValue(); + value.type = this._inputElement.type; + sendAsyncMessage("FormDateTime:UpdatePicker", { value }); + break; + } + case "MozCloseDateTimePicker": { + sendAsyncMessage("FormDateTime:ClosePicker"); + this.close(); + break; + } + case "pagehide": { + if (this._inputElement && + this._inputElement.ownerDocument == aEvent.target) { + sendAsyncMessage("FormDateTime:ClosePicker"); + this.close(); + } + break; + } + default: + break; + } + }, +} + +DateTimePickerListener.init(); diff --git a/components/global/content/buildconfig.html b/components/global/content/buildconfig.html new file mode 100644 index 000000000..322208189 --- /dev/null +++ b/components/global/content/buildconfig.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +# 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/. +# +#filter substitution +#include @TOPOBJDIR@/source-repo.h +<html> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width; user-scalable=false;"> + <title>about:buildconfig</title> + <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"> + <style type="text/css"> + th { text-align: start; } + h2 { margin-top: 1.5em; } + th, td { vertical-align: top; } + </style> +</head> +<body class="aboutPageWideContainer"> +<h1>about:buildconfig</h1> +#ifdef MOZ_SOURCE_URL +<h2>Source</h2> +<p>Built from <a href="@MOZ_SOURCE_URL@">@MOZ_SOURCE_URL@</a></p> +#endif +<h2>Build platform</h2> +<table> + <tbody> + <tr> + <th>target</th> + </tr> + <tr> + <td>@target@</td> + </tr> + </tbody> +</table> +<h2>Build tools</h2> +<table> + <tbody> + <tr> + <th>Compiler</th> + <th>Version</th> + <th>Compiler flags</th> + </tr> + <tr> + <td>@CC@</td> + <td>@CC_VERSION@</td> + <td>@CFLAGS@</td> + </tr> + <tr> + <td>@CXX@</td> + <td>@CC_VERSION@</td> +#ifndef BUILD_FASTER + <td>@CXXFLAGS@ @CPPFLAGS@</td> +#endif + </tr> + </tbody> +</table> +<h2>Configure options</h2> +<p>@MOZ_CONFIGURE_OPTIONS@</p> +</body> +</html> diff --git a/components/global/content/contentAreaUtils.js b/components/global/content/contentAreaUtils.js new file mode 100644 index 000000000..17e463325 --- /dev/null +++ b/components/global/content/contentAreaUtils.js @@ -0,0 +1,1297 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", + "resource://gre/modules/BrowserUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", + "resource://gre/modules/Downloads.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "DownloadLastDir", + "resource://gre/modules/DownloadLastDir.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Deprecated", + "resource://gre/modules/Deprecated.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); + +var ContentAreaUtils = { + + // this is for backwards compatibility. + get ioService() { + return Services.io; + }, + + get stringBundle() { + delete this.stringBundle; + return this.stringBundle = + Services.strings.createBundle("chrome://global/locale/contentAreaCommands.properties"); + } +} + +function urlSecurityCheck(aURL, aPrincipal, aFlags) +{ + return BrowserUtils.urlSecurityCheck(aURL, aPrincipal, aFlags); +} + +/** + * Determine whether or not a given focused DOMWindow is in the content area. + **/ +function isContentFrame(aFocusedWindow) +{ + if (!aFocusedWindow) + return false; + + return (aFocusedWindow.top == window.content); +} + +function forbidCPOW(arg, func, argname) +{ + if (arg && (typeof(arg) == "object" || typeof(arg) == "function") && + Components.utils.isCrossProcessWrapper(arg)) { + throw new Error(`no CPOWs allowed for argument ${argname} to ${func}`); + } +} + +// Clientele: (Make sure you don't break any of these) +// - File -> Save Page/Frame As... +// - Context -> Save Page/Frame As... +// - Context -> Save Link As... +// - Alt-Click links in web pages +// - Alt-Click links in the UI +// +// Try saving each of these types: +// - A complete webpage using File->Save Page As, and Context->Save Page As +// - A webpage as HTML only using the above methods +// - A webpage as Text only using the above methods +// - An image with an extension (e.g. .jpg) in its file name, using +// Context->Save Image As... +// - An image without an extension (e.g. a banner ad on cnn.com) using +// the above method. +// - A linked document using Save Link As... +// - A linked document using Alt-click Save Link As... +// +function saveURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache, + aSkipPrompt, aReferrer, aSourceDocument, aIsContentWindowPrivate) +{ + forbidCPOW(aURL, "saveURL", "aURL"); + forbidCPOW(aReferrer, "saveURL", "aReferrer"); + // Allow aSourceDocument to be a CPOW. + + internalSave(aURL, null, aFileName, null, null, aShouldBypassCache, + aFilePickerTitleKey, null, aReferrer, aSourceDocument, + aSkipPrompt, null, aIsContentWindowPrivate); +} + +// Just like saveURL, but will get some info off the image before +// calling internalSave +// Clientele: (Make sure you don't break any of these) +// - Context -> Save Image As... +const imgICache = Components.interfaces.imgICache; +const nsISupportsCString = Components.interfaces.nsISupportsCString; + +/** + * Offers to save an image URL to the file system. + * + * @param aURL (string) + * The URL of the image to be saved. + * @param aFileName (string) + * The suggested filename for the saved file. + * @param aFilePickerTitleKey (string, optional) + * Localized string key for an alternate title for the file + * picker. If set to null, this will default to something sensible. + * @param aShouldBypassCache (bool) + * If true, the image will always be retrieved from the server instead + * of the network or image caches. + * @param aSkipPrompt (bool) + * If true, we will attempt to save the file with the suggested + * filename to the default downloads folder without showing the + * file picker. + * @param aReferrer (nsIURI, optional) + * The referrer URI object (not a URL string) to use, or null + * if no referrer should be sent. + * @param aDoc (nsIDocument, deprecated, optional) + * The content document that the save is being initiated from. If this + * is omitted, then aIsContentWindowPrivate must be provided. + * @param aContentType (string, optional) + * The content type of the image. + * @param aContentDisp (string, optional) + * The content disposition of the image. + * @param aIsContentWindowPrivate (bool) + * Whether or not the containing window is in private browsing mode. + * Does not need to be provided is aDoc is passed. + */ +function saveImageURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache, + aSkipPrompt, aReferrer, aDoc, aContentType, aContentDisp, + aIsContentWindowPrivate) +{ + forbidCPOW(aURL, "saveImageURL", "aURL"); + forbidCPOW(aReferrer, "saveImageURL", "aReferrer"); + + if (aDoc && aIsContentWindowPrivate == undefined) { + if (Components.utils.isCrossProcessWrapper(aDoc)) { + Deprecated.warning("saveImageURL should not be passed document CPOWs. " + + "The caller should pass in the content type and " + + "disposition themselves", + "https://bugzilla.mozilla.org/show_bug.cgi?id=1243643"); + } + // This will definitely not work for in-browser code or multi-process compatible + // add-ons due to bug 1233497, which makes unsafe CPOW usage throw by default. + Deprecated.warning("saveImageURL should be passed the private state of " + + "the containing window.", + "https://bugzilla.mozilla.org/show_bug.cgi?id=1243643"); + aIsContentWindowPrivate = + PrivateBrowsingUtils.isContentWindowPrivate(aDoc.defaultView); + } + + // We'd better have the private state by now. + if (aIsContentWindowPrivate == undefined) { + throw new Error("saveImageURL couldn't compute private state of content window"); + } + + if (!aShouldBypassCache && (aDoc && !Components.utils.isCrossProcessWrapper(aDoc)) && + (!aContentType && !aContentDisp)) { + try { + var imageCache = Components.classes["@mozilla.org/image/tools;1"] + .getService(Components.interfaces.imgITools) + .getImgCacheForDocument(aDoc); + var props = + imageCache.findEntryProperties(makeURI(aURL, getCharsetforSave(null)), aDoc); + if (props) { + aContentType = props.get("type", nsISupportsCString); + aContentDisp = props.get("content-disposition", nsISupportsCString); + } + } catch (e) { + // Failure to get type and content-disposition off the image is non-fatal + } + } + + internalSave(aURL, null, aFileName, aContentDisp, aContentType, + aShouldBypassCache, aFilePickerTitleKey, null, aReferrer, + null, aSkipPrompt, null, aIsContentWindowPrivate); +} + +// This is like saveDocument, but takes any browser/frame-like element +// (nsIFrameLoaderOwner) and saves the current document inside it, +// whether in-process or out-of-process. +function saveBrowser(aBrowser, aSkipPrompt, aOuterWindowID=0) +{ + if (!aBrowser) { + throw "Must have a browser when calling saveBrowser"; + } + let persistable = aBrowser.QueryInterface(Ci.nsIFrameLoaderOwner) + .frameLoader + .QueryInterface(Ci.nsIWebBrowserPersistable); + let stack = Components.stack.caller; + persistable.startPersistence(aOuterWindowID, { + onDocumentReady: function (document) { + saveDocument(document, aSkipPrompt); + }, + onError: function (status) { + throw new Components.Exception("saveBrowser failed asynchronously in startPersistence", + status, stack); + } + }); +} + +// Saves a document; aDocument can be an nsIWebBrowserPersistDocument +// (see saveBrowser, above) or an nsIDOMDocument. +// +// aDocument can also be a CPOW for a remote nsIDOMDocument, in which +// case "save as" modes that serialize the document's DOM are +// unavailable. This is a temporary measure for the "Save Frame As" +// command (bug 1141337) and pre-e10s add-ons. +function saveDocument(aDocument, aSkipPrompt) +{ + const Ci = Components.interfaces; + + if (!aDocument) + throw "Must have a document when calling saveDocument"; + + let contentDisposition = null; + let cacheKeyInt = null; + + if (aDocument instanceof Ci.nsIWebBrowserPersistDocument) { + // nsIWebBrowserPersistDocument exposes these directly. + contentDisposition = aDocument.contentDisposition; + cacheKeyInt = aDocument.cacheKey; + } else if (aDocument instanceof Ci.nsIDOMDocument) { + // Otherwise it's an actual nsDocument (and possibly a CPOW). + // We want to use cached data because the document is currently visible. + let ifreq = + aDocument.defaultView + .QueryInterface(Ci.nsIInterfaceRequestor); + + try { + contentDisposition = + ifreq.getInterface(Ci.nsIDOMWindowUtils) + .getDocumentMetadata("content-disposition"); + } catch (ex) { + // Failure to get a content-disposition is ok + } + + try { + let shEntry = + ifreq.getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIWebPageDescriptor) + .currentDescriptor + .QueryInterface(Ci.nsISHEntry); + + let cacheKey = shEntry.cacheKey + .QueryInterface(Ci.nsISupportsPRUint32) + .data; + // cacheKey might be a CPOW, which can't be passed to native + // code, but the data attribute is just a number. + cacheKeyInt = cacheKey.data; + } catch (ex) { + // We might not find it in the cache. Oh, well. + } + } + + // Convert the cacheKey back into an XPCOM object. + let cacheKey = null; + if (cacheKeyInt) { + cacheKey = Cc["@mozilla.org/supports-PRUint32;1"] + .createInstance(Ci.nsISupportsPRUint32); + cacheKey.data = cacheKeyInt; + } + + internalSave(aDocument.documentURI, aDocument, null, contentDisposition, + aDocument.contentType, false, null, null, + aDocument.referrer ? makeURI(aDocument.referrer) : null, + aDocument, aSkipPrompt, cacheKey); +} + +function DownloadListener(win, transfer) { + function makeClosure(name) { + return function() { + transfer[name].apply(transfer, arguments); + } + } + + this.window = win; + + // Now... we need to forward all calls to our transfer + for (var i in transfer) { + if (i != "QueryInterface") + this[i] = makeClosure(i); + } +} + +DownloadListener.prototype = { + QueryInterface: function dl_qi(aIID) + { + if (aIID.equals(Components.interfaces.nsIInterfaceRequestor) || + aIID.equals(Components.interfaces.nsIWebProgressListener) || + aIID.equals(Components.interfaces.nsIWebProgressListener2) || + aIID.equals(Components.interfaces.nsISupports)) { + return this; + } + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + getInterface: function dl_gi(aIID) + { + if (aIID.equals(Components.interfaces.nsIAuthPrompt) || + aIID.equals(Components.interfaces.nsIAuthPrompt2)) { + var ww = + Components.classes["@mozilla.org/embedcomp/window-watcher;1"] + .getService(Components.interfaces.nsIPromptFactory); + return ww.getPrompt(this.window, aIID); + } + + throw Components.results.NS_ERROR_NO_INTERFACE; + } +} + +const kSaveAsType_Complete = 0; // Save document with attached objects. +XPCOMUtils.defineConstant(this, "kSaveAsType_Complete", 0); +// const kSaveAsType_URL = 1; // Save document or URL by itself. +const kSaveAsType_Text = 2; // Save document, converting to plain text. +XPCOMUtils.defineConstant(this, "kSaveAsType_Text", kSaveAsType_Text); + +/** + * internalSave: Used when saving a document or URL. + * + * If aChosenData is null, this method: + * - Determines a local target filename to use + * - Prompts the user to confirm the destination filename and save mode + * (aContentType affects this) + * - [Note] This process involves the parameters aURL, aReferrer (to determine + * how aURL was encoded), aDocument, aDefaultFileName, aFilePickerTitleKey, + * and aSkipPrompt. + * + * If aChosenData is non-null, this method: + * - Uses the provided source URI and save file name + * - Saves the document as complete DOM if possible (aDocument present and + * right aContentType) + * - [Note] The parameters aURL, aDefaultFileName, aFilePickerTitleKey, and + * aSkipPrompt are ignored. + * + * In any case, this method: + * - Creates a 'Persist' object (which will perform the saving in the + * background) and then starts it. + * - [Note] This part of the process only involves the parameters aDocument, + * aShouldBypassCache and aReferrer. The source, the save name and the save + * mode are the ones determined previously. + * + * @param aURL + * The String representation of the URL of the document being saved + * @param aDocument + * The document to be saved + * @param aDefaultFileName + * The caller-provided suggested filename if we don't + * find a better one + * @param aContentDisposition + * The caller-provided content-disposition header to use. + * @param aContentType + * The caller-provided content-type to use + * @param aShouldBypassCache + * If true, the document will always be refetched from the server + * @param aFilePickerTitleKey + * Alternate title for the file picker + * @param aChosenData + * If non-null this contains an instance of object AutoChosen (see below) + * which holds pre-determined data so that the user does not need to be + * prompted for a target filename. + * @param aReferrer + * the referrer URI object (not URL string) to use, or null + * if no referrer should be sent. + * @param aInitiatingDocument [optional] + * The document from which the save was initiated. + * If this is omitted then aIsContentWindowPrivate has to be provided. + * @param aSkipPrompt [optional] + * If set to true, we will attempt to save the file to the + * default downloads folder without prompting. + * @param aCacheKey [optional] + * If set will be passed to saveURI. See nsIWebBrowserPersist for + * allowed values. + * @param aIsContentWindowPrivate [optional] + * This parameter is provided when the aInitiatingDocument is not a + * real document object. Stores whether aInitiatingDocument.defaultView + * was private or not. + */ +function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition, + aContentType, aShouldBypassCache, aFilePickerTitleKey, + aChosenData, aReferrer, aInitiatingDocument, aSkipPrompt, + aCacheKey, aIsContentWindowPrivate) +{ + forbidCPOW(aURL, "internalSave", "aURL"); + forbidCPOW(aReferrer, "internalSave", "aReferrer"); + forbidCPOW(aCacheKey, "internalSave", "aCacheKey"); + // Allow aInitiatingDocument to be a CPOW. + + if (aSkipPrompt == undefined) + aSkipPrompt = false; + + if (aCacheKey == undefined) + aCacheKey = null; + + // Note: aDocument == null when this code is used by save-link-as... + var saveMode = GetSaveModeForContentType(aContentType, aDocument); + + var file, sourceURI, saveAsType; + // Find the URI object for aURL and the FileName/Extension to use when saving. + // FileName/Extension will be ignored if aChosenData supplied. + if (aChosenData) { + file = aChosenData.file; + sourceURI = aChosenData.uri; + saveAsType = kSaveAsType_Complete; + + continueSave(); + } else { + var charset = null; + if (aDocument) + charset = aDocument.characterSet; + else if (aReferrer) + charset = aReferrer.originCharset; + var fileInfo = new FileInfo(aDefaultFileName); + initFileInfo(fileInfo, aURL, charset, aDocument, + aContentType, aContentDisposition); + sourceURI = fileInfo.uri; + + var fpParams = { + fpTitleKey: aFilePickerTitleKey, + fileInfo: fileInfo, + contentType: aContentType, + saveMode: saveMode, + saveAsType: kSaveAsType_Complete, + file: file + }; + + // Find a URI to use for determining last-downloaded-to directory + let relatedURI = aReferrer || sourceURI; + + promiseTargetFile(fpParams, aSkipPrompt, relatedURI).then(aDialogAccepted => { + if (!aDialogAccepted) + return; + + saveAsType = fpParams.saveAsType; + file = fpParams.file; + + continueSave(); + }).then(null, Components.utils.reportError); + } + + function continueSave() { + // XXX We depend on the following holding true in appendFiltersForContentType(): + // If we should save as a complete page, the saveAsType is kSaveAsType_Complete. + // If we should save as text, the saveAsType is kSaveAsType_Text. + var useSaveDocument = aDocument && + (((saveMode & SAVEMODE_COMPLETE_DOM) && (saveAsType == kSaveAsType_Complete)) || + ((saveMode & SAVEMODE_COMPLETE_TEXT) && (saveAsType == kSaveAsType_Text))); + // If we're saving a document, and are saving either in complete mode or + // as converted text, pass the document to the web browser persist component. + // If we're just saving the HTML (second option in the list), send only the URI. + let nonCPOWDocument = + aDocument && !Components.utils.isCrossProcessWrapper(aDocument); + + let isPrivate = aIsContentWindowPrivate; + if (isPrivate === undefined) { + isPrivate = aInitiatingDocument instanceof Components.interfaces.nsIDOMDocument + ? PrivateBrowsingUtils.isContentWindowPrivate(aInitiatingDocument.defaultView) + : aInitiatingDocument.isPrivate; + } + + var persistArgs = { + sourceURI : sourceURI, + sourceReferrer : aReferrer, + sourceDocument : useSaveDocument ? aDocument : null, + targetContentType : (saveAsType == kSaveAsType_Text) ? "text/plain" : null, + targetFile : file, + sourceCacheKey : aCacheKey, + sourcePostData : nonCPOWDocument ? getPostData(aDocument) : null, + bypassCache : aShouldBypassCache, + isPrivate : isPrivate, + }; + + // Start the actual save process + internalPersist(persistArgs); + } +} + +/** + * internalPersist: Creates a 'Persist' object (which will perform the saving + * in the background) and then starts it. + * + * @param persistArgs.sourceURI + * The nsIURI of the document being saved + * @param persistArgs.sourceCacheKey [optional] + * If set will be passed to saveURI + * @param persistArgs.sourceDocument [optional] + * The document to be saved, or null if not saving a complete document + * @param persistArgs.sourceReferrer + * Required and used only when persistArgs.sourceDocument is NOT present, + * the nsIURI of the referrer to use, or null if no referrer should be + * sent. + * @param persistArgs.sourcePostData + * Required and used only when persistArgs.sourceDocument is NOT present, + * represents the POST data to be sent along with the HTTP request, and + * must be null if no POST data should be sent. + * @param persistArgs.targetFile + * The nsIFile of the file to create + * @param persistArgs.targetContentType + * Required and used only when persistArgs.sourceDocument is present, + * determines the final content type of the saved file, or null to use + * the same content type as the source document. Currently only + * "text/plain" is meaningful. + * @param persistArgs.bypassCache + * If true, the document will always be refetched from the server + * @param persistArgs.isPrivate + * Indicates whether this is taking place in a private browsing context. + */ +function internalPersist(persistArgs) +{ + var persist = makeWebBrowserPersist(); + + // Calculate persist flags. + const nsIWBP = Components.interfaces.nsIWebBrowserPersist; + const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES | + nsIWBP.PERSIST_FLAGS_FORCE_ALLOW_COOKIES; + if (persistArgs.bypassCache) + persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_BYPASS_CACHE; + else + persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_FROM_CACHE; + + // Leave it to WebBrowserPersist to discover the encoding type (or lack thereof): + persist.persistFlags |= nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; + + // Find the URI associated with the target file + var targetFileURL = makeFileURI(persistArgs.targetFile); + + // Create download and initiate it (below) + var tr = Components.classes["@mozilla.org/transfer;1"].createInstance(Components.interfaces.nsITransfer); + tr.init(persistArgs.sourceURI, + targetFileURL, "", null, null, null, persist, persistArgs.isPrivate); + persist.progressListener = new DownloadListener(window, tr); + + if (persistArgs.sourceDocument) { + // Saving a Document, not a URI: + var filesFolder = null; + if (persistArgs.targetContentType != "text/plain") { + // Create the local directory into which to save associated files. + filesFolder = persistArgs.targetFile.clone(); + + var nameWithoutExtension = getFileBaseName(filesFolder.leafName); + var filesFolderLeafName = + ContentAreaUtils.stringBundle + .formatStringFromName("filesFolder", [nameWithoutExtension], 1); + + filesFolder.leafName = filesFolderLeafName; + } + + var encodingFlags = 0; + if (persistArgs.targetContentType == "text/plain") { + encodingFlags |= nsIWBP.ENCODE_FLAGS_FORMATTED; + encodingFlags |= nsIWBP.ENCODE_FLAGS_ABSOLUTE_LINKS; + encodingFlags |= nsIWBP.ENCODE_FLAGS_NOFRAMES_CONTENT; + } + else { + encodingFlags |= nsIWBP.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES; + } + + const kWrapColumn = 80; + persist.saveDocument(persistArgs.sourceDocument, targetFileURL, filesFolder, + persistArgs.targetContentType, encodingFlags, kWrapColumn); + } else { + persist.savePrivacyAwareURI(persistArgs.sourceURI, + persistArgs.sourceCacheKey, + persistArgs.sourceReferrer, + Components.interfaces.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE, + persistArgs.sourcePostData, + null, + targetFileURL, + persistArgs.isPrivate); + } +} + +/** + * Structure for holding info about automatically supplied parameters for + * internalSave(...). This allows parameters to be supplied so the user does not + * need to be prompted for file info. + * @param aFileAutoChosen This is an nsIFile object that has been + * pre-determined as the filename for the target to save to + * @param aUriAutoChosen This is the nsIURI object for the target + */ +function AutoChosen(aFileAutoChosen, aUriAutoChosen) { + this.file = aFileAutoChosen; + this.uri = aUriAutoChosen; +} + +/** + * Structure for holding info about a URL and the target filename it should be + * saved to. This structure is populated by initFileInfo(...). + * @param aSuggestedFileName This is used by initFileInfo(...) when it + * cannot 'discover' the filename from the url + * @param aFileName The target filename + * @param aFileBaseName The filename without the file extension + * @param aFileExt The extension of the filename + * @param aUri An nsIURI object for the url that is being saved + */ +function FileInfo(aSuggestedFileName, aFileName, aFileBaseName, aFileExt, aUri) { + this.suggestedFileName = aSuggestedFileName; + this.fileName = aFileName; + this.fileBaseName = aFileBaseName; + this.fileExt = aFileExt; + this.uri = aUri; +} + +/** + * Determine what the 'default' filename string is, its file extension and the + * filename without the extension. This filename is used when prompting the user + * for confirmation in the file picker dialog. + * @param aFI A FileInfo structure into which we'll put the results of this method. + * @param aURL The String representation of the URL of the document being saved + * @param aURLCharset The charset of aURL. + * @param aDocument The document to be saved + * @param aContentType The content type we're saving, if it could be + * determined by the caller. + * @param aContentDisposition The content-disposition header for the object + * we're saving, if it could be determined by the caller. + */ +function initFileInfo(aFI, aURL, aURLCharset, aDocument, + aContentType, aContentDisposition) +{ + try { + // Get an nsIURI object from aURL if possible: + try { + aFI.uri = makeURI(aURL, aURLCharset); + // Assuming nsiUri is valid, calling QueryInterface(...) on it will + // populate extra object fields (eg filename and file extension). + var url = aFI.uri.QueryInterface(Components.interfaces.nsIURL); + aFI.fileExt = url.fileExtension; + } catch (e) { + } + + // Get the default filename: + aFI.fileName = getDefaultFileName((aFI.suggestedFileName || aFI.fileName), + aFI.uri, aDocument, aContentDisposition); + // If aFI.fileExt is still blank, consider: aFI.suggestedFileName is supplied + // if saveURL(...) was the original caller (hence both aContentType and + // aDocument are blank). If they were saving a link to a website then make + // the extension .htm . + if (!aFI.fileExt && !aDocument && !aContentType && (/^http(s?):\/\//i.test(aURL))) { + aFI.fileExt = "htm"; + aFI.fileBaseName = aFI.fileName; + } else { + aFI.fileExt = getDefaultExtension(aFI.fileName, aFI.uri, aContentType); + aFI.fileBaseName = getFileBaseName(aFI.fileName); + } + } catch (e) { + } +} + +/** + * Given the Filepicker Parameters (aFpP), show the file picker dialog, + * prompting the user to confirm (or change) the fileName. + * @param aFpP + * A structure (see definition in internalSave(...) method) + * containing all the data used within this method. + * @param aSkipPrompt + * If true, attempt to save the file automatically to the user's default + * download directory, thus skipping the explicit prompt for a file name, + * but only if the associated preference is set. + * If false, don't save the file automatically to the user's + * default download directory, even if the associated preference + * is set, but ask for the target explicitly. + * @param aRelatedURI + * An nsIURI associated with the download. The last used + * directory of the picker is retrieved from/stored in the + * Content Pref Service using this URI. + * @return Promise + * @resolve a boolean. When true, it indicates that the file picker dialog + * is accepted. + */ +function promiseTargetFile(aFpP, /* optional */ aSkipPrompt, /* optional */ aRelatedURI) +{ + return Task.spawn(function*() { + let downloadLastDir = new DownloadLastDir(window); + let prefBranch = Services.prefs.getBranch("browser.download."); + let useDownloadDir = prefBranch.getBoolPref("useDownloadDir"); + + if (!aSkipPrompt) + useDownloadDir = false; + + // Default to the user's default downloads directory configured + // through download prefs. + let dirPath = yield Downloads.getPreferredDownloadsDirectory(); + let dirExists = yield OS.File.exists(dirPath); + let dir = new FileUtils.File(dirPath); + + if (useDownloadDir && dirExists) { + dir.append(getNormalizedLeafName(aFpP.fileInfo.fileName, + aFpP.fileInfo.fileExt)); + aFpP.file = uniqueFile(dir); + return true; + } + + // We must prompt for the file name explicitly. + // If we must prompt because we were asked to... + let deferred = Promise.defer(); + if (useDownloadDir) { + // Keep async behavior in both branches + Services.tm.mainThread.dispatch(function() { + deferred.resolve(null); + }, Components.interfaces.nsIThread.DISPATCH_NORMAL); + } else { + downloadLastDir.getFileAsync(aRelatedURI, function getFileAsyncCB(aFile) { + deferred.resolve(aFile); + }); + } + let file = yield deferred.promise; + if (file && (yield OS.File.exists(file.path))) { + dir = file; + dirExists = true; + } + + if (!dirExists) { + // Default to desktop. + dir = Services.dirsvc.get("Desk", Components.interfaces.nsIFile); + } + + let fp = makeFilePicker(); + let titleKey = aFpP.fpTitleKey || "SaveLinkTitle"; + fp.init(window, ContentAreaUtils.stringBundle.GetStringFromName(titleKey), + Components.interfaces.nsIFilePicker.modeSave); + + fp.displayDirectory = dir; + fp.defaultExtension = aFpP.fileInfo.fileExt; + fp.defaultString = getNormalizedLeafName(aFpP.fileInfo.fileName, + aFpP.fileInfo.fileExt); + appendFiltersForContentType(fp, aFpP.contentType, aFpP.fileInfo.fileExt, + aFpP.saveMode); + + // The index of the selected filter is only preserved and restored if there's + // more than one filter in addition to "All Files". + if (aFpP.saveMode != SAVEMODE_FILEONLY) { + try { + fp.filterIndex = prefBranch.getIntPref("save_converter_index"); + } + catch (e) { + } + } + + let deferComplete = Promise.defer(); + fp.open(function(aResult) { + deferComplete.resolve(aResult); + }); + let result = yield deferComplete.promise; + if (result == Components.interfaces.nsIFilePicker.returnCancel || !fp.file) { + return false; + } + + if (aFpP.saveMode != SAVEMODE_FILEONLY) + prefBranch.setIntPref("save_converter_index", fp.filterIndex); + + // Do not store the last save directory as a pref inside the private browsing mode + downloadLastDir.setFile(aRelatedURI, fp.file.parent); + + fp.file.leafName = validateFileName(fp.file.leafName); + + aFpP.saveAsType = fp.filterIndex; + aFpP.file = fp.file; + aFpP.fileURL = fp.fileURL; + + return true; + }); +} + +// Since we're automatically downloading, we don't get the file picker's +// logic to check for existing files, so we need to do that here. +// +// Note - this code is identical to that in +// mozilla/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in +// If you are updating this code, update that code too! We can't share code +// here since that code is called in a js component. +function uniqueFile(aLocalFile) +{ + var collisionCount = 0; + while (aLocalFile.exists()) { + collisionCount++; + if (collisionCount == 1) { + // Append "(2)" before the last dot in (or at the end of) the filename + // special case .ext.gz etc files so we don't wind up with .tar(2).gz + if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i)) + aLocalFile.leafName = aLocalFile.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&"); + else + aLocalFile.leafName = aLocalFile.leafName.replace(/(\.[^\.]*)?$/, "(2)$&"); + } + else { + // replace the last (n) in the filename with (n+1) + aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount + 1) + ")"); + } + } + return aLocalFile; +} + +/** + * Download a URL using the new jsdownloads API. + * + * @param aURL + * the url to download + * @param [optional] aFileName + * the destination file name, if omitted will be obtained from the url. + * @param aInitiatingDocument + * The document from which the download was initiated. + */ +function DownloadURL(aURL, aFileName, aInitiatingDocument) { + // For private browsing, try to get document out of the most recent browser + // window, or provide our own if there's no browser window. + let isPrivate = aInitiatingDocument.defaultView + .QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebNavigation) + .QueryInterface(Components.interfaces.nsILoadContext) + .usePrivateBrowsing; + + let fileInfo = new FileInfo(aFileName); + initFileInfo(fileInfo, aURL, null, null, null, null); + + let filepickerParams = { + fileInfo: fileInfo, + saveMode: SAVEMODE_FILEONLY + }; + + Task.spawn(function* () { + let accepted = yield promiseTargetFile(filepickerParams, true, fileInfo.uri); + if (!accepted) + return; + + let file = filepickerParams.file; + let download = yield Downloads.createDownload({ + source: { url: aURL, isPrivate: isPrivate }, + target: { path: file.path, partFilePath: file.path + ".part" } + }); + download.tryToKeepPartialData = true; + + // Ignore errors because failures are reported through the download list. + download.start().catch(() => {}); + + // Add the download to the list, allowing it to be managed. + let list = yield Downloads.getList(Downloads.ALL); + list.add(download); + }).then(null, Components.utils.reportError); +} + +// We have no DOM, and can only save the URL as is. +const SAVEMODE_FILEONLY = 0x00; +XPCOMUtils.defineConstant(this, "SAVEMODE_FILEONLY", SAVEMODE_FILEONLY); +// We have a DOM and can save as complete. +const SAVEMODE_COMPLETE_DOM = 0x01; +XPCOMUtils.defineConstant(this, "SAVEMODE_COMPLETE_DOM", SAVEMODE_COMPLETE_DOM); +// We have a DOM which we can serialize as text. +const SAVEMODE_COMPLETE_TEXT = 0x02; +XPCOMUtils.defineConstant(this, "SAVEMODE_COMPLETE_TEXT", SAVEMODE_COMPLETE_TEXT); + +// If we are able to save a complete DOM, the 'save as complete' filter +// must be the first filter appended. The 'save page only' counterpart +// must be the second filter appended. And the 'save as complete text' +// filter must be the third filter appended. +function appendFiltersForContentType(aFilePicker, aContentType, aFileExtension, aSaveMode) +{ + // The bundle name for saving only a specific content type. + var bundleName; + // The corresponding filter string for a specific content type. + var filterString; + + // Every case where GetSaveModeForContentType can return non-FILEONLY + // modes must be handled here. + if (aSaveMode != SAVEMODE_FILEONLY) { + switch (aContentType) { + case "text/html": + bundleName = "WebPageHTMLOnlyFilter"; + filterString = "*.htm; *.html"; + break; + + case "application/xhtml+xml": + bundleName = "WebPageXHTMLOnlyFilter"; + filterString = "*.xht; *.xhtml"; + break; + + case "image/svg+xml": + bundleName = "WebPageSVGOnlyFilter"; + filterString = "*.svg; *.svgz"; + break; + + case "text/xml": + case "application/xml": + bundleName = "WebPageXMLOnlyFilter"; + filterString = "*.xml"; + break; + } + } + + if (!bundleName) { + if (aSaveMode != SAVEMODE_FILEONLY) + throw "Invalid save mode for type '" + aContentType + "'"; + + var mimeInfo = getMIMEInfoForType(aContentType, aFileExtension); + if (mimeInfo) { + + var extEnumerator = mimeInfo.getFileExtensions(); + + var extString = ""; + while (extEnumerator.hasMore()) { + var extension = extEnumerator.getNext(); + if (extString) + extString += "; "; // If adding more than one extension, + // separate by semi-colon + extString += "*." + extension; + } + + if (extString) + aFilePicker.appendFilter(mimeInfo.description, extString); + } + } + + if (aSaveMode & SAVEMODE_COMPLETE_DOM) { + aFilePicker.appendFilter(ContentAreaUtils.stringBundle.GetStringFromName("WebPageCompleteFilter"), + filterString); + // We should always offer a choice to save document only if + // we allow saving as complete. + aFilePicker.appendFilter(ContentAreaUtils.stringBundle.GetStringFromName(bundleName), + filterString); + } + + if (aSaveMode & SAVEMODE_COMPLETE_TEXT) + aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterText); + + // Always append the all files (*) filter + aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterAll); +} + +function getPostData(aDocument) +{ + const Ci = Components.interfaces; + + if (aDocument instanceof Ci.nsIWebBrowserPersistDocument) { + return aDocument.postData; + } + try { + // Find the session history entry corresponding to the given document. In + // the current implementation, nsIWebPageDescriptor.currentDescriptor always + // returns a session history entry. + let sessionHistoryEntry = + aDocument.defaultView + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIWebPageDescriptor) + .currentDescriptor + .QueryInterface(Ci.nsISHEntry); + return sessionHistoryEntry.postData; + } + catch (e) { + } + return null; +} + +function makeWebBrowserPersist() +{ + const persistContractID = "@mozilla.org/embedding/browser/nsWebBrowserPersist;1"; + const persistIID = Components.interfaces.nsIWebBrowserPersist; + return Components.classes[persistContractID].createInstance(persistIID); +} + +function makeURI(aURL, aOriginCharset, aBaseURI) +{ + return BrowserUtils.makeURI(aURL, aOriginCharset, aBaseURI); +} + +function makeFileURI(aFile) +{ + return BrowserUtils.makeFileURI(aFile); +} + +function makeFilePicker() +{ + const fpContractID = "@mozilla.org/filepicker;1"; + const fpIID = Components.interfaces.nsIFilePicker; + return Components.classes[fpContractID].createInstance(fpIID); +} + +function getMIMEService() +{ + const mimeSvcContractID = "@mozilla.org/mime;1"; + const mimeSvcIID = Components.interfaces.nsIMIMEService; + const mimeSvc = Components.classes[mimeSvcContractID].getService(mimeSvcIID); + return mimeSvc; +} + +// Given aFileName, find the fileName without the extension on the end. +function getFileBaseName(aFileName) +{ + // Remove the file extension from aFileName: + return aFileName.replace(/\.[^.]*$/, ""); +} + +function getMIMETypeForURI(aURI) +{ + try { + return getMIMEService().getTypeFromURI(aURI); + } + catch (e) { + } + return null; +} + +function getMIMEInfoForType(aMIMEType, aExtension) +{ + if (aMIMEType || aExtension) { + try { + return getMIMEService().getFromTypeAndExtension(aMIMEType, aExtension); + } + catch (e) { + } + } + return null; +} + +function getDefaultFileName(aDefaultFileName, aURI, aDocument, + aContentDisposition) +{ + // 1) look for a filename in the content-disposition header, if any + if (aContentDisposition) { + const mhpContractID = "@mozilla.org/network/mime-hdrparam;1"; + const mhpIID = Components.interfaces.nsIMIMEHeaderParam; + const mhp = Components.classes[mhpContractID].getService(mhpIID); + var dummy = { value: null }; // Need an out param... + var charset = getCharsetforSave(aDocument); + + var fileName = null; + try { + fileName = mhp.getParameter(aContentDisposition, "filename", charset, + true, dummy); + } + catch (e) { + try { + fileName = mhp.getParameter(aContentDisposition, "name", charset, true, + dummy); + } + catch (e) { + } + } + if (fileName) + return fileName; + } + + let docTitle; + if (aDocument) { + // If the document looks like HTML or XML, try to use its original title. + docTitle = validateFileName(aDocument.title).trim(); + if (docTitle) { + let contentType = aDocument.contentType; + if (contentType == "application/xhtml+xml" || + contentType == "application/xml" || + contentType == "image/svg+xml" || + contentType == "text/html" || + contentType == "text/xml") { + // 2) Use the document title + return docTitle; + } + } + } + + try { + var url = aURI.QueryInterface(Components.interfaces.nsIURL); + if (url.fileName != "") { + // 3) Use the actual file name, if present + var textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"] + .getService(Components.interfaces.nsITextToSubURI); + return validateFileName(textToSubURI.unEscapeURIForUI(url.originCharset || "UTF-8", url.fileName)); + } + } catch (e) { + // This is something like a data: and so forth URI... no filename here. + } + + if (docTitle) + // 4) Use the document title + return docTitle; + + if (aDefaultFileName) + // 5) Use the caller-provided name, if any + return validateFileName(aDefaultFileName); + + // 6) If this is a directory, use the last directory name + var path = aURI.path.match(/\/([^\/]+)\/$/); + if (path && path.length > 1) + return validateFileName(path[1]); + + try { + if (aURI.host) + // 7) Use the host. + return aURI.host; + } catch (e) { + // Some files have no information at all, like Javascript generated pages + } + try { + // 8) Use the default file name + return ContentAreaUtils.stringBundle.GetStringFromName("DefaultSaveFileName"); + } catch (e) { + // in case localized string cannot be found + } + // 9) If all else fails, use "index" + return "index"; +} + +function validateFileName(aFileName) +{ + aFileName = aFileName.replace(/[\u200e\u200f\u202a-\u202e]/g, ""); + var re = /[\/]+/g; + if (navigator.appVersion.indexOf("Windows") != -1) { + re = /[\\\/\|]+/g; + aFileName = aFileName.replace(/[\"]+/g, "'"); + aFileName = aFileName.replace(/[\*\:\?]+/g, " "); + aFileName = aFileName.replace(/[\<]+/g, "("); + aFileName = aFileName.replace(/[\>]+/g, ")"); + } + else if (navigator.appVersion.indexOf("Macintosh") != -1) { + re = /[\:\/]+/g; + } + + return aFileName.replace(re, "_"); +} + +function getNormalizedLeafName(aFile, aDefaultExtension) +{ + if (!aDefaultExtension) + return aFile; + +#ifdef XP_WIN + // Remove trailing dots and spaces on windows + aFile = aFile.replace(/[\s.]+$/, ""); +#endif + + // Remove leading dots + aFile = aFile.replace(/^\.+/, ""); + + // Fix up the file name we're saving to to include the default extension + var i = aFile.lastIndexOf("."); + if (aFile.substr(i + 1) != aDefaultExtension) + return aFile + "." + aDefaultExtension; + + return aFile; +} + +function getDefaultExtension(aFilename, aURI, aContentType) +{ + if (aContentType == "text/plain" || aContentType == "application/octet-stream" || aURI.scheme == "ftp") + return ""; // temporary fix for bug 120327 + + // First try the extension from the filename + const stdURLContractID = "@mozilla.org/network/standard-url;1"; + const stdURLIID = Components.interfaces.nsIURL; + var url = Components.classes[stdURLContractID].createInstance(stdURLIID); + url.filePath = aFilename; + + var ext = url.fileExtension; + + // This mirrors some code in nsExternalHelperAppService::DoContent + // Use the filename first and then the URI if that fails + + var mimeInfo = getMIMEInfoForType(aContentType, ext); + + if (ext && mimeInfo && mimeInfo.extensionExists(ext)) + return ext; + + // Well, that failed. Now try the extension from the URI + var urlext; + try { + url = aURI.QueryInterface(Components.interfaces.nsIURL); + urlext = url.fileExtension; + } catch (e) { + } + + if (urlext && mimeInfo && mimeInfo.extensionExists(urlext)) { + return urlext; + } + try { + if (mimeInfo) + return mimeInfo.primaryExtension; + } + catch (e) { + } + // Fall back on the extensions in the filename and URI for lack + // of anything better. + return ext || urlext; +} + +function GetSaveModeForContentType(aContentType, aDocument) +{ + // We can only save a complete page if we have a loaded document, + // and it's not a CPOW -- nsWebBrowserPersist needs a real document. + if (!aDocument || Components.utils.isCrossProcessWrapper(aDocument)) + return SAVEMODE_FILEONLY; + + // Find the possible save modes using the provided content type + var saveMode = SAVEMODE_FILEONLY; + switch (aContentType) { + case "text/html": + case "application/xhtml+xml": + case "image/svg+xml": + saveMode |= SAVEMODE_COMPLETE_TEXT; + // Fall through + case "text/xml": + case "application/xml": + saveMode |= SAVEMODE_COMPLETE_DOM; + break; + } + + return saveMode; +} + +function getCharsetforSave(aDocument) +{ + if (aDocument) + return aDocument.characterSet; + + if (document.commandDispatcher.focusedWindow) + return document.commandDispatcher.focusedWindow.document.characterSet; + + return window.content.document.characterSet; +} + +/** + * Open a URL from chrome, determining if we can handle it internally or need to + * launch an external application to handle it. + * @param aURL The URL to be opened + * + * WARNING: Please note that openURL() does not perform any content security checks!!! + */ +function openURL(aURL) +{ + var uri = makeURI(aURL); + + var protocolSvc = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"] + .getService(Components.interfaces.nsIExternalProtocolService); + + if (!protocolSvc.isExposedProtocol(uri.scheme)) { + // If we're not a browser, use the external protocol service to load the URI. + protocolSvc.loadUrl(uri); + } + else { + var recentWindow = Services.wm.getMostRecentWindow("navigator:browser"); + if (recentWindow) { + recentWindow.openUILinkIn(uri.spec, "tab"); + return; + } + + var loadgroup = Components.classes["@mozilla.org/network/load-group;1"] + .createInstance(Components.interfaces.nsILoadGroup); + var appstartup = Services.startup; + + var loadListener = { + onStartRequest: function ll_start(aRequest, aContext) { + appstartup.enterLastWindowClosingSurvivalArea(); + }, + onStopRequest: function ll_stop(aRequest, aContext, aStatusCode) { + appstartup.exitLastWindowClosingSurvivalArea(); + }, + QueryInterface: function ll_QI(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsIRequestObserver) || + iid.equals(Components.interfaces.nsISupportsWeakReference)) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + } + } + loadgroup.groupObserver = loadListener; + + var uriListener = { + onStartURIOpen: function(uri) { return false; }, + doContent: function(ctype, preferred, request, handler) { return false; }, + isPreferred: function(ctype, desired) { return false; }, + canHandleContent: function(ctype, preferred, desired) { return false; }, + loadCookie: null, + parentContentListener: null, + getInterface: function(iid) { + if (iid.equals(Components.interfaces.nsIURIContentListener)) + return this; + if (iid.equals(Components.interfaces.nsILoadGroup)) + return loadgroup; + throw Components.results.NS_ERROR_NO_INTERFACE; + } + } + + var channel = NetUtil.newChannel({ + uri: uri, + loadUsingSystemPrincipal: true + }); + + var uriLoader = Components.classes["@mozilla.org/uriloader;1"] + .getService(Components.interfaces.nsIURILoader); + uriLoader.openURI(channel, + Components.interfaces.nsIURILoader.IS_CONTENT_PREFERRED, + uriListener); + } +} diff --git a/components/global/content/customizeToolbar.css b/components/global/content/customizeToolbar.css new file mode 100644 index 000000000..ae90a2f28 --- /dev/null +++ b/components/global/content/customizeToolbar.css @@ -0,0 +1,39 @@ +/* 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"); /* set default namespace to XUL */ +@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */ + +#palette-box { + overflow: auto; + display: block; + min-height: 3em; +} + +#palette-box > toolbarpaletteitem { + width: 110px; + height: 94px; + overflow: hidden; + display: inline-block; +} + +.toolbarpaletteitem-box { + -moz-box-pack: center; + -moz-box-flex: 1; + width: 110px; + max-width: 110px; +} + +toolbarpaletteitem > label { + text-align: center; +} + +#main-box > box { + overflow: hidden; +} + +/* Hide the toolbarbutton label because we replicate it on the wrapper */ +.toolbarbutton-text { + display: none; +} diff --git a/components/global/content/customizeToolbar.js b/components/global/content/customizeToolbar.js new file mode 100644 index 000000000..1775d9f52 --- /dev/null +++ b/components/global/content/customizeToolbar.js @@ -0,0 +1,852 @@ +/* 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 gToolbarInfoSeparators = ["|", "-"]; + +var gToolboxDocument = null; +var gToolbox = null; +var gCurrentDragOverItem = null; +var gToolboxChanged = false; +var gToolboxSheet = false; +var gPaletteBox = null; + +Components.utils.import("resource://gre/modules/Services.jsm"); + +function onLoad() +{ + if ("arguments" in window && window.arguments[0]) { + InitWithToolbox(window.arguments[0]); + repositionDialog(window); + } + else if (window.frameElement && + "toolbox" in window.frameElement) { + gToolboxSheet = true; + InitWithToolbox(window.frameElement.toolbox); + repositionDialog(window.frameElement.panel); + } +} + +function InitWithToolbox(aToolbox) +{ + gToolbox = aToolbox; + dispatchCustomizationEvent("beforecustomization"); + gToolboxDocument = gToolbox.ownerDocument; + gToolbox.customizing = true; + forEachCustomizableToolbar(function (toolbar) { + toolbar.setAttribute("customizing", "true"); + }); + gPaletteBox = document.getElementById("palette-box"); + + var elts = getRootElements(); + for (let i=0; i < elts.length; i++) { + elts[i].addEventListener("dragstart", onToolbarDragStart, true); + elts[i].addEventListener("dragover", onToolbarDragOver, true); + elts[i].addEventListener("dragexit", onToolbarDragExit, true); + elts[i].addEventListener("drop", onToolbarDrop, true); + } + + initDialog(); +} + +function onClose() +{ + if (!gToolboxSheet) + window.close(); + else + finishToolbarCustomization(); +} + +function onUnload() +{ + if (!gToolboxSheet) + finishToolbarCustomization(); +} + +function finishToolbarCustomization() +{ + removeToolboxListeners(); + unwrapToolbarItems(); + persistCurrentSets(); + gToolbox.customizing = false; + forEachCustomizableToolbar(function (toolbar) { + toolbar.removeAttribute("customizing"); + }); + + notifyParentComplete(); +} + +function initDialog() +{ + if (!gToolbox.toolbarset) { + document.getElementById("newtoolbar").hidden = true; + } + + var mode = gToolbox.getAttribute("mode"); + document.getElementById("modelist").value = mode; + var smallIconsCheckbox = document.getElementById("smallicons"); + smallIconsCheckbox.checked = gToolbox.getAttribute("iconsize") == "small"; + if (mode == "text") + smallIconsCheckbox.disabled = true; + + // Build up the palette of other items. + buildPalette(); + + // Wrap all the items on the toolbar in toolbarpaletteitems. + wrapToolbarItems(); +} + +function repositionDialog(aWindow) +{ + // Position the dialog touching the bottom of the toolbox and centered with + // it. + if (!aWindow) + return; + + var width; + if (aWindow != window) + width = aWindow.getBoundingClientRect().width; + else if (document.documentElement.hasAttribute("width")) + width = document.documentElement.getAttribute("width"); + else + width = parseInt(document.documentElement.style.width); + var screenX = gToolbox.boxObject.screenX + + ((gToolbox.boxObject.width - width) / 2); + var screenY = gToolbox.boxObject.screenY + gToolbox.boxObject.height; + + aWindow.moveTo(screenX, screenY); +} + +function removeToolboxListeners() +{ + var elts = getRootElements(); + for (let i=0; i < elts.length; i++) { + elts[i].removeEventListener("dragstart", onToolbarDragStart, true); + elts[i].removeEventListener("dragover", onToolbarDragOver, true); + elts[i].removeEventListener("dragexit", onToolbarDragExit, true); + elts[i].removeEventListener("drop", onToolbarDrop, true); + } +} + +/** + * Invoke a callback on the toolbox to notify it that the dialog is done + * and going away. + */ +function notifyParentComplete() +{ + if ("customizeDone" in gToolbox) + gToolbox.customizeDone(gToolboxChanged); + dispatchCustomizationEvent("aftercustomization"); +} + +function toolboxChanged(aType) +{ + gToolboxChanged = true; + if ("customizeChange" in gToolbox) + gToolbox.customizeChange(aType); + dispatchCustomizationEvent("customizationchange"); +} + +function dispatchCustomizationEvent(aEventName) { + var evt = document.createEvent("Events"); + evt.initEvent(aEventName, true, true); + gToolbox.dispatchEvent(evt); +} + +/** + * Persist the current set of buttons in all customizable toolbars to + * localstore. + */ +function persistCurrentSets() +{ + if (!gToolboxChanged || gToolboxDocument.defaultView.closed) + return; + + var customCount = 0; + forEachCustomizableToolbar(function (toolbar) { + // Calculate currentset and store it in the attribute. + var currentSet = toolbar.currentSet; + toolbar.setAttribute("currentset", currentSet); + + var customIndex = toolbar.hasAttribute("customindex"); + if (customIndex) { + if (!toolbar.hasChildNodes()) { + // Remove custom toolbars whose contents have been removed. + gToolbox.removeChild(toolbar); + } else if (gToolbox.toolbarset) { + var hidingAttribute = toolbar.getAttribute("type") == "menubar" ? + "autohide" : "collapsed"; + // Persist custom toolbar info on the <toolbarset/> + // Attributes: + // Names: "toolbarX" (X - the number of the toolbar) + // Values: "Name|HidingAttributeName-HidingAttributeValue|CurrentSet" + gToolbox.toolbarset.setAttribute("toolbar" + (++customCount), + toolbar.toolbarName + + gToolbarInfoSeparators[0] + + hidingAttribute + + gToolbarInfoSeparators[1] + + toolbar.getAttribute(hidingAttribute) + + gToolbarInfoSeparators[0] + + currentSet); + gToolboxDocument.persist(gToolbox.toolbarset.id, "toolbar"+customCount); + } + } + + if (!customIndex) { + // Persist the currentset attribute directly on hardcoded toolbars. + gToolboxDocument.persist(toolbar.id, "currentset"); + } + }); + + // Remove toolbarX attributes for removed toolbars. + while (gToolbox.toolbarset && gToolbox.toolbarset.hasAttribute("toolbar"+(++customCount))) { + gToolbox.toolbarset.removeAttribute("toolbar"+customCount); + gToolboxDocument.persist(gToolbox.toolbarset.id, "toolbar"+customCount); + } +} + +/** + * Wraps all items in all customizable toolbars in a toolbox. + */ +function wrapToolbarItems() +{ + forEachCustomizableToolbar(function (toolbar) { + Array.forEach(toolbar.childNodes, function (item) { + if (isToolbarItem(item)) { + let wrapper = wrapToolbarItem(item); + cleanupItemForToolbar(item, wrapper); + } + }); + }); +} + +function getRootElements() +{ + return [gToolbox].concat(gToolbox.externalToolbars); +} + +/** + * Unwraps all items in all customizable toolbars in a toolbox. + */ +function unwrapToolbarItems() +{ + let elts = getRootElements(); + for (let i=0; i < elts.length; i++) { + let paletteItems = elts[i].getElementsByTagName("toolbarpaletteitem"); + let paletteItem; + while ((paletteItem = paletteItems.item(0)) != null) { + let toolbarItem = paletteItem.firstChild; + restoreItemForToolbar(toolbarItem, paletteItem); + paletteItem.parentNode.replaceChild(toolbarItem, paletteItem); + } + } +} + +/** + * Creates a wrapper that can be used to contain a toolbaritem and prevent + * it from receiving UI events. + */ +function createWrapper(aId, aDocument) +{ + var wrapper = aDocument.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", + "toolbarpaletteitem"); + + wrapper.id = "wrapper-"+aId; + return wrapper; +} + +/** + * Wraps an item that has been cloned from a template and adds + * it to the end of the palette. + */ +function wrapPaletteItem(aPaletteItem) +{ + var wrapper = createWrapper(aPaletteItem.id, document); + + wrapper.appendChild(aPaletteItem); + + // XXX We need to call this AFTER the palette item has been appended + // to the wrapper or else we crash dropping certain buttons on the + // palette due to removal of the command and disabled attributes - JRH + cleanUpItemForPalette(aPaletteItem, wrapper); + + gPaletteBox.appendChild(wrapper); +} + +/** + * Wraps an item that is currently on a toolbar and replaces the item + * with the wrapper. This is not used when dropping items from the palette, + * only when first starting the dialog and wrapping everything on the toolbars. + */ +function wrapToolbarItem(aToolbarItem) +{ + var wrapper = createWrapper(aToolbarItem.id, gToolboxDocument); + + wrapper.flex = aToolbarItem.flex; + + aToolbarItem.parentNode.replaceChild(wrapper, aToolbarItem); + + wrapper.appendChild(aToolbarItem); + + return wrapper; +} + +/** + * Get the list of ids for the current set of items on each toolbar. + */ +function getCurrentItemIds() +{ + var currentItems = {}; + forEachCustomizableToolbar(function (toolbar) { + var child = toolbar.firstChild; + while (child) { + if (isToolbarItem(child)) + currentItems[child.id] = 1; + child = child.nextSibling; + } + }); + return currentItems; +} + +/** + * Builds the palette of draggable items that are not yet in a toolbar. + */ +function buildPalette() +{ + // Empty the palette first. + while (gPaletteBox.lastChild) + gPaletteBox.removeChild(gPaletteBox.lastChild); + + // Add the toolbar separator item. + var templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", + "toolbarseparator"); + templateNode.id = "separator"; + wrapPaletteItem(templateNode); + + // Add the toolbar spring item. + templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", + "toolbarspring"); + templateNode.id = "spring"; + templateNode.flex = 1; + wrapPaletteItem(templateNode); + + // Add the toolbar spacer item. + templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", + "toolbarspacer"); + templateNode.id = "spacer"; + templateNode.flex = 1; + wrapPaletteItem(templateNode); + + var currentItems = getCurrentItemIds(); + templateNode = gToolbox.palette.firstChild; + while (templateNode) { + // Check if the item is already in a toolbar before adding it to the palette. + if (!(templateNode.id in currentItems)) { + var paletteItem = document.importNode(templateNode, true); + wrapPaletteItem(paletteItem); + } + + templateNode = templateNode.nextSibling; + } +} + +/** + * Makes sure that an item that has been cloned from a template + * is stripped of any attributes that may adversely affect its + * appearance in the palette. + */ +function cleanUpItemForPalette(aItem, aWrapper) +{ + aWrapper.setAttribute("place", "palette"); + setWrapperType(aItem, aWrapper); + + if (aItem.hasAttribute("title")) + aWrapper.setAttribute("title", aItem.getAttribute("title")); + else if (aItem.hasAttribute("label")) + aWrapper.setAttribute("title", aItem.getAttribute("label")); + else if (isSpecialItem(aItem)) { + var stringBundle = document.getElementById("stringBundle"); + // Remove the common "toolbar" prefix to generate the string name. + var title = stringBundle.getString(aItem.localName.slice(7) + "Title"); + aWrapper.setAttribute("title", title); + } + aWrapper.setAttribute("tooltiptext", aWrapper.getAttribute("title")); + + // Remove attributes that screw up our appearance. + aItem.removeAttribute("command"); + aItem.removeAttribute("observes"); + aItem.removeAttribute("type"); + aItem.removeAttribute("width"); + + Array.forEach(aWrapper.querySelectorAll("[disabled]"), function(aNode) { + aNode.removeAttribute("disabled"); + }); +} + +/** + * Makes sure that an item that has been cloned from a template + * is stripped of all properties that may adversely affect its + * appearance in the toolbar. Store critical properties on the + * wrapper so they can be put back on the item when we're done. + */ +function cleanupItemForToolbar(aItem, aWrapper) +{ + setWrapperType(aItem, aWrapper); + aWrapper.setAttribute("place", "toolbar"); + + if (aItem.hasAttribute("command")) { + aWrapper.setAttribute("itemcommand", aItem.getAttribute("command")); + aItem.removeAttribute("command"); + } + + if (aItem.checked) { + aWrapper.setAttribute("itemchecked", "true"); + aItem.checked = false; + } + + if (aItem.disabled) { + aWrapper.setAttribute("itemdisabled", "true"); + aItem.disabled = false; + } +} + +/** + * Restore all the properties that we stripped off above. + */ +function restoreItemForToolbar(aItem, aWrapper) +{ + if (aWrapper.hasAttribute("itemdisabled")) + aItem.disabled = true; + + if (aWrapper.hasAttribute("itemchecked")) + aItem.checked = true; + + if (aWrapper.hasAttribute("itemcommand")) { + let commandID = aWrapper.getAttribute("itemcommand"); + aItem.setAttribute("command", commandID); + + // XXX Bug 309953 - toolbarbuttons aren't in sync with their commands after customizing + let command = gToolboxDocument.getElementById(commandID); + if (command && command.hasAttribute("disabled")) + aItem.setAttribute("disabled", command.getAttribute("disabled")); + } +} + +function setWrapperType(aItem, aWrapper) +{ + if (aItem.localName == "toolbarseparator") { + aWrapper.setAttribute("type", "separator"); + } else if (aItem.localName == "toolbarspring") { + aWrapper.setAttribute("type", "spring"); + } else if (aItem.localName == "toolbarspacer") { + aWrapper.setAttribute("type", "spacer"); + } else if (aItem.localName == "toolbaritem" && aItem.firstChild) { + aWrapper.setAttribute("type", aItem.firstChild.localName); + } +} + +function setDragActive(aItem, aValue) +{ + var node = aItem; + var direction = window.getComputedStyle(aItem, null).direction; + var value = direction == "ltr"? "left" : "right"; + if (aItem.localName == "toolbar") { + node = aItem.lastChild; + value = direction == "ltr"? "right" : "left"; + } + + if (!node) + return; + + if (aValue) { + if (!node.hasAttribute("dragover")) + node.setAttribute("dragover", value); + } else { + node.removeAttribute("dragover"); + } +} + +function addNewToolbar() +{ + var promptService = Services.prompt; + var stringBundle = document.getElementById("stringBundle"); + var message = stringBundle.getString("enterToolbarName"); + var title = stringBundle.getString("enterToolbarTitle"); + + var name = {}; + + // Quitting from the toolbar dialog while the new toolbar prompt is up + // can cause things to become unresponsive on the Mac. Until dialog modality + // is fixed (395465), disable the "Done" button explicitly. + var doneButton = document.getElementById("donebutton"); + doneButton.disabled = true; + + while (true) { + + if (!promptService.prompt(window, title, message, name, null, {})) { + doneButton.disabled = false; + return; + } + + if (!name.value) { + message = stringBundle.getFormattedString("enterToolbarBlank", [name.value]); + continue; + } + + if (name.value.includes(gToolbarInfoSeparators[0])) { + message = stringBundle.getFormattedString("enterToolbarIllegalChars", [name.value]); + continue; + } + + var dupeFound = false; + + // Check for an existing toolbar with the same display name + for (let i = 0; i < gToolbox.childNodes.length; ++i) { + var toolbar = gToolbox.childNodes[i]; + var toolbarName = toolbar.getAttribute("toolbarname"); + + if (toolbarName == name.value && + toolbar.getAttribute("type") != "menubar" && + toolbar.nodeName == 'toolbar') { + dupeFound = true; + break; + } + } + + if (!dupeFound) + break; + + message = stringBundle.getFormattedString("enterToolbarDup", [name.value]); + } + + gToolbox.appendCustomToolbar(name.value, "", [null, null]); + + toolboxChanged(); + + doneButton.disabled = false; +} + +/** + * Restore the default set of buttons to fixed toolbars, + * remove all custom toolbars, and rebuild the palette. + */ +function restoreDefaultSet() +{ + // Unwrap the items on the toolbar. + unwrapToolbarItems(); + + // Remove all of the customized toolbars. + var child = gToolbox.lastChild; + while (child) { + if (child.hasAttribute("customindex")) { + var thisChild = child; + child = child.previousSibling; + thisChild.currentSet = "__empty"; + gToolbox.removeChild(thisChild); + } else { + child = child.previousSibling; + } + } + + // Restore the defaultset for fixed toolbars. + forEachCustomizableToolbar(function (toolbar) { + var defaultSet = toolbar.getAttribute("defaultset"); + if (defaultSet) + toolbar.currentSet = defaultSet; + }); + + // Restore the default icon size and mode. + document.getElementById("smallicons").checked = (updateIconSize() == "small"); + document.getElementById("modelist").value = updateToolbarMode(); + + // Now rebuild the palette. + buildPalette(); + + // Now re-wrap the items on the toolbar. + wrapToolbarItems(); + + toolboxChanged("reset"); +} + +function updateIconSize(aSize) { + return updateToolboxProperty("iconsize", aSize, "large"); +} + +function updateToolbarMode(aModeValue) { + var mode = updateToolboxProperty("mode", aModeValue, "icons"); + + var iconSizeCheckbox = document.getElementById("smallicons"); + iconSizeCheckbox.disabled = mode == "text"; + + return mode; +} + +function updateToolboxProperty(aProp, aValue, aToolkitDefault) { + var toolboxDefault = gToolbox.getAttribute("default" + aProp) || + aToolkitDefault; + + gToolbox.setAttribute(aProp, aValue || toolboxDefault); + gToolboxDocument.persist(gToolbox.id, aProp); + + forEachCustomizableToolbar(function (toolbar) { + var toolbarDefault = toolbar.getAttribute("default" + aProp) || + toolboxDefault; + if (toolbar.getAttribute("lock" + aProp) == "true" && + toolbar.getAttribute(aProp) == toolbarDefault) + return; + + toolbar.setAttribute(aProp, aValue || toolbarDefault); + gToolboxDocument.persist(toolbar.id, aProp); + }); + + toolboxChanged(aProp); + + return aValue || toolboxDefault; +} + +function forEachCustomizableToolbar(callback) { + Array.filter(gToolbox.childNodes, isCustomizableToolbar).forEach(callback); + Array.filter(gToolbox.externalToolbars, isCustomizableToolbar).forEach(callback); +} + +function isCustomizableToolbar(aElt) +{ + return aElt.localName == "toolbar" && + aElt.getAttribute("customizable") == "true"; +} + +function isSpecialItem(aElt) +{ + return aElt.localName == "toolbarseparator" || + aElt.localName == "toolbarspring" || + aElt.localName == "toolbarspacer"; +} + +function isToolbarItem(aElt) +{ + return aElt.localName == "toolbarbutton" || + aElt.localName == "toolbaritem" || + aElt.localName == "toolbarseparator" || + aElt.localName == "toolbarspring" || + aElt.localName == "toolbarspacer"; +} + +// Drag and Drop observers + +function onToolbarDragExit(aEvent) +{ + if (isUnwantedDragEvent(aEvent)) { + return; + } + + if (gCurrentDragOverItem) + setDragActive(gCurrentDragOverItem, false); +} + +function onToolbarDragStart(aEvent) +{ + var item = aEvent.target; + while (item && item.localName != "toolbarpaletteitem") { + if (item.localName == "toolbar") + return; + item = item.parentNode; + } + + item.setAttribute("dragactive", "true"); + + var dt = aEvent.dataTransfer; + var documentId = gToolboxDocument.documentElement.id; + dt.setData("text/toolbarwrapper-id/" + documentId, item.firstChild.id); + dt.effectAllowed = "move"; +} + +function onToolbarDragOver(aEvent) +{ + if (isUnwantedDragEvent(aEvent)) { + return; + } + + var documentId = gToolboxDocument.documentElement.id; + if (!aEvent.dataTransfer.types.includes("text/toolbarwrapper-id/" + documentId.toLowerCase())) + return; + + var toolbar = aEvent.target; + var dropTarget = aEvent.target; + while (toolbar && toolbar.localName != "toolbar") { + dropTarget = toolbar; + toolbar = toolbar.parentNode; + } + + // Make sure we are dragging over a customizable toolbar. + if (!toolbar || !isCustomizableToolbar(toolbar)) { + gCurrentDragOverItem = null; + return; + } + + var previousDragItem = gCurrentDragOverItem; + + if (dropTarget.localName == "toolbar") { + gCurrentDragOverItem = dropTarget; + } else { + gCurrentDragOverItem = null; + + var direction = window.getComputedStyle(dropTarget.parentNode, null).direction; + var dropTargetCenter = dropTarget.boxObject.x + (dropTarget.boxObject.width / 2); + var dragAfter; + if (direction == "ltr") + dragAfter = aEvent.clientX > dropTargetCenter; + else + dragAfter = aEvent.clientX < dropTargetCenter; + + if (dragAfter) { + gCurrentDragOverItem = dropTarget.nextSibling; + if (!gCurrentDragOverItem) + gCurrentDragOverItem = toolbar; + } else + gCurrentDragOverItem = dropTarget; + } + + if (previousDragItem && gCurrentDragOverItem != previousDragItem) { + setDragActive(previousDragItem, false); + } + + setDragActive(gCurrentDragOverItem, true); + + aEvent.preventDefault(); + aEvent.stopPropagation(); +} + +function onToolbarDrop(aEvent) +{ + if (isUnwantedDragEvent(aEvent)) { + return; + } + + if (!gCurrentDragOverItem) + return; + + setDragActive(gCurrentDragOverItem, false); + + var documentId = gToolboxDocument.documentElement.id; + var draggedItemId = aEvent.dataTransfer.getData("text/toolbarwrapper-id/" + documentId); + if (gCurrentDragOverItem.id == draggedItemId) + return; + + var toolbar = aEvent.target; + while (toolbar.localName != "toolbar") + toolbar = toolbar.parentNode; + + var draggedPaletteWrapper = document.getElementById("wrapper-"+draggedItemId); + if (!draggedPaletteWrapper) { + // The wrapper has been dragged from the toolbar. + // Get the wrapper from the toolbar document and make sure that + // it isn't being dropped on itself. + let wrapper = gToolboxDocument.getElementById("wrapper-"+draggedItemId); + if (wrapper == gCurrentDragOverItem) + return; + + // Don't allow non-removable kids (e.g., the menubar) to move. + if (wrapper.firstChild.getAttribute("removable") != "true") + return; + + // Remove the item from its place in the toolbar. + wrapper.parentNode.removeChild(wrapper); + + // Determine which toolbar we are dropping on. + var dropToolbar = null; + if (gCurrentDragOverItem.localName == "toolbar") + dropToolbar = gCurrentDragOverItem; + else + dropToolbar = gCurrentDragOverItem.parentNode; + + // Insert the item into the toolbar. + if (gCurrentDragOverItem != dropToolbar) + dropToolbar.insertBefore(wrapper, gCurrentDragOverItem); + else + dropToolbar.appendChild(wrapper); + } else { + // The item has been dragged from the palette + + // Create a new wrapper for the item. We don't know the id yet. + let wrapper = createWrapper("", gToolboxDocument); + + // Ask the toolbar to clone the item's template, place it inside the wrapper, and insert it in the toolbar. + var newItem = toolbar.insertItem(draggedItemId, gCurrentDragOverItem == toolbar ? null : gCurrentDragOverItem, wrapper); + + // Prepare the item and wrapper to look good on the toolbar. + cleanupItemForToolbar(newItem, wrapper); + wrapper.id = "wrapper-"+newItem.id; + wrapper.flex = newItem.flex; + + // Remove the wrapper from the palette. + if (draggedItemId != "separator" && + draggedItemId != "spring" && + draggedItemId != "spacer") + gPaletteBox.removeChild(draggedPaletteWrapper); + } + + gCurrentDragOverItem = null; + + toolboxChanged(); +} + +function onPaletteDragOver(aEvent) +{ + if (isUnwantedDragEvent(aEvent)) { + return; + } + var documentId = gToolboxDocument.documentElement.id; + if (aEvent.dataTransfer.types.includes("text/toolbarwrapper-id/" + documentId.toLowerCase())) + aEvent.preventDefault(); +} + +function onPaletteDrop(aEvent) +{ + if (isUnwantedDragEvent(aEvent)) { + return; + } + var documentId = gToolboxDocument.documentElement.id; + var itemId = aEvent.dataTransfer.getData("text/toolbarwrapper-id/" + documentId); + + var wrapper = gToolboxDocument.getElementById("wrapper-"+itemId); + if (wrapper) { + // Don't allow non-removable kids (e.g., the menubar) to move. + if (wrapper.firstChild.getAttribute("removable") != "true") + return; + + var wrapperType = wrapper.getAttribute("type"); + if (wrapperType != "separator" && + wrapperType != "spacer" && + wrapperType != "spring") { + restoreItemForToolbar(wrapper.firstChild, wrapper); + wrapPaletteItem(document.importNode(wrapper.firstChild, true)); + gToolbox.palette.appendChild(wrapper.firstChild); + } + + // The item was dragged out of the toolbar. + wrapper.parentNode.removeChild(wrapper); + } + + toolboxChanged(); +} + + +function isUnwantedDragEvent(aEvent) { + try { + if (Services.prefs.getBoolPref("toolkit.customization.unsafe_drag_events")) { + return false; + } + } catch (ex) {} + + /* Discard drag events that originated from a separate window to + prevent content->chrome privilege escalations. */ + let mozSourceNode = aEvent.dataTransfer.mozSourceNode; + // mozSourceNode is null in the dragStart event handler or if + // the drag event originated in an external application. + if (!mozSourceNode) { + return true; + } + let sourceWindow = mozSourceNode.ownerDocument.defaultView; + return sourceWindow != window && sourceWindow != gToolboxDocument.defaultView; +} + diff --git a/components/global/content/customizeToolbar.xul b/components/global/content/customizeToolbar.xul new file mode 100644 index 000000000..09c45e834 --- /dev/null +++ b/components/global/content/customizeToolbar.xul @@ -0,0 +1,67 @@ +<?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 dialog [ +<!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd"> + %customizeToolbarDTD; +]> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://global/content/customizeToolbar.css" type="text/css"?> +<?xml-stylesheet href="chrome://global/skin/customizeToolbar.css" type="text/css"?> + +<window id="CustomizeToolbarWindow" + title="&dialog.title;" + onload="onLoad();" + onunload="onUnload();" + style="&dialog.dimensions;" + persist="width height" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script type="application/javascript" src="chrome://global/content/customizeToolbar.js"/> + +<stringbundle id="stringBundle" src="chrome://global/locale/customizeToolbar.properties"/> + +<keyset id="CustomizeToolbarKeyset"> + <key id="cmd_close1" keycode="VK_ESCAPE" oncommand="onClose();"/> + <key id="cmd_close2" keycode="VK_RETURN" oncommand="onClose();"/> +</keyset> + +<vbox id="main-box" flex="1"> + <description id="instructions"> + &instructions.description; + </description> + + <vbox flex="1" id="palette-box" + ondragstart="onToolbarDragStart(event)" + ondragover="onPaletteDragOver(event)" + ondrop="onPaletteDrop(event)"/> + + <box align="center"> + <label value="&show.label;"/> + <menulist id="modelist" value="icons" oncommand="updateToolbarMode(this.value);"> + <menupopup id="modelistpopup"> + <menuitem id="modefull" value="full" label="&iconsAndText.label;"/> + <menuitem id="modeicons" value="icons" label="&icons.label;"/> + <menuitem id="modetext" value="text" label="&text.label;"/> + </menupopup> + </menulist> + + <checkbox id="smallicons" oncommand="updateIconSize(this.checked ? 'small' : 'large');" label="&useSmallIcons.label;"/> + + <button id="newtoolbar" label="&addNewToolbar.label;" oncommand="addNewToolbar();" icon="add"/> + <button id="restoreDefault" label="&restoreDefaultSet.label;" oncommand="restoreDefaultSet();" icon="revert"/> + </box> + + <separator class="groove"/> + + <hbox align="center" pack="end"> + <button id="donebutton" label="&saveChanges.label;" oncommand="onClose();" + default="true" icon="close"/> + </hbox> +</vbox> + +</window> diff --git a/components/global/content/datepicker.xhtml b/components/global/content/datepicker.xhtml new file mode 100644 index 000000000..4da6e398f --- /dev/null +++ b/components/global/content/datepicker.xhtml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html [ + <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> + %htmlDTD; +]> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<head> + <title>Date Picker</title> + <link rel="stylesheet" href="chrome://global/skin/datetimeinputpickers.css"/> + <script type="application/javascript" src="chrome://global/content/bindings/datekeeper.js"></script> + <script type="application/javascript" src="chrome://global/content/bindings/spinner.js"></script> + <script type="application/javascript" src="chrome://global/content/bindings/calendar.js"></script> + <script type="application/javascript" src="chrome://global/content/bindings/datepicker.js"></script> +</head> +<body> + <div id="date-picker"> + <div class="calendar-container"> + <div class="nav"> + <button class="left"/> + <button class="right"/> + </div> + <div class="week-header"></div> + <div class="days-viewport"> + <div class="days-view"></div> + </div> + </div> + <div class="month-year-container"> + <button class="month-year"/> + </div> + <div class="month-year-view"></div> + </div> + <template id="spinner-template"> + <div class="spinner-container"> + <button class="up"/> + <div class="spinner"></div> + <button class="down"/> + </div> + </template> + <script type="application/javascript"> + // We need to hide the scroll bar but maintain its scrolling + // capability, so using |overflow: hidden| is not an option. + // Instead, we are inserting a user agent stylesheet that is + // capable of selecting scrollbars, and do |display: none|. + var domWinUtls = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor). + getInterface(Components.interfaces.nsIDOMWindowUtils); + domWinUtls.loadSheetUsingURIString('data:text/css,@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); scrollbar { display: none; }', domWinUtls.AGENT_SHEET); + // Create a DatePicker instance and prepare to be + // initialized by the "DatePickerInit" event from datetimepopup.xml + const root = document.getElementById("date-picker"); + new DatePicker({ + monthYear: root.querySelector(".month-year"), + monthYearView: root.querySelector(".month-year-view"), + buttonLeft: root.querySelector(".left"), + buttonRight: root.querySelector(".right"), + weekHeader: root.querySelector(".week-header"), + daysView: root.querySelector(".days-view") + }); + </script> +</body> +</html>
\ No newline at end of file diff --git a/components/global/content/dialogOverlay.js b/components/global/content/dialogOverlay.js new file mode 100644 index 000000000..4b03f268f --- /dev/null +++ b/components/global/content/dialogOverlay.js @@ -0,0 +1,107 @@ +/* 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 doOKFunction = 0; +var doCancelFunction = 0; +var doButton2Function = 0; +var doButton3Function = 0; + +// call this from dialog onload() to allow ok and cancel to call your code +// functions should return true if they want the dialog to close +function doSetOKCancel(okFunc, cancelFunc, button2Func, button3Func ) +{ + //dump("top.window.navigator.platform: " + top.window.navigator.platform + "\n"); + + doOKFunction = okFunc; + doCancelFunction = cancelFunc; + doButton2Function = button2Func; + doButton3Function = button3Func; +} + +function doOKButton() +{ + var close = true; + + if ( doOKFunction ) + close = doOKFunction(); + + if (close && top) + top.window.close(); +} + +function doCancelButton() +{ + var close = true; + + if ( doCancelFunction ) + close = doCancelFunction(); + + if (close && top) + top.window.close(); +} + +function doButton2() +{ + var close = true; + + if ( doButton2Function ) + close = doButton2Function(); + + if (close && top) + top.window.close(); +} + +function doButton3() +{ + var close = true; + + if ( doButton3Function ) + close = doButton3Function(); + + if (close && top) + top.window.close(); +} + +function moveToAlertPosition() +{ + // hack. we need this so the window has something like its final size + if (window.outerWidth == 1) { + dump("Trying to position a sizeless window; caller should have called sizeToContent() or sizeTo(). See bug 75649.\n"); + sizeToContent(); + } + + if (opener) { + var xOffset = (opener.outerWidth - window.outerWidth) / 2; + var yOffset = opener.outerHeight / 5; + + var newX = opener.screenX + xOffset; + var newY = opener.screenY + yOffset; + } else { + newX = (screen.availWidth - window.outerWidth) / 2; + newY = (screen.availHeight - window.outerHeight) / 2; + } + + // ensure the window is fully onscreen (if smaller than the screen) + if (newX < screen.availLeft) + newX = screen.availLeft + 20; + if ((newX + window.outerWidth) > (screen.availLeft + screen.availWidth)) + newX = (screen.availLeft + screen.availWidth) - window.outerWidth - 20; + + if (newY < screen.availTop) + newY = screen.availTop + 20; + if ((newY + window.outerHeight) > (screen.availTop + screen.availHeight)) + newY = (screen.availTop + screen.availHeight) - window.outerHeight - 60; + + window.moveTo( newX, newY ); +} + +function centerWindowOnScreen() +{ + var xOffset = screen.availWidth/2 - window.outerWidth/2; + var yOffset = screen.availHeight/2 - window.outerHeight/2; //(opener.outerHeight *2)/10; + + xOffset = ( xOffset > 0 ) ? xOffset : 0; + yOffset = ( yOffset > 0 ) ? yOffset : 0; + window.moveTo( xOffset, yOffset); +} diff --git a/components/global/content/dialogOverlay.xul b/components/global/content/dialogOverlay.xul new file mode 100644 index 000000000..9d4d8b613 --- /dev/null +++ b/components/global/content/dialogOverlay.xul @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<!-- -*- Mode: XML -*- + 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/. + + WARNING!!! This file is obsoleted by the dialog.xml widget +--> + +<!DOCTYPE overlay SYSTEM "chrome://global/locale/dialogOverlay.dtd"> + +<overlay id="dialogOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://global/content/dialogOverlay.js"/> + + <hbox id="okCancelButtons"> + <spacer flex="1"/> + <button class="right exit-dialog" id="ok" label="&okButton.label;" default="true" oncommand="doOKButton();"/> + <button class="exit-dialog" id="Button2" label="&cancelButton.label;" collapsed="true" oncommand="doButton2();"/> + <button class="exit-dialog" id="Button3" label="&cancelButton.label;" collapsed="true" oncommand="doButton3();"/> + <button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/> + <spacer flex="1"/> + </hbox> + + <hbox id="okCancelHelpButtons"> + <spacer flex="1"/> + <button class="right exit-dialog" id="ok" label="&okButton.label;" default="true" oncommand="doOKButton();"/> + <button class="exit-dialog" id="Button2" label="&cancelButton.label;" collapsed="true" oncommand="doButton2();"/> + <button class="exit-dialog" id="Button3" label="&cancelButton.label;" collapsed="true" oncommand="doButton3();"/> + <button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/> + <button class="exit-dialog" id="help" label="&helpButton.label;" oncommand="doHelpButton();"/> + <spacer flex="1"/> + </hbox> + + <hbox id="okCancelButtonsRight"> + <spacer flex="1"/> + <button class="right exit-dialog" id="ok" label="&okButton.label;" default="true" oncommand="doOKButton();"/> + <button class="exit-dialog" id="Button2" label="&cancelButton.label;" collapsed="true" oncommand="doButton2();"/> + <button class="exit-dialog" id="Button3" label="&cancelButton.label;" collapsed="true" oncommand="doButton3();"/> + <button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/> + </hbox> + + <hbox id="okCancelHelpButtonsRight"> + <spacer flex="1"/> + <button class="right exit-dialog" id="ok" label="&okButton.label;" default="true" oncommand="doOKButton();"/> + <button class="exit-dialog" id="Button2" label="&cancelButton.label;" collapsed="true" oncommand="doButton2();"/> + <button class="exit-dialog" id="Button3" label="&cancelButton.label;" collapsed="true" oncommand="doButton3();"/> + <button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/> + <button class="exit-dialog" id="help" label="&helpButton.label;" oncommand="doHelpButton();"/> + </hbox> + + <keyset id="dialogKeys"> + <key keycode="VK_RETURN" oncommand="if (!document.getElementById('ok').disabled) doOKButton();"/> + <key keycode="VK_ESCAPE" oncommand="doCancelButton();"/> + </keyset> + +</overlay> diff --git a/components/global/content/directionDetector.html b/components/global/content/directionDetector.html new file mode 100644 index 000000000..5e91ba7f5 --- /dev/null +++ b/components/global/content/directionDetector.html @@ -0,0 +1,13 @@ +<!-- 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> + <head> + <link rel="stylesheet" type="text/css" href="chrome://global/locale/intl.css"> + </head> + <body> + <window id="target" style="display: none;"></window> + </body> +</html> diff --git a/components/global/content/editMenuOverlay.js b/components/global/content/editMenuOverlay.js new file mode 100644 index 000000000..a610b641a --- /dev/null +++ b/components/global/content/editMenuOverlay.js @@ -0,0 +1,39 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +// update menu items that rely on focus or on the current selection +function goUpdateGlobalEditMenuItems() +{ + // Don't bother updating the edit commands if they aren't visible in any way + // (i.e. the Edit menu isn't open, nor is the context menu open, nor have the + // cut, copy, and paste buttons been added to the toolbars) for performance. + // This only works in applications/on platforms that set the gEditUIVisible + // flag, so we check to see if that flag is defined before using it. + if (typeof gEditUIVisible != "undefined" && !gEditUIVisible) + return; + + goUpdateCommand("cmd_undo"); + goUpdateCommand("cmd_redo"); + goUpdateCommand("cmd_cut"); + goUpdateCommand("cmd_copy"); + goUpdateCommand("cmd_paste"); + goUpdateCommand("cmd_selectAll"); + goUpdateCommand("cmd_delete"); + goUpdateCommand("cmd_switchTextDirection"); +} + +// 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"); +} diff --git a/components/global/content/editMenuOverlay.xul b/components/global/content/editMenuOverlay.xul new file mode 100644 index 000000000..909701990 --- /dev/null +++ b/components/global/content/editMenuOverlay.xul @@ -0,0 +1,108 @@ +<?xml version="1.0"?> <!-- -*- Mode: HTML -*- --> +<!-- 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/editMenuOverlay.dtd"> + +<overlay id="editMenuOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://global/content/editMenuOverlay.js"/> + + <commandset id="editMenuCommands"> + <commandset id="editMenuCommandSetAll" commandupdater="true" events="focus,select" + oncommandupdate="goUpdateGlobalEditMenuItems()"/> + <commandset id="editMenuCommandSetUndo" commandupdater="true" events="undo" + oncommandupdate="goUpdateUndoEditMenuItems()"/> + <commandset id="editMenuCommandSetPaste" commandupdater="true" events="clipboard" + oncommandupdate="goUpdatePasteMenuItems()"/> + <command id="cmd_undo" oncommand="goDoCommand('cmd_undo')"/> + <command id="cmd_redo" oncommand="goDoCommand('cmd_redo')"/> + <command id="cmd_cut" oncommand="goDoCommand('cmd_cut')"/> + <command id="cmd_copy" oncommand="goDoCommand('cmd_copy')"/> + <command id="cmd_paste" oncommand="goDoCommand('cmd_paste')"/> + <command id="cmd_delete" oncommand="goDoCommand('cmd_delete')"/> + <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/> + <command id="cmd_switchTextDirection" oncommand="goDoCommand('cmd_switchTextDirection');"/> + </commandset> + + <!-- These key nodes are here only for show. The real bindings come from + XBL, in platformHTMLBindings.xml. See bugs 57078 and 71779. --> + + <keyset id="editMenuKeys"> + <key id="key_undo" key="&undoCmd.key;" modifiers="accel" command="cmd_undo"/> +#ifdef XP_UNIX + <key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift" command="cmd_redo"/> +#else + <key id="key_redo" key="&redoCmd.key;" modifiers="accel" command="cmd_redo"/> +#endif + <key id="key_cut" key="&cutCmd.key;" modifiers="accel" command="cmd_cut"/> + <key id="key_copy" key="©Cmd.key;" modifiers="accel" command="cmd_copy"/> + <key id="key_paste" key="&pasteCmd.key;" modifiers="accel" command="cmd_paste"/> + <key id="key_delete" keycode="VK_DELETE" command="cmd_delete"/> + <key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel" command="cmd_selectAll"/> + <key id="key_find" key="&findCmd.key;" modifiers="accel" command="cmd_find"/> + <key id="key_findAgain" key="&findAgainCmd.key;" modifiers="accel" command="cmd_findAgain"/> + <key id="key_findPrevious" key="&findAgainCmd.key;" modifiers="shift,accel" command="cmd_findPrevious"/> + <key id="key_findAgain2" keycode="&findAgainCmd.key2;" command="cmd_findAgain"/> + <key id="key_findPrevious2" keycode="&findAgainCmd.key2;" modifiers="shift" command="cmd_findPrevious"/> + </keyset> + + <!-- Edit Menu --> + <menu id="menu_edit" label="&editMenu.label;" + accesskey="&editMenu.accesskey;"/> + + <menuitem id="menu_undo" label="&undoCmd.label;" + key="key_undo" accesskey="&undoCmd.accesskey;" + command="cmd_undo"/> + <menuitem id="menu_redo" label="&redoCmd.label;" + key="key_redo" accesskey="&redoCmd.accesskey;" + command="cmd_redo"/> + <menuitem id="menu_cut" label="&cutCmd.label;" + key="key_cut" accesskey="&cutCmd.accesskey;" + command="cmd_cut"/> + <menuitem id="menu_copy" label="©Cmd.label;" + key="key_copy" accesskey="©Cmd.accesskey;" + command="cmd_copy"/> + <menuitem id="menu_paste" label="&pasteCmd.label;" + key="key_paste" accesskey="&pasteCmd.accesskey;" + command="cmd_paste"/> + <menuitem id="menu_delete" label="&deleteCmd.label;" + 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" label="&findCmd.label;" + key="key_find" accesskey="&findCmd.accesskey;" + command="cmd_find"/> + <menuitem id="menu_findAgain" label="&findAgainCmd.label;" + key="key_findAgain" accesskey="&findAgainCmd.accesskey;" + command="cmd_findAgain"/> + <menuitem id="menu_findPrevious" label="&findPreviousCmd.label;" + key="key_findPrevious" accesskey="&findPreviousCmd.accesskey;" + command="cmd_findPrevious"/> + + <menuitem id="cMenu_undo" label="&undoCmd.label;" + accesskey="&undoCmd.accesskey;" command="cmd_undo"/> + <menuitem id="cMenu_redo" label="&redoCmd.label;" + accesskey="&redoCmd.accesskey;" command="cmd_redo"/> + <menuitem id="cMenu_cut" label="&cutCmd.label;" + accesskey="&cutCmd.accesskey;" command="cmd_cut"/> + <menuitem id="cMenu_copy" label="©Cmd.label;" + accesskey="©Cmd.accesskey;" command="cmd_copy"/> + <menuitem id="cMenu_paste" label="&pasteCmd.label;" + accesskey="&pasteCmd.accesskey;" command="cmd_paste"/> + <menuitem id="cMenu_delete" label="&deleteCmd.label;" + accesskey="&deleteCmd.accesskey;" command="cmd_delete"/> + <menuitem id="cMenu_selectAll" label="&selectAllCmd.label;" + accesskey="&selectAllCmd.accesskey;" command="cmd_selectAll"/> + <menuitem id="cMenu_find" label="&findCmd.label;" + accesskey="&findCmd.accesskey;" command="cmd_find"/> + <menuitem id="cMenu_findAgain" label="&findAgainCmd.label;" + accesskey="&findAgainCmd.accesskey;" command="cmd_findAgain"/> + <menuitem id="cMenu_findPrevious" label="&findPreviousCmd.label;" + accesskey="&findPreviousCmd.accesskey;" command="cmd_findPrevious"/> +</overlay> diff --git a/components/global/content/filepicker.properties b/components/global/content/filepicker.properties new file mode 100644 index 000000000..5be84e3a0 --- /dev/null +++ b/components/global/content/filepicker.properties @@ -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/. + +allFilter=* +htmlFilter=*.html; *.htm; *.shtml; *.xhtml +textFilter=*.txt; *.text +imageFilter=*.jpe; *.jpg; *.jpeg; *.gif; *.png; *.bmp; *.ico; *.svg; *.svgz; *.tif; *.tiff; *.ai; *.drw; *.pct; *.psp; *.xcf; *.psd; *.raw +xmlFilter=*.xml +xulFilter=*.xul +audioFilter=*.aac; *.aif; *.flac; *.iff; *.m4a; *.m4b; *.mid; *.midi; *.mp3; *.mpa; *.mpc; *.oga; *.ogg; *.ra; *.ram; *.snd; *.wav; *.wma +videoFilter=*.avi; *.divx; *.flv; *.m4v; *.mkv; *.mov; *.mp4; *.mpeg; *.mpg; *.ogm; *.ogv; *.ogx; *.rm; *.rmvb; *.smil; *.webm; *.wmv; *.xvid diff --git a/components/global/content/findUtils.js b/components/global/content/findUtils.js new file mode 100644 index 000000000..397a98f6c --- /dev/null +++ b/components/global/content/findUtils.js @@ -0,0 +1,111 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); + +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(Components.interfaces.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) +{ + // is the dialog up already? + if ("findDialog" in window && window.findDialog) + 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) +{ + if ("findDialog" in window && window.findDialog) + window.findDialog.focus(); + 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 = Components.classes["@mozilla.org/find/find_service;1"] + .getService(Components.interfaces.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 findService = Components.classes["@mozilla.org/find/find_service;1"] + .getService(Components.interfaces.nsIFindService); + return (findService.searchString.length > 0); +} + diff --git a/components/global/content/finddialog.js b/components/global/content/finddialog.js new file mode 100644 index 000000000..fc5875321 --- /dev/null +++ b/components/global/content/finddialog.js @@ -0,0 +1,151 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/FormHistory.jsm"); + +var dialog; // Quick access to document/form elements. +var gFindInst; // nsIWebBrowserFind that we're going to use +var gFindInstData; // use this to update the find inst data + +function initDialogObject() +{ + // Create dialog object and initialize. + dialog = {}; + dialog.findKey = document.getElementById("dialog.findKey"); + dialog.caseSensitive = document.getElementById("dialog.caseSensitive"); + dialog.wrap = document.getElementById("dialog.wrap"); + dialog.find = document.getElementById("btnFind"); + dialog.up = document.getElementById("radioUp"); + dialog.down = document.getElementById("radioDown"); + dialog.rg = dialog.up.radioGroup; + dialog.bundle = null; + + // Move dialog to center, if it not been shown before + var windowElement = document.getElementById("findDialog"); + if (!windowElement.hasAttribute("screenX") || !windowElement.hasAttribute("screenY")) + { + sizeToContent(); + moveToAlertPosition(); + } +} + +function fillDialog() +{ + // get the find service, which stores global find state + var findService = Components.classes["@mozilla.org/find/find_service;1"] + .getService(Components.interfaces.nsIFindService); + + // Set initial dialog field contents. Use the gFindInst attributes first, + // this is necessary for window.find() + dialog.findKey.value = gFindInst.searchString ? gFindInst.searchString : findService.searchString; + dialog.caseSensitive.checked = gFindInst.matchCase ? gFindInst.matchCase : findService.matchCase; + dialog.wrap.checked = gFindInst.wrapFind ? gFindInst.wrapFind : findService.wrapFind; + var findBackwards = gFindInst.findBackwards ? gFindInst.findBackwards : findService.findBackwards; + if (findBackwards) + dialog.rg.selectedItem = dialog.up; + else + dialog.rg.selectedItem = dialog.down; +} + +function saveFindData() +{ + // get the find service, which stores global find state + var findService = Components.classes["@mozilla.org/find/find_service;1"] + .getService(Components.interfaces.nsIFindService); + + // Set data attributes per user input. + findService.searchString = dialog.findKey.value; + findService.matchCase = dialog.caseSensitive.checked; + findService.wrapFind = dialog.wrap.checked; + findService.findBackwards = dialog.up.selected; +} + +function onLoad() +{ + initDialogObject(); + + // get the find instance + var arg0 = window.arguments[0]; + // If the dialog was opened from window.find(), + // arg0 will be an instance of nsIWebBrowserFind + if (arg0 instanceof Components.interfaces.nsIWebBrowserFind) { + gFindInst = arg0; + } else { + gFindInstData = arg0; + gFindInst = gFindInstData.webBrowserFind; + } + + fillDialog(); + doEnabling(); + + if (dialog.findKey.value) + dialog.findKey.select(); + dialog.findKey.focus(); +} + +function onUnload() +{ + window.opener.findDialog = 0; +} + +function onAccept() +{ + if (gFindInstData && gFindInst != gFindInstData.webBrowserFind) { + gFindInstData.init(); + gFindInst = gFindInstData.webBrowserFind; + } + + // Transfer dialog contents to the find service. + saveFindData(); + updateFormHistory(); + + // set up the find instance + gFindInst.searchString = dialog.findKey.value; + gFindInst.matchCase = dialog.caseSensitive.checked; + gFindInst.wrapFind = dialog.wrap.checked; + gFindInst.findBackwards = dialog.up.selected; + + // Search. + var result = gFindInst.findNext(); + + if (!result) + { + if (!dialog.bundle) + dialog.bundle = document.getElementById("findBundle"); + Services.prompt.alert(window, dialog.bundle.getString("notFoundTitle"), + dialog.bundle.getString("notFoundWarning")); + dialog.findKey.select(); + dialog.findKey.focus(); + } + return false; +} + +function doEnabling() +{ + dialog.find.disabled = !dialog.findKey.value; +} + +function updateFormHistory() +{ + if (window.opener.PrivateBrowsingUtils && + window.opener.PrivateBrowsingUtils.isWindowPrivate(window.opener) || + !dialog.findKey.value) + return; + + if (FormHistory.enabled) { + FormHistory.update({ + op: "bump", + fieldname: "find-dialog", + value: dialog.findKey.value + }, { + handleError: function(aError) { + Components.utils.reportError("Saving find to form history failed: " + + aError.message); + } + }); + } +} diff --git a/components/global/content/finddialog.xul b/components/global/content/finddialog.xul new file mode 100644 index 000000000..c49092e6f --- /dev/null +++ b/components/global/content/finddialog.xul @@ -0,0 +1,58 @@ +<?xml version="1.0"?> <!-- -*- Mode: HTML -*- --> +# 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"?> + +<!DOCTYPE window SYSTEM "chrome://global/locale/finddialog.dtd"> + +<dialog id="findDialog" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + orient="horizontal" + windowtype="findInPage" + onload="onLoad();" + onunload="onUnload();" + ondialogaccept="return onAccept();" + buttons="accept,cancel" + title="&findDialog.title;" + persist="screenX screenY"> + + <script type="application/javascript" src="chrome://global/content/finddialog.js"/> + <stringbundle id="findBundle" src="chrome://global/locale/finddialog.properties"/> + + <hbox> + <vbox> + <hbox align="center"> + <label value="&findField.label;" accesskey="&findField.accesskey;" control="dialog.findKey"/> + <textbox id="dialog.findKey" flex="1" + type="autocomplete" + autocompletesearch="form-history" + autocompletesearchparam="find-dialog" + oninput="doEnabling();"/> + </hbox> + <hbox align="center"> + <vbox> + <checkbox id="dialog.caseSensitive" label="&caseSensitiveCheckbox.label;" accesskey="&caseSensitiveCheckbox.accesskey;"/> + <checkbox id="dialog.wrap" label="&wrapCheckbox.label;" accesskey="&wrapCheckbox.accesskey;" checked="true"/> + </vbox> + <groupbox orient="horizontal"> + <caption label="&direction.label;"/> + <radiogroup orient="horizontal"> + <radio id="radioUp" label="&up.label;" accesskey="&up.accesskey;"/> + <radio id="radioDown" label="&down.label;" accesskey="&down.accesskey;" selected="true"/> + </radiogroup> + </groupbox> + </hbox> + </vbox> + <vbox> + <button id="btnFind" label="&findButton.label;" accesskey="&findButton.accesskey;" + dlgtype="accept" icon="find"/> +#ifdef XP_UNIX + <button label="&closeButton.label;" icon="close" dlgtype="cancel"/> +#else + <button label="&cancelButton.label;" icon="cancel" dlgtype="cancel"/> +#endif + </vbox> + </hbox> +</dialog> diff --git a/components/global/content/globalOverlay.js b/components/global/content/globalOverlay.js new file mode 100644 index 000000000..d5ee13191 --- /dev/null +++ b/components/global/content/globalOverlay.js @@ -0,0 +1,161 @@ +/* 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/. */ + +function closeWindow(aClose, aPromptFunction) +{ + var windowCount = 0; + var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] + .getService(Components.interfaces.nsIWindowMediator); + var e = wm.getEnumerator(null); + + while (e.hasMoreElements()) { + var w = e.getNext(); + if (w.closed) { + continue; + } + if (++windowCount == 2) + break; + } + + // If we're down to the last window and someone tries to shut down, check to make sure we can! + if (windowCount == 1 && !canQuitApplication("lastwindow")) + return false; + if (windowCount != 1 && typeof(aPromptFunction) == "function" && !aPromptFunction()) + return false; + + if (aClose) { + window.close(); + return window.closed; + } + + return true; +} + +function canQuitApplication(aData) +{ + var os = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + if (!os) return true; + + try { + var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"] + .createInstance(Components.interfaces.nsISupportsPRBool); + os.notifyObservers(cancelQuit, "quit-application-requested", aData || null); + + // Something aborted the quit process. + if (cancelQuit.data) + return false; + } + catch (ex) { } + return true; +} + +function goQuitApplication() +{ + if (!canQuitApplication()) + return false; + + var appStartup = Components.classes['@mozilla.org/toolkit/app-startup;1']. + getService(Components.interfaces.nsIAppStartup); + + appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit); + return true; +} + +// +// Command Updater functions +// +function goUpdateCommand(aCommand) +{ + try { + var controller = top.document.commandDispatcher + .getControllerForCommand(aCommand); + + var enabled = false; + if (controller) + enabled = controller.isCommandEnabled(aCommand); + + goSetCommandEnabled(aCommand, enabled); + } + catch (e) { + Components.utils.reportError("An error occurred updating the " + + aCommand + " command: " + e); + } +} + +function goDoCommand(aCommand) +{ + try { + var controller = top.document.commandDispatcher + .getControllerForCommand(aCommand); + if (controller && controller.isCommandEnabled(aCommand)) + controller.doCommand(aCommand); + } + catch (e) { + Components.utils.reportError("An error occurred executing the " + + aCommand + " command: " + e); + } +} + + +function goSetCommandEnabled(aID, aEnabled) +{ + var node = document.getElementById(aID); + + if (node) { + if (aEnabled) + node.removeAttribute("disabled"); + else + node.setAttribute("disabled", "true"); + } +} + +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); + } +} + +function setTooltipText(aID, aTooltipText) +{ + var element = document.getElementById(aID); + if (element) + element.setAttribute("tooltiptext", aTooltipText); +} + +this.__defineGetter__("NS_ASSERT", function() { + delete this.NS_ASSERT; + var tmpScope = {}; + Components.utils.import("resource://gre/modules/debug.js", tmpScope); + return this.NS_ASSERT = tmpScope.NS_ASSERT; +}); diff --git a/components/global/content/globalOverlay.xul b/components/global/content/globalOverlay.xul new file mode 100644 index 000000000..90268a8e4 --- /dev/null +++ b/components/global/content/globalOverlay.xul @@ -0,0 +1,38 @@ +<?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="globalOverlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/> + <script type="application/javascript"><![CDATA[ + + function FillInTooltip ( tipElement ) + { + var retVal = false; + var textNode = document.getElementById("TOOLTIP-tooltipText"); + if (textNode) { + while (textNode.hasChildNodes()) + textNode.removeChild(textNode.firstChild); + var tipText = tipElement.getAttribute("tooltiptext"); + if (tipText) { + var node = document.createTextNode(tipText); + textNode.appendChild(node); + retVal = true; + } + } + return retVal; + } + + ]]></script> + + <popupset id="aTooltipSet"> + <tooltip id="aTooltip" class="tooltip" onpopupshowing="return FillInTooltip(document.tooltipNode);"> + <label id="TOOLTIP-tooltipText" class="tooltip-label" flex="1"/> + </tooltip> + </popupset> + +</overlay> diff --git a/components/global/content/gmp-sources/openh264.json b/components/global/content/gmp-sources/openh264.json new file mode 100644 index 000000000..7c6eb0197 --- /dev/null +++ b/components/global/content/gmp-sources/openh264.json @@ -0,0 +1,57 @@ +{ + "vendors": { + "gmp-gmpopenh264": { + "platforms": { + "WINNT_x86-msvc-x64": { + "alias": "WINNT_x86-msvc" + }, + "WINNT_x86-msvc": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-win32-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "991e01c3b95fa13fac52e0512e1936f1edae42ecbbbcc55447a36915eb3ca8f836546cc780343751691e0188872e5bc56fe3ad5f23f3243e90b96a637561b89e", + "filesize": 356940 + }, + "WINNT_x86-msvc-x86": { + "alias": "WINNT_x86-msvc" + }, + "Linux_x86_64-gcc3": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-linux64-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "e1086ee6e4fb60a1aa11b5626594b97695533a8e269d776877cebd5cf29088619e2c164e7bd1eba5486f772c943f2efec723f69cc48478ec84a11d7b61ca1865", + "filesize": 515722 + }, + "Darwin_x86-gcc3-u-i386-x86_64": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-macosx32-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "64b0e13e6319b7a31ed35a46bea5abcfe6af04ba59a277db07677236cfb685813763731ff6b44b85e03e1489f3b15f8df0128a299a36720531b9f4ba6e1c1f58", + "filesize": 382435 + }, + "Darwin_x86_64-gcc3": { + "alias": "Darwin_x86_64-gcc3-u-i386-x86_64" + }, + "Linux_x86-gcc3": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-linux32-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "19084f0230218c584715861f4723e072b1af02e26995762f368105f670f60ecb4082531bc4e33065a4675dd1296f6872a6cb101547ef2d19ef3e25e2e16d4dc0", + "filesize": 515857 + }, + "Darwin_x86_64-gcc3-u-i386-x86_64": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-macosx64-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "3b52343070a2f75e91b7b0d3bb33935352237c7e1d2fdc6a467d039ffbbda6a72087f9e0a369fe95e6c4c789ff3052f0c134af721d7273db9ba66d077d85b327", + "filesize": 390308 + }, + "Darwin_x86-gcc3": { + "alias": "Darwin_x86-gcc3-u-i386-x86_64" + }, + "WINNT_x86_64-msvc-x64": { + "alias": "WINNT_x86_64-msvc" + }, + "WINNT_x86_64-msvc": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-win64-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "5030b47065e817db5c40bca9c62ac27292bbf636e24698f45dc67f03fa6420b97bd2f792c1cb39df65776c1e7597c70122ac7abf36fb2ad0603734e9e8ec4ef3", + "filesize": 404355 + } + }, + "version": "1.6" + } + }, + "hashFunction": "sha512", + "name": "OpenH264-1.6", + "schema_version": 1000 +} diff --git a/components/global/content/gmp-sources/widevinecdm.json b/components/global/content/gmp-sources/widevinecdm.json new file mode 100644 index 000000000..02ef7fee5 --- /dev/null +++ b/components/global/content/gmp-sources/widevinecdm.json @@ -0,0 +1,49 @@ +{ + "vendors": { + "gmp-widevinecdm": { + "platforms": { + "WINNT_x86-msvc-x64": { + "alias": "WINNT_x86-msvc" + }, + "WINNT_x86-msvc": { + "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-win-ia32.zip", + "hashValue": "d7e10d09c87a157af865f8388ba70ae672bd9e38987bdd94077af52d6b1abaa745b3db92e9f93f607af6420c68210f7cfd518a9d2c99fecf79aed3385cbcbc0b", + "filesize": 2884452 + }, + "WINNT_x86-msvc-x86": { + "alias": "WINNT_x86-msvc" + }, + "Linux_x86_64-gcc3": { + "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-linux-x64.zip", + "hashValue": "1edfb58a44792d2a53694f46fcc698161edafb2a1fe0e5c31b50c1d52408b5e8918d9f33271c62a19a65017694ebeacb4f390fe914688ca7b1952cdb84ed55ec", + "filesize": 2975492 + }, + "Darwin_x86_64-gcc3-u-i386-x86_64": { + "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-mac-x64.zip", + "hashValue": "1916805e84a49e04748204f4e4c48ae52c8312f7c04afedacacd7dfab2de424412bc988a8c3e5bcb0865f8844b569c0eb9589dae51e74d9bdfe46792c9d1631f", + "filesize": 2155607 + }, + "Darwin_x86_64-gcc3": { + "alias": "Darwin_x86_64-gcc3-u-i386-x86_64" + }, + "Linux_x86-gcc3": { + "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-linux-ia32.zip", + "hashValue": "5c4beb72ea693740a013b60bc5491a042bb82fa5ca6845a2f450579e2e1e465263f19e7ab6d08d91deb8219b30f092ab6e6745300d5adda627f270c95e5a66e0", + "filesize": 3084582 + }, + "WINNT_x86_64-msvc-x64": { + "alias": "WINNT_x86_64-msvc" + }, + "WINNT_x86_64-msvc": { + "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-win-x64.zip", + "hashValue": "33497f3458846e11fa52413f6477bfe1a7f502da262c3a2ce9fe6d773a4a2d023c54228596eb162444b55c87fb126de01f60fa729d897ef5e6eec73b2dfbdc7a", + "filesize": 2853777 + } + }, + "version": "1.4.8.903" + } + }, + "hashFunction": "sha512", + "name": "Widevine-1.4.8.903", + "schema_version": 1000 +} diff --git a/components/global/content/inlineSpellCheckUI.js b/components/global/content/inlineSpellCheckUI.js new file mode 100644 index 000000000..177ce90cd --- /dev/null +++ b/components/global/content/inlineSpellCheckUI.js @@ -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/. */ + +Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm"); + +var InlineSpellCheckerUI = new InlineSpellChecker(); diff --git a/components/global/content/logopage.xhtml b/components/global/content/logopage.xhtml new file mode 100644 index 000000000..80de8ed58 --- /dev/null +++ b/components/global/content/logopage.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" [ +]> + +<!-- 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></title> + <style type="text/css"> + body { + background: #eee; + color: black; + } + + img { + text-align: center; + position: absolute; + margin: auto; + top: 0; + right: 0; + bottom: 0; + left: 0; + opacity: 0.2; + } + + @media(-moz-dark-theme) { + body { + background: #2E3B41; + color: #eee; + } + } + </style> +</head> + +<body> + <img src="about:logo" alt=""/> +</body> +</html> diff --git a/components/global/content/menulist.css b/components/global/content/menulist.css new file mode 100644 index 000000000..ae6166d1b --- /dev/null +++ b/components/global/content/menulist.css @@ -0,0 +1,11 @@ +/* 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 html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */ + +html|*.menulist-editable-input { + -moz-appearance: none !important; + background: transparent ! important; + -moz-box-flex: 1; +} diff --git a/components/global/content/minimal-xul.css b/components/global/content/minimal-xul.css new file mode 100644 index 000000000..0cd41922d --- /dev/null +++ b/components/global/content/minimal-xul.css @@ -0,0 +1,133 @@ +/* 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 file should only contain a minimal set of rules for the XUL elements + * that may be implicitly created as part of HTML/SVG documents (e.g. + * scrollbars). Rules for everything else related to XUL can be found in + * xul.css. (This split of the XUL rules is to minimize memory use and improve + * performance in HTML/SVG documents.) + * + * This file should also not contain any app specific styling. Defaults for + * widgets of a particular application should be in that application's style + * sheet. For example style definitions for navigator can be found in + * navigator.css. + * + * THIS FILE IS LOCKED DOWN. YOU ARE NOT ALLOWED TO MODIFY IT WITHOUT FIRST + * HAVING YOUR CHANGES REVIEWED BY enndeakin@gmail.com + */ + +@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */ +@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */ + +* { + -moz-user-focus: ignore; + -moz-user-select: none; + display: -moz-box; + box-sizing: border-box; +} + +:root { + text-rendering: optimizeLegibility; + -moz-binding: url("chrome://global/content/bindings/general.xml#root-element"); + -moz-control-character-visibility: visible; +} + +:root:-moz-locale-dir(rtl) { + direction: rtl; +} + +/* hide the content and destroy the frame */ +[hidden="true"] { + display: none; +} + +/* hide the content, but don't destroy the frames */ +[collapsed="true"], +[moz-collapsed="true"] { + visibility: collapse; +} + +/********** label **********/ + +description { + -moz-binding: url("chrome://global/content/bindings/text.xml#text-base"); +} + +label { + -moz-binding: url("chrome://global/content/bindings/text.xml#text-label"); +} + +label.text-link, label[onclick] { + -moz-binding: url("chrome://global/content/bindings/text.xml#text-link"); + -moz-user-focus: normal; +} + +label[control], label.radio-label, label.checkbox-label, label.toolbarbutton-multiline-text { + -moz-binding: url("chrome://global/content/bindings/text.xml#label-control"); +} + +html|span.accesskey { + text-decoration: underline; +} + +/********** resizer **********/ + +resizer { + -moz-binding: url("chrome://global/content/bindings/resizer.xml#resizer"); + position: relative; + z-index: 2147483647; +} + +/********** scrollbar **********/ + +/* Scrollbars are never flipped even if BiDI kicks in. */ +scrollbar[orient="horizontal"] { + direction: ltr; +} + +thumb { + -moz-binding: url(chrome://global/content/bindings/scrollbar.xml#thumb); + display: -moz-box !important; +} + +.scale-thumb { + -moz-binding: url(chrome://global/content/bindings/scale.xml#scalethumb); +} + +scrollbar, scrollbarbutton, scrollcorner, slider, thumb, scale { + -moz-user-select: none; +} + +scrollcorner { + display: -moz-box !important; +} + +scrollcorner[hidden="true"] { + display: none !important; +} + +scrollbar[value="hidden"] { + visibility: hidden; +} + +scale { + -moz-binding: url(chrome://global/content/bindings/scale.xml#scale); +} + +.scale-slider { + -moz-binding: url(chrome://global/content/bindings/scale.xml#scaleslider); + -moz-user-focus: normal; +} + +scrollbarbutton[sbattr="scrollbar-up-top"]:not(:-moz-system-metric(scrollbar-start-backward)), +scrollbarbutton[sbattr="scrollbar-down-top"]:not(:-moz-system-metric(scrollbar-start-forward)), +scrollbarbutton[sbattr="scrollbar-up-bottom"]:not(:-moz-system-metric(scrollbar-end-backward)), +scrollbarbutton[sbattr="scrollbar-down-bottom"]:not(:-moz-system-metric(scrollbar-end-forward)) { + display: none; +} + +thumb[sbattr="scrollbar-thumb"]:-moz-system-metric(scrollbar-thumb-proportional) { + -moz-box-flex: 1; +} diff --git a/components/global/content/mozilla.xhtml b/components/global/content/mozilla.xhtml new file mode 100644 index 000000000..f1a1b474d --- /dev/null +++ b/components/global/content/mozilla.xhtml @@ -0,0 +1,76 @@ +<!DOCTYPE html +[ + <!ENTITY % directionDTD SYSTEM "chrome://global/locale/global.dtd" > + %directionDTD; + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> + %brandDTD; +]> + +<!-- 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> + <meta charset='utf-8' /> + <title>Mozilla: In Memoriam</title> + +<style> +html { + background: maroon radial-gradient( circle, #a01010 0%, #800000 80%) center center / cover no-repeat; + color: white; + font-style: italic; + text-rendering: optimizeLegibility; + min-height: 100%; +} + +#moztext { + margin-top: 15%; + font-size: 1.1em; + font-family: serif; + text-align: center; + line-height: 1.5; +} + +#from { + font-size: 1.0em; + font-family: serif; + text-align: right; +} + +em { + font-size: 1.3em; + line-height: 0; +} + +a { + text-decoration: none; + color: white; +} +</style> +</head> + +<body dir="&locale.dir;"> + +<section> + <p id="moztext"> + <h1>Mozilla: In Memoriam</h1> + <br/> + Dedicated to the tireless developers who have come and gone.<br/> + To those who have put their heart and soul into Mozilla products.<br/> + To those who have seen their good intentions and hard work squandered.<br/> + To those who really cared about the user, and cared about usability.<br/> + To those who truly understood us and desired freedom, but were unheard.<br/> + To those who knew that change is inevitable, but loss of vision is not.<br/> + To those who were forced to give up the good fight.<br/> + <br/> + <em>Thank you.</em> &brandFullName; would not have been possible without you.<br/> + <br/> + </p> + + <p id="from"> + </p> +</section> + +</body> +</html>
\ No newline at end of file diff --git a/components/global/content/nsClipboard.js b/components/global/content/nsClipboard.js new file mode 100644 index 000000000..d9f7c4589 --- /dev/null +++ b/components/global/content/nsClipboard.js @@ -0,0 +1,64 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +/** + * nsClipboard - wrapper around nsIClipboard and nsITransferable + * that simplifies access to the clipboard. + **/ +var nsClipboard = { + _CB: null, + get mClipboard() + { + if (!this._CB) + { + const kCBContractID = "@mozilla.org/widget/clipboard;1"; + const kCBIID = Components.interfaces.nsIClipboard; + this._CB = Components.classes[kCBContractID].getService(kCBIID); + } + return this._CB; + }, + + currentClipboard: null, + /** + * Array/Object read (Object aFlavourList, long aClipboard, Bool aAnyFlag) ; + * + * returns the data in the clipboard + * + * @param FlavourSet aFlavourSet + * formatted list of desired flavours + * @param long aClipboard + * the clipboard to read data from (kSelectionClipboard/kGlobalClipboard) + * @param Bool aAnyFlag + * should be false. + **/ + read: function (aFlavourList, aClipboard, aAnyFlag) + { + this.currentClipboard = aClipboard; + var data = nsTransferable.get(aFlavourList, this.getClipboardTransferable, aAnyFlag); + return data.first.first; // only support one item + }, + + /** + * nsISupportsArray getClipboardTransferable (Object aFlavourList) ; + * + * returns a nsISupportsArray of the item on the clipboard + * + * @param Object aFlavourList + * formatted list of desired flavours. + **/ + getClipboardTransferable: function (aFlavourList) + { + const supportsContractID = "@mozilla.org/supports-array;1"; + const supportsIID = Components.interfaces.nsISupportsArray; + var supportsArray = Components.classes[supportsContractID].createInstance(supportsIID); + var trans = nsTransferable.createTransferable(); + for (var flavour in aFlavourList) + trans.addDataFlavor(flavour); + nsClipboard.mClipboard.getData(trans, nsClipboard.currentClipboard) + supportsArray.AppendElement(trans); + return supportsArray; + } +}; + diff --git a/components/global/content/nsUserSettings.js b/components/global/content/nsUserSettings.js new file mode 100644 index 000000000..e0c378caf --- /dev/null +++ b/components/global/content/nsUserSettings.js @@ -0,0 +1,108 @@ +/* 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/. */ + + +/** + * nsPreferences - a wrapper around nsIPrefService. Provides built in + * exception handling to make preferences access simpler. + **/ +var nsPreferences = { + get mPrefService() + { + return Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + }, + + setBoolPref: function (aPrefName, aPrefValue) + { + try + { + this.mPrefService.setBoolPref(aPrefName, aPrefValue); + } + catch (e) + { + } + }, + + getBoolPref: function (aPrefName, aDefVal) + { + try + { + return this.mPrefService.getBoolPref(aPrefName); + } + catch (e) + { + return aDefVal != undefined ? aDefVal : null; + } + return null; // quiet warnings + }, + + setUnicharPref: function (aPrefName, aPrefValue) + { + try + { + var str = Components.classes["@mozilla.org/supports-string;1"] + .createInstance(Components.interfaces.nsISupportsString); + str.data = aPrefValue; + this.mPrefService.setComplexValue(aPrefName, + Components.interfaces.nsISupportsString, str); + } + catch (e) + { + } + }, + + copyUnicharPref: function (aPrefName, aDefVal) + { + try + { + return this.mPrefService.getComplexValue(aPrefName, + Components.interfaces.nsISupportsString).data; + } + catch (e) + { + return aDefVal != undefined ? aDefVal : null; + } + return null; // quiet warnings + }, + + setIntPref: function (aPrefName, aPrefValue) + { + try + { + this.mPrefService.setIntPref(aPrefName, aPrefValue); + } + catch (e) + { + } + }, + + getIntPref: function (aPrefName, aDefVal) + { + try + { + return this.mPrefService.getIntPref(aPrefName); + } + catch (e) + { + return aDefVal != undefined ? aDefVal : null; + } + return null; // quiet warnings + }, + + getLocalizedUnicharPref: function (aPrefName, aDefVal) + { + try + { + return this.mPrefService.getComplexValue(aPrefName, + Components.interfaces.nsIPrefLocalizedString).data; + } + catch (e) + { + return aDefVal != undefined ? aDefVal : null; + } + return null; // quiet warnings + } +}; + diff --git a/components/global/content/plugins.css b/components/global/content/plugins.css new file mode 100644 index 000000000..a599b5be4 --- /dev/null +++ b/components/global/content/plugins.css @@ -0,0 +1,88 @@ +/* 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/. */ + +/* ===== plugins.css ===================================================== + == Styles used by the about:plugins page. + ======================================================================= */ + +body { + background-color: -moz-Field; + color: -moz-FieldText; + font: message-box; +} + +div#outside { + text-align: justify; + width: 90%; + margin-left: 5%; + margin-right: 5%; +} + +#plugs { + text-align: center; + font-size: xx-large; + font-weight: bold; +} + +#noplugs { + font-size: x-large; + font-weight: bold; +} + +.plugname { + margin-top: 2em; + margin-bottom: 1em; + font-size: large; + text-align: start; + font-weight: bold; +} + +dl { + margin: 0px 0px 3px 0px; +} + +table { + background-color: -moz-Dialog; + color: -moz-DialogText; + font: message-box; + text-align: start; + width: 100%; + border: 1px solid ThreeDShadow; + border-spacing: 0px; +} + +th, td { + border: none; + padding: 3px; +} + +th { + text-align: center; + background-color: Highlight; + color: HighlightText; +} + +th + th, +td + td { + border-inline-start: 1px dotted ThreeDShadow; +} + +td { + text-align: start; + border-top: 1px dotted ThreeDShadow; +} + +th.type, th.suff { + width: 25%; +} + +th.desc { + width: 50%; +} + +.notice { + background: -moz-cellhighlight; + border: 1px solid ThreeDShadow; + padding: 10px; +} diff --git a/components/global/content/plugins.html b/components/global/content/plugins.html new file mode 100644 index 000000000..15bbed9bb --- /dev/null +++ b/components/global/content/plugins.html @@ -0,0 +1,207 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> + +<!-- 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> +<head> +<script type="application/javascript"> + "use strict"; + + Components.utils.import("resource://gre/modules/Services.jsm"); + Components.utils.import("resource://gre/modules/AddonManager.jsm"); + + var Ci = Components.interfaces; + var strBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService); + var pluginsbundle = strBundleService.createBundle("chrome://global/locale/plugins.properties"); + + document.writeln("<title>" + pluginsbundle.GetStringFromName("title_label") + "<\/title>"); +</script> +<link rel="stylesheet" type="text/css" href="chrome://global/content/plugins.css"> +<link rel="stylesheet" type="text/css" href="chrome://global/skin/plugins.css"> +</head> +<body> +<div id="outside"> +<script type="application/javascript"> + "use strict"; + + function setDirection() { + var frame = document.getElementById("directionDetector"); + var direction = frame.contentDocument + .defaultView + .window + .getComputedStyle(frame.contentDocument.getElementById("target"), "") + .getPropertyValue("direction"); + document.body.removeChild(frame); + document.dir = direction; + } + + function setupDirection() { + var frame = document.createElement("iframe"); + frame.setAttribute("id", "directionDetector"); + frame.setAttribute("src", "chrome://global/content/directionDetector.html"); + frame.setAttribute("width", "0"); + frame.setAttribute("height", "0"); + frame.setAttribute("style", "visibility: hidden;"); + frame.setAttribute("onload", "setDirection();"); + document.body.appendChild(frame); + } + setupDirection(); + + /* JavaScript to enumerate and display all installed plug-ins + + * First, refresh plugins in case anything has been changed recently in + * prefs: (The "false" argument tells refresh not to reload or activate + * any plug-ins that would be active otherwise. In contrast, one would + * use "true" in the case of ASD instead of restarting) + */ + navigator.plugins.refresh(false); + + AddonManager.getAddonsByTypes(["plugin"], function (aPlugins) { + var fragment = document.createDocumentFragment(); + + // "Installed plugins" + var id, label; + if (aPlugins.length > 0) { + id = "plugs"; + label = "installedplugins_label"; + } else { + id = "noplugs"; + label = "nopluginsareinstalled_label"; + } + var enabledplugins = document.createElement("h1"); + enabledplugins.setAttribute("id", id); + enabledplugins.appendChild(document.createTextNode(pluginsbundle.GetStringFromName(label))); + fragment.appendChild(enabledplugins); + + var stateNames = {}; + ["STATE_SOFTBLOCKED", + "STATE_BLOCKED", + "STATE_OUTDATED", + "STATE_VULNERABLE_UPDATE_AVAILABLE", + "STATE_VULNERABLE_NO_UPDATE"].forEach(function(label) { + stateNames[Ci.nsIBlocklistService[label]] = label; + }); + + for (var i = 0; i < aPlugins.length; i++) { + var plugin = aPlugins[i]; + if (plugin) { + // "Shockwave Flash" + var plugname = document.createElement("h2"); + plugname.setAttribute("class", "plugname"); + plugname.appendChild(document.createTextNode(plugin.name)); + fragment.appendChild(plugname); + + var dl = document.createElement("dl"); + fragment.appendChild(dl); + + // "File: Flash Player.plugin" + var fileDd = document.createElement("dd"); + var file = document.createElement("span"); + file.setAttribute("class", "label"); + file.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("file_label") + " ")); + fileDd.appendChild(file); + fileDd.appendChild(document.createTextNode(plugin.pluginLibraries)); + dl.appendChild(fileDd); + + // "Path: /usr/lib/mozilla/plugins/libtotem-cone-plugin.so" + var pathDd = document.createElement("dd"); + var path = document.createElement("span"); + path.setAttribute("class", "label"); + path.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("path_label") + " ")); + pathDd.appendChild(path); + pathDd.appendChild(document.createTextNode(plugin.pluginFullpath)); + dl.appendChild(pathDd); + + // "Version: " + var versionDd = document.createElement("dd"); + var version = document.createElement("span"); + version.setAttribute("class", "label"); + version.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("version_label") + " ")); + versionDd.appendChild(version); + versionDd.appendChild(document.createTextNode(plugin.version)); + dl.appendChild(versionDd); + + // "State: " + var stateDd = document.createElement("dd"); + var state = document.createElement("span"); + state.setAttribute("label", "state"); + state.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("state_label") + " ")); + stateDd.appendChild(state); + var status = plugin.isActive ? pluginsbundle.GetStringFromName("state_enabled") : pluginsbundle.GetStringFromName("state_disabled"); + if (plugin.blocklistState in stateNames) { + status += " (" + stateNames[plugin.blocklistState] + ")"; + } + stateDd.appendChild(document.createTextNode(status)); + dl.appendChild(stateDd); + + // Plugin Description + var descDd = document.createElement("dd"); + descDd.appendChild(document.createTextNode(plugin.description)); + dl.appendChild(descDd); + + // MIME Type table + var mimetypeTable = document.createElement("table"); + mimetypeTable.setAttribute("border", "1"); + mimetypeTable.setAttribute("class", "contenttable"); + fragment.appendChild(mimetypeTable); + + var thead = document.createElement("thead"); + mimetypeTable.appendChild(thead); + var tr = document.createElement("tr"); + thead.appendChild(tr); + + // "MIME Type" column header + var typeTh = document.createElement("th"); + typeTh.setAttribute("class", "type"); + typeTh.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("mimetype_label"))); + tr.appendChild(typeTh); + + // "Description" column header + var descTh = document.createElement("th"); + descTh.setAttribute("class", "desc"); + descTh.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("description_label"))); + tr.appendChild(descTh); + + // "Suffixes" column header + var suffixesTh = document.createElement("th"); + suffixesTh.setAttribute("class", "suff"); + suffixesTh.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("suffixes_label"))); + tr.appendChild(suffixesTh); + + var tbody = document.createElement("tbody"); + mimetypeTable.appendChild(tbody); + + var mimeTypes = plugin.pluginMimeTypes; + for (var j = 0; j < mimeTypes.length; j++) { + var mimetype = mimeTypes[j]; + if (mimetype) { + var mimetypeRow = document.createElement("tr"); + tbody.appendChild(mimetypeRow); + + // "application/x-shockwave-flash" + var typename = document.createElement("td"); + typename.appendChild(document.createTextNode(mimetype.type)); + mimetypeRow.appendChild(typename); + + // "Shockwave Flash" + var description = document.createElement("td"); + description.appendChild(document.createTextNode(mimetype.description)); + mimetypeRow.appendChild(description); + + // "swf" + var suffixes = document.createElement("td"); + suffixes.appendChild(document.createTextNode(mimetype.suffixes)); + mimetypeRow.appendChild(suffixes); + } + } + } + } + + document.getElementById("outside").appendChild(fragment); + }); +</script> +</div> +</body> +</html> diff --git a/components/global/content/process-content.js b/components/global/content/process-content.js new file mode 100644 index 000000000..2ff8f908a --- /dev/null +++ b/components/global/content/process-content.js @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +// Creates a new PageListener for this process. This will listen for page loads +// and for those that match URLs provided by the parent process will set up +// a dedicated message port and notify the parent process. +Cu.import("resource://gre/modules/RemotePageManager.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT; + +Services.cpmm.addMessageListener("gmp-plugin-crash", msg => { + let gmpservice = Cc["@mozilla.org/gecko-media-plugin-service;1"] + .getService(Ci.mozIGeckoMediaPluginService); + + gmpservice.RunPluginCrashCallbacks(msg.data.pluginID, msg.data.pluginName); +}); + +if (gInContentProcess) { + let ProcessObserver = { + TOPICS: [ + "inner-window-destroyed", + "xpcom-shutdown", + ], + + init() { + for (let topic of this.TOPICS) { + Services.obs.addObserver(this, topic, false); + Services.cpmm.addMessageListener("Memory:GetSummary", this); + } + }, + + uninit() { + for (let topic of this.TOPICS) { + Services.obs.removeObserver(this, topic); + Services.cpmm.removeMessageListener("Memory:GetSummary", this); + } + }, + + receiveMessage(msg) { + if (msg.name != "Memory:GetSummary") { + return; + } + let pid = Services.appinfo.processID; + let memMgr = Cc["@mozilla.org/memory-reporter-manager;1"] + .getService(Ci.nsIMemoryReporterManager); + let rss = memMgr.resident; + let uss = memMgr.residentUnique; + Services.cpmm.sendAsyncMessage("Memory:Summary", { + pid, + summary: { + uss, + rss, + } + }); + }, + + observe(subject, topic, data) { + switch (topic) { + case "inner-window-destroyed": { + // Forward inner-window-destroyed notifications with the + // inner window ID, so that code in the parent that should + // do something when content windows go away can do it + let innerWindowID = + subject.QueryInterface(Ci.nsISupportsPRUint64).data; + Services.cpmm.sendAsyncMessage("Toolkit:inner-window-destroyed", + innerWindowID); + break; + } + case "xpcom-shutdown": { + this.uninit(); + break; + } + } + }, + }; + + ProcessObserver.init(); +} diff --git a/components/global/content/resetProfile.css b/components/global/content/resetProfile.css new file mode 100644 index 000000000..a83171ff5 --- /dev/null +++ b/components/global/content/resetProfile.css @@ -0,0 +1,15 @@ +/* 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/. */ + +#migratedItems { + margin-inline-start: 1.5em; +} + +#resetProfileFooter { + font-weight: bold; +} + +#resetProfileProgressDialog { + padding: 10px; +} diff --git a/components/global/content/resetProfile.js b/components/global/content/resetProfile.js new file mode 100644 index 000000000..9a46a09a5 --- /dev/null +++ b/components/global/content/resetProfile.js @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// NB: this file can be loaded from aboutSupport.xhtml or from the +// resetProfile.xul dialog, and so Cu may or may not exist already. +// Proceed with caution: +if (!("Cu" in window)) { + window.Cu = Components.utils; +} + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/ResetProfile.jsm"); + +function onResetProfileAccepted() { + let retVals = window.arguments[0]; + retVals.reset = true; +} diff --git a/components/global/content/resetProfile.xul b/components/global/content/resetProfile.xul new file mode 100644 index 000000000..845d473a4 --- /dev/null +++ b/components/global/content/resetProfile.xul @@ -0,0 +1,35 @@ +<?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 % resetProfileDTD SYSTEM "chrome://global/locale/resetProfile.dtd" > +%resetProfileDTD; +]> + +<?xml-stylesheet href="chrome://global/skin/"?> +<?xml-stylesheet href="chrome://global/content/resetProfile.css"?> + +<dialog id="resetProfileDialog" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&refreshProfile.dialog.title;" + buttons="accept,cancel" + defaultButton="cancel" + buttonlabelaccept="&refreshProfile.dialog.button.label;" + ondialogaccept="return onResetProfileAccepted();" + ondialogcancel="window.close();"> + + <script type="application/javascript" src="chrome://global/content/resetProfile.js"/> + + <description value="&refreshProfile.dialog.description1;"></description> + <label value="&refreshProfile.dialog.description2;"/> + + <vbox id="migratedItems"> + <label class="migratedLabel" value="&refreshProfile.dialog.items.label1;"/> + <label class="migratedLabel" value="&refreshProfile.dialog.items.label2;"/> + </vbox> +</dialog> diff --git a/components/global/content/resetProfileProgress.xul b/components/global/content/resetProfileProgress.xul new file mode 100644 index 000000000..66585e224 --- /dev/null +++ b/components/global/content/resetProfileProgress.xul @@ -0,0 +1,25 @@ +<?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 window [ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > +%brandDTD; +<!ENTITY % resetProfileDTD SYSTEM "chrome://global/locale/resetProfile.dtd" > +%resetProfileDTD; +]> + +<?xml-stylesheet href="chrome://global/content/resetProfile.css"?> +<?xml-stylesheet href="chrome://global/skin/"?> + +<window id="resetProfileProgressDialog" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&refreshProfile.dialog.title;" + style="min-width: 300px;"> + <vbox> + <description>&refreshProfile.cleaning.description;</description> + <progressmeter mode="undetermined"/> + </vbox> +</window> diff --git a/components/global/content/select-child.js b/components/global/content/select-child.js new file mode 100644 index 000000000..d3690b7b8 --- /dev/null +++ b/components/global/content/select-child.js @@ -0,0 +1,26 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "SelectContentHelper", + "resource://gre/modules/SelectContentHelper.jsm"); + +addEventListener("mozshowdropdown", event => { + if (!event.isTrusted) + return; + + if (!SelectContentHelper.open) { + new SelectContentHelper(event.target, {isOpenedViaTouch: false}, this); + } +}); + +addEventListener("mozshowdropdown-sourcetouch", event => { + if (!event.isTrusted) + return; + + if (!SelectContentHelper.open) { + new SelectContentHelper(event.target, {isOpenedViaTouch: true}, this); + } +}); diff --git a/components/global/content/strres.js b/components/global/content/strres.js new file mode 100644 index 000000000..928c8f75f --- /dev/null +++ b/components/global/content/strres.js @@ -0,0 +1,28 @@ +/* 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 strBundleService = null; + +function srGetStrBundle(path) +{ + var strBundle = null; + + if (!strBundleService) { + try { + strBundleService = + Components.classes["@mozilla.org/intl/stringbundle;1"].getService(); + strBundleService = + strBundleService.QueryInterface(Components.interfaces.nsIStringBundleService); + } catch (ex) { + dump("\n--** strBundleService failed: " + ex + "\n"); + return null; + } + } + + strBundle = strBundleService.createBundle(path); + if (!strBundle) { + dump("\n--** strBundle createInstance failed **--\n"); + } + return strBundle; +} diff --git a/components/global/content/tests/reftests/reftest-stylo.list b/components/global/content/tests/reftests/reftest-stylo.list new file mode 100644 index 000000000..77caaabfc --- /dev/null +++ b/components/global/content/tests/reftests/reftest-stylo.list @@ -0,0 +1,6 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +skip-if(B2G&&browserIsRemote) random-if(cocoaWidget) == bug-442419-progressmeter-max.xul bug-442419-progressmeter-max.xul +# fails most of the time on Mac because progress meter animates +# Bug 974780 +skip-if(B2G&&browserIsRemote) == textbox-multiline-default-value.xul textbox-multiline-default-value.xul +# Bug 974780 diff --git a/components/global/content/tests/reftests/reftest.list b/components/global/content/tests/reftests/reftest.list new file mode 100644 index 000000000..a37a9722a --- /dev/null +++ b/components/global/content/tests/reftests/reftest.list @@ -0,0 +1,2 @@ +random-if(cocoaWidget) == bug-442419-progressmeter-max.xul bug-442419-progressmeter-max-ref.xul # fails most of the time on Mac because progress meter animates +!= textbox-multiline-default-value.xul textbox-multiline-empty.xul diff --git a/components/global/content/textbox.css b/components/global/content/textbox.css new file mode 100644 index 000000000..a16b4fd43 --- /dev/null +++ b/components/global/content/textbox.css @@ -0,0 +1,35 @@ +/* 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"); /* set default namespace to XUL */ +@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */ + +html|*.textbox-input { + -moz-appearance: none !important; + text-align: inherit; + text-shadow: inherit; + box-sizing: border-box; + -moz-box-flex: 1; +} + +html|*.textbox-textarea { + -moz-appearance: none !important; + text-shadow: inherit; + box-sizing: border-box; + -moz-box-flex: 1; +} + +/* +html|*.textbox-input::placeholder, +html|*.textbox-textarea::placeholder { + text-align: left; + direction: ltr; +} + +html|*.textbox-input::placeholder:-moz-locale-dir(rtl), +html|*.textbox-textarea::placeholder:-moz-locale-dir(rtl) { + text-align: right; + direction: rtl; +} +*/ diff --git a/components/global/content/timepicker.xhtml b/components/global/content/timepicker.xhtml new file mode 100644 index 000000000..77b9fba41 --- /dev/null +++ b/components/global/content/timepicker.xhtml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html [ + <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> + %htmlDTD; +]> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<head> + <title>Time Picker</title> + <link rel="stylesheet" href="chrome://global/skin/datetimeinputpickers.css"/> + <script type="application/javascript" src="chrome://global/content/bindings/timekeeper.js"></script> + <script type="application/javascript" src="chrome://global/content/bindings/spinner.js"></script> + <script type="application/javascript" src="chrome://global/content/bindings/timepicker.js"></script> +</head> +<body> + <div id="time-picker"></div> + <template id="spinner-template"> + <div class="spinner-container"> + <button class="up"/> + <div class="spinner"></div> + <button class="down"/> + </div> + </template> + <script type="application/javascript"> + // We need to hide the scroll bar but maintain its scrolling + // capability, so using |overflow: hidden| is not an option. + // Instead, we are inserting a user agent stylesheet that is + // capable of selecting scrollbars, and do |display: none|. + var domWinUtls = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor). + getInterface(Components.interfaces.nsIDOMWindowUtils); + domWinUtls.loadSheetUsingURIString('data:text/css,@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); scrollbar { display: none; }', domWinUtls.AGENT_SHEET); + // Create a TimePicker instance and prepare to be + // initialized by the "TimePickerInit" event from timepicker.xml + new TimePicker(document.getElementById("time-picker")); + </script> +</body> +</html>
\ No newline at end of file diff --git a/components/global/content/treeUtils.js b/components/global/content/treeUtils.js new file mode 100644 index 000000000..b4d1fb17d --- /dev/null +++ b/components/global/content/treeUtils.js @@ -0,0 +1,78 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 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 gTreeUtils = { + deleteAll: function (aTree, aView, aItems, aDeletedItems) + { + for (var i = 0; i < aItems.length; ++i) + aDeletedItems.push(aItems[i]); + aItems.splice(0, aItems.length); + var oldCount = aView.rowCount; + aView._rowCount = 0; + aTree.treeBoxObject.rowCountChanged(0, -oldCount); + }, + + deleteSelectedItems: function (aTree, aView, aItems, aDeletedItems) + { + var selection = aTree.view.selection; + selection.selectEventsSuppressed = true; + + var rc = selection.getRangeCount(); + for (var i = 0; i < rc; ++i) { + var min = { }; var max = { }; + selection.getRangeAt(i, min, max); + for (let j = min.value; j <= max.value; ++j) { + aDeletedItems.push(aItems[j]); + aItems[j] = null; + } + } + + var nextSelection = 0; + for (i = 0; i < aItems.length; ++i) { + if (!aItems[i]) { + let j = i; + while (j < aItems.length && !aItems[j]) + ++j; + aItems.splice(i, j - i); + nextSelection = j < aView.rowCount ? j - 1 : j - 2; + aView._rowCount -= j - i; + aTree.treeBoxObject.rowCountChanged(i, i - j); + } + } + + if (aItems.length) { + selection.select(nextSelection); + aTree.treeBoxObject.ensureRowIsVisible(nextSelection); + aTree.focus(); + } + selection.selectEventsSuppressed = false; + }, + + sort: function (aTree, aView, aDataSet, aColumn, aComparator, + aLastSortColumn, aLastSortAscending) + { + var ascending = (aColumn == aLastSortColumn) ? !aLastSortAscending : true; + if (aDataSet.length == 0) + return ascending; + + var numericSort = !isNaN(aDataSet[0][aColumn]); + var sortFunction = null; + if (aComparator) { + sortFunction = function (a, b) { return aComparator(a[aColumn], b[aColumn]); }; + } + aDataSet.sort(sortFunction); + if (!ascending) + aDataSet.reverse(); + + aTree.view.selection.clearSelection(); + aTree.view.selection.select(0); + aTree.treeBoxObject.invalidate(); + aTree.treeBoxObject.ensureRowIsVisible(0); + + return ascending; + } +}; + diff --git a/components/global/content/viewZoomOverlay.js b/components/global/content/viewZoomOverlay.js new file mode 100644 index 000000000..66e054437 --- /dev/null +++ b/components/global/content/viewZoomOverlay.js @@ -0,0 +1,117 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +/** Document Zoom Management Code + * + * To use this, you'll need to have a getBrowser() function or use the methods + * that accept a browser to be modified. + **/ + +var ZoomManager = { + get _prefBranch() { + delete this._prefBranch; + return this._prefBranch = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + }, + + get MIN() { + delete this.MIN; + return this.MIN = this._prefBranch.getIntPref("zoom.minPercent") / 100; + }, + + get MAX() { + delete this.MAX; + return this.MAX = this._prefBranch.getIntPref("zoom.maxPercent") / 100; + }, + + get useFullZoom() { + return this._prefBranch.getBoolPref("browser.zoom.full"); + }, + + set useFullZoom(aVal) { + this._prefBranch.setBoolPref("browser.zoom.full", aVal); + return aVal; + }, + + get zoom() { + return this.getZoomForBrowser(getBrowser()); + }, + + getZoomForBrowser: function ZoomManager_getZoomForBrowser(aBrowser) { + let zoom = (this.useFullZoom || aBrowser.isSyntheticDocument) + ? aBrowser.fullZoom : aBrowser.textZoom; + // Round to remove any floating-point error. + return Number(zoom.toFixed(2)); + }, + + set zoom(aVal) { + this.setZoomForBrowser(getBrowser(), aVal); + return aVal; + }, + + setZoomForBrowser: function ZoomManager_setZoomForBrowser(aBrowser, aVal) { + if (aVal < this.MIN || aVal > this.MAX) + throw Components.results.NS_ERROR_INVALID_ARG; + + if (this.useFullZoom || aBrowser.isSyntheticDocument) { + aBrowser.textZoom = 1; + aBrowser.fullZoom = aVal; + } else { + aBrowser.textZoom = aVal; + aBrowser.fullZoom = 1; + } + }, + + get zoomValues() { + var zoomValues = this._prefBranch.getCharPref("toolkit.zoomManager.zoomValues") + .split(",").map(parseFloat); + zoomValues.sort((a, b) => a - b); + + while (zoomValues[0] < this.MIN) + zoomValues.shift(); + + while (zoomValues[zoomValues.length - 1] > this.MAX) + zoomValues.pop(); + + delete this.zoomValues; + return this.zoomValues = zoomValues; + }, + + enlarge: function ZoomManager_enlarge() { + var i = this.zoomValues.indexOf(this.snap(this.zoom)) + 1; + if (i < this.zoomValues.length) + this.zoom = this.zoomValues[i]; + }, + + reduce: function ZoomManager_reduce() { + var i = this.zoomValues.indexOf(this.snap(this.zoom)) - 1; + if (i >= 0) + this.zoom = this.zoomValues[i]; + }, + + reset: function ZoomManager_reset() { + this.zoom = 1; + }, + + toggleZoom: function ZoomManager_toggleZoom() { + var zoomLevel = this.zoom; + + this.useFullZoom = !this.useFullZoom; + this.zoom = zoomLevel; + }, + + snap: function ZoomManager_snap(aVal) { + var values = this.zoomValues; + for (var i = 0; i < values.length; i++) { + if (values[i] >= aVal) { + if (i > 0 && aVal - values[i - 1] < values[i] - aVal) + i--; + return values[i]; + } + } + return values[i - 1]; + } +}; diff --git a/components/global/content/xul.css b/components/global/content/xul.css new file mode 100644 index 000000000..f51ec77e4 --- /dev/null +++ b/components/global/content/xul.css @@ -0,0 +1,1198 @@ +/* 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/. */ + +/** + * A minimal set of rules for the XUL elements that may be implicitly created + * as part of HTML/SVG documents (e.g. scrollbars) can be found over in + * minimal-xul.css. Rules for everything else related to XUL can be found in + * this file. Make sure you choose the correct style sheet when adding new + * rules. (This split of the XUL rules is to minimize memory use and improve + * performance in HTML/SVG documents.) + * + * This file should also not contain any app specific styling. Defaults for + * widgets of a particular application should be in that application's style + * sheet. For example, style definitions for navigator can be found in + * navigator.css. + * + * THIS FILE IS LOCKED DOWN. YOU ARE NOT ALLOWED TO MODIFY IT WITHOUT FIRST + * HAVING YOUR CHANGES REVIEWED BY enndeakin@gmail.com + */ + +@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */ +@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */ +@namespace xbl url("http://www.mozilla.org/xbl"); /* namespace for XBL elements */ + +/* :::::::::: + :: Rules for 'hiding' portions of the chrome for special + :: kinds of windows (not JUST browser windows) with toolbars + ::::: */ + +window[chromehidden~="menubar"] .chromeclass-menubar, +window[chromehidden~="directories"] .chromeclass-directories, +window[chromehidden~="status"] .chromeclass-status, +window[chromehidden~="extrachrome"] .chromeclass-extrachrome, +window[chromehidden~="location"] .chromeclass-location, +window[chromehidden~="location"][chromehidden~="toolbar"] .chromeclass-toolbar, +window[chromehidden~="toolbar"] .chromeclass-toolbar-additional { + display: none; +} + +/* :::::::::: + :: Rules for forcing direction for entry and display of URIs + :: or URI elements + ::::: */ + +.uri-element { + direction: ltr !important; +} + +/****** elements that have no visual representation ******/ + +script, data, +xbl|children, +commands, commandset, command, +broadcasterset, broadcaster, observes, +keyset, key, toolbarpalette, toolbarset, +template, rule, conditions, action, +bindings, binding, content, member, triple, +treechildren, treeitem, treeseparator, treerow, treecell { + display: none; +} + +/********** focus rules **********/ + +button, +checkbox, +colorpicker[type="button"], +datepicker[type="grid"], +menulist, +radiogroup, +tree, +browser, +editor, +iframe { + -moz-user-focus: normal; +} + +menulist[editable="true"] { + -moz-user-focus: ignore; +} + +/******** window & page ******/ + +window, +page { + overflow: -moz-hidden-unscrollable; + -moz-box-orient: vertical; +} + +/******** box *******/ + +vbox { + -moz-box-orient: vertical; +} + +bbox { + -moz-box-align: baseline; +} + +/********** button **********/ + +button { + -moz-binding: url("chrome://global/content/bindings/button.xml#button"); +} + +button[type="repeat"] { + -moz-binding: url("chrome://global/content/bindings/button.xml#button-repeat"); +} + +button[type="menu"], button[type="panel"] { + -moz-binding: url("chrome://global/content/bindings/button.xml#menu"); +} + +button[type="menu-button"] { + -moz-binding: url("chrome://global/content/bindings/button.xml#menu-button"); +} + +/********** toolbarbutton **********/ + +toolbarbutton { + -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton"); +} + +toolbarbutton.badged-button > toolbarbutton, +toolbarbutton.badged-button { + -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-badged"); +} + +.toolbarbutton-badge:not([value]), +.toolbarbutton-badge[value=""] { + display: none; +} + +toolbarbutton[type="menu"], +toolbarbutton[type="panel"] { + -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#menu"); +} + +toolbarbutton.badged-button[type="menu"], +toolbarbutton.badged-button[type="panel"] { + -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-badged-menu"); +} + +toolbarbutton[type="menu-button"] { + -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#menu-button"); +} + +toolbar[mode="icons"] .toolbarbutton-text, +toolbar[mode="icons"] .toolbarbutton-multiline-text, +toolbar[mode="text"] .toolbarbutton-icon { + display: none; +} + +.toolbarbutton-multiline-text:not([wrap="true"]), +.toolbarbutton-text[wrap="true"] { + display: none; +} + +/******** browser, editor, iframe ********/ + +browser, +editor, +iframe { + display: inline; +} + +browser { + -moz-binding: url("chrome://global/content/bindings/browser.xml#browser"); +} + +editor { + -moz-binding: url("chrome://global/content/bindings/editor.xml#editor"); +} + +iframe { + -moz-binding: url("chrome://global/content/bindings/general.xml#iframe"); +} + +/********** notifications **********/ + +notificationbox { + -moz-binding: url("chrome://global/content/bindings/notification.xml#notificationbox"); + -moz-box-orient: vertical; +} + +.notificationbox-stack { + overflow: -moz-hidden-unscrollable; +} + +notification { + -moz-binding: url("chrome://global/content/bindings/notification.xml#notification"); + transition: margin-top 300ms, opacity 300ms; +} + +/*********** popup notification ************/ +popupnotification { + -moz-binding: url("chrome://global/content/bindings/notification.xml#popup-notification"); +} + +.popup-notification-menubutton:not([label]) { + display: none; +} + +/********** image **********/ + +image { + -moz-binding: url("chrome://global/content/bindings/general.xml#image"); +} + +/********** checkbox **********/ + +checkbox { + -moz-binding: url("chrome://global/content/bindings/checkbox.xml#checkbox"); +} + +/********** radio **********/ + +radiogroup { + -moz-binding: url("chrome://global/content/bindings/radio.xml#radiogroup"); + -moz-box-orient: vertical; +} + +radio { + -moz-binding: url("chrome://global/content/bindings/radio.xml#radio"); +} + +/******** groupbox *********/ + +groupbox { + -moz-binding: url("chrome://global/content/bindings/groupbox.xml#groupbox"); + display: -moz-groupbox; +} + +caption { + -moz-binding: url("chrome://global/content/bindings/groupbox.xml#caption"); +} + +.groupbox-body { + -moz-box-pack: inherit; + -moz-box-align: inherit; + -moz-box-orient: vertical; +} + +/******** draggable elements *********/ + +windowdragbox { + -moz-window-dragging: drag; +} + +/* The list below is non-comprehensive and will probably need some tweaking. */ +toolbaritem, +toolbarbutton, +button, +textbox, +tab, +radio, +splitter, +scale, +menulist { + -moz-window-dragging: no-drag; +} + +/******* toolbar *******/ + +toolbox { + -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbox"); + -moz-box-orient: vertical; +} + +toolbar { + -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar"); +} + +toolbar[customizing="true"][collapsed="true"] { + /* Some apps, e.g. Firefox, use 'collapsed' to hide toolbars. + Override it while customizing. */ + visibility: visible; +} + +toolbar[customizing="true"][hidden="true"] { + /* Some apps, e.g. SeaMonkey, use 'hidden' to hide toolbars. + Override it while customizing. */ + display: -moz-box; +} + +toolbar[type="menubar"][autohide="true"] { + -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-menubar-autohide"); + overflow: hidden; +} + +toolbar[type="menubar"][autohide="true"][inactive="true"]:not([customizing="true"]) { + min-height: 0 !important; + height: 0 !important; + -moz-appearance: none !important; + border-style: none !important; +} + +%ifdef MOZ_WIDGET_GTK +window[shellshowingmenubar="true"] menubar, +window[shellshowingmenubar="true"] +toolbar[type="menubar"]:not([customizing="true"]) { + /* If a system-wide global menubar is in use, hide the XUL menubar. */ + display: none !important; +} +%endif + +toolbarseparator { + -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration"); +} + +toolbarspacer { + -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration"); +} + +toolbarspring { + -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration"); + -moz-box-flex: 1000; +} + +toolbarpaletteitem { + -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem"); +} + +toolbarpaletteitem[place="palette"] { + -moz-box-orient: vertical; + -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem-palette"); +} + +/********* menubar ***********/ + +menubar { + -moz-binding: url("chrome://global/content/bindings/toolbar.xml#menubar"); +} + +/********* menu ***********/ + +menubar > menu { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menu-menubar"); +} + +menubar > menu.menu-iconic { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menu-menubar-iconic"); +} + +menu { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menu"); +} + +menu.menu-iconic { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menu-iconic"); +} + +menubar > menu:empty { + visibility: collapse; +} + +/********* menuitem ***********/ + +menuitem { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem"); +} + +menuitem.menuitem-iconic { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic"); +} + +menuitem[description] { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic-desc-noaccel"); +} + +menuitem[type="checkbox"], +menuitem[type="radio"] { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic"); +} + +menuitem.menuitem-non-iconic { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menubutton-item"); +} + +menucaption { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menucaption"); +} + +.menu-text { + -moz-box-flex: 1; +} + +/********* menuseparator ***********/ + +menuseparator { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menuseparator"); +} + +/********* popup & menupopup ***********/ + +/* <popup> is deprecated. Only <menupopup> and <tooltip> are still valid. */ + +popup, +menupopup { + -moz-binding: url("chrome://global/content/bindings/popup.xml#popup"); + -moz-box-orient: vertical; +} + +panel { + -moz-binding: url("chrome://global/content/bindings/popup.xml#panel"); + -moz-box-orient: vertical; +} + +popup, +menupopup, +panel, +tooltip { + display: -moz-popup; + z-index: 2147483647; + text-shadow: none; +} + +tooltip { + -moz-binding: url("chrome://global/content/bindings/popup.xml#tooltip"); + -moz-box-orient: vertical; + white-space: pre-wrap; + margin-top: 21px; +} + +panel[type="arrow"] { + -moz-binding: url("chrome://global/content/bindings/popup.xml#arrowpanel"); +} + +%ifndef MOZ_WIDGET_GTK + +panel[type="arrow"]:not([animate="false"]) { + transform: scale(.4); + opacity: 0; + transition-property: transform, opacity; + transition-duration: 0.15s; + transition-timing-function: ease-out; +} + +panel[type="arrow"][animate="open"] { + transform: none; + opacity: 1.0; +} + +panel[type="arrow"][animate="cancel"] { + transform: none; +} + +panel[arrowposition="after_start"]:-moz-locale-dir(ltr), +panel[arrowposition="after_end"]:-moz-locale-dir(rtl) { + transform-origin: 20px top; +} + +panel[arrowposition="after_end"]:-moz-locale-dir(ltr), +panel[arrowposition="after_start"]:-moz-locale-dir(rtl) { + transform-origin: calc(100% - 20px) top; +} + +panel[arrowposition="before_start"]:-moz-locale-dir(ltr), +panel[arrowposition="before_end"]:-moz-locale-dir(rtl) { + transform-origin: 20px bottom; +} + +panel[arrowposition="before_end"]:-moz-locale-dir(ltr), +panel[arrowposition="before_start"]:-moz-locale-dir(rtl) { + transform-origin: calc(100% - 20px) bottom; +} + +panel[arrowposition="start_before"]:-moz-locale-dir(ltr), +panel[arrowposition="end_before"]:-moz-locale-dir(rtl) { + transform-origin: right 20px; +} + +panel[arrowposition="start_after"]:-moz-locale-dir(ltr), +panel[arrowposition="end_after"]:-moz-locale-dir(rtl) { + transform-origin: right calc(100% - 20px); +} + +panel[arrowposition="end_before"]:-moz-locale-dir(ltr), +panel[arrowposition="start_before"]:-moz-locale-dir(rtl) { + transform-origin: left 20px; +} + +panel[arrowposition="end_after"]:-moz-locale-dir(ltr), +panel[arrowposition="start_after"]:-moz-locale-dir(rtl) { + transform-origin: left calc(100% - 20px); +} + +%endif + +window[sizemode="maximized"] statusbarpanel.statusbar-resizerpanel { + visibility: collapse; +} + +/******** grid **********/ + +grid { + display: -moz-grid; +} + +rows, +columns { + display: -moz-grid-group; +} + +row, +column { + display: -moz-grid-line; +} + +rows { + -moz-box-orient: vertical; +} + +column { + -moz-box-orient: vertical; +} + +/******** listbox **********/ + +listbox { + -moz-binding: url("chrome://global/content/bindings/listbox.xml#listbox"); +} + +listhead { + -moz-binding: url("chrome://global/content/bindings/listbox.xml#listhead"); +} + +listrows { + -moz-binding: url("chrome://global/content/bindings/listbox.xml#listrows"); +} + +listitem { + -moz-binding: url("chrome://global/content/bindings/listbox.xml#listitem"); +} + +listitem[type="checkbox"] { + -moz-binding: url("chrome://global/content/bindings/listbox.xml#listitem-checkbox"); +} + +listheader { + -moz-binding: url("chrome://global/content/bindings/listbox.xml#listheader"); + -moz-box-ordinal-group: 2147483646; +} + +listcell { + -moz-binding: url("chrome://global/content/bindings/listbox.xml#listcell"); +} + +listcell[type="checkbox"] { + -moz-binding: url("chrome://global/content/bindings/listbox.xml#listcell-checkbox"); +} + +.listitem-iconic { + -moz-binding: url("chrome://global/content/bindings/listbox.xml#listitem-iconic"); +} + +listitem[type="checkbox"].listitem-iconic { + -moz-binding: url("chrome://global/content/bindings/listbox.xml#listitem-checkbox-iconic"); +} + +.listcell-iconic { + -moz-binding: url("chrome://global/content/bindings/listbox.xml#listcell-iconic"); +} + +listcell[type="checkbox"].listcell-iconic { + -moz-binding: url("chrome://global/content/bindings/listbox.xml#listcell-checkbox-iconic"); +} + +listbox { + display: -moz-grid; +} + +listbox[rows] { + height: auto; +} + +listcols, listhead, listrows, listboxbody { + display: -moz-grid-group; +} + +listcol, listitem, listheaditem { + display: -moz-grid-line; +} + +listbox { + -moz-user-focus: normal; + -moz-box-orient: vertical; + min-width: 0px; + min-height: 0px; + width: 200px; + height: 200px; +} + +listhead { + -moz-box-orient: vertical; +} + +listrows { + -moz-box-orient: vertical; + -moz-box-flex: 1; +} + +listboxbody { + -moz-box-orient: vertical; + -moz-box-flex: 1; + /* Don't permit a horizontal scrollbar. See bug 285449 */ + overflow-x: hidden !important; + overflow-y: auto; + min-height: 0px; +} + +listcol { + -moz-box-orient: vertical; + min-width: 16px; +} + +listcell { + -moz-box-align: center; +} + +/******** tree ******/ + +tree { + -moz-binding: url("chrome://global/content/bindings/tree.xml#tree"); +} + +treecols { + -moz-binding: url("chrome://global/content/bindings/tree.xml#treecols"); +} + +treecol { + -moz-binding: url("chrome://global/content/bindings/tree.xml#treecol"); + -moz-box-ordinal-group: 2147483646; +} + +treecol.treecol-image { + -moz-binding: url("chrome://global/content/bindings/tree.xml#treecol-image"); +} + +tree > treechildren { + display: -moz-box; + -moz-binding: url("chrome://global/content/bindings/tree.xml#treebody"); + -moz-user-select: none; + -moz-box-flex: 1; +} + +treerows { + -moz-binding: url("chrome://global/content/bindings/tree.xml#treerows"); +} + +treecolpicker { + -moz-binding: url("chrome://global/content/bindings/tree.xml#columnpicker"); +} + +tree { + -moz-box-orient: vertical; + min-width: 0px; + min-height: 0px; + width: 10px; + height: 10px; +} + +tree[hidecolumnpicker="true"] > treecols > treecolpicker { + display: none; +} + +treecol { + min-width: 16px; +} + +treecol[hidden="true"] { + visibility: collapse; + display: -moz-box; +} + +.tree-scrollable-columns { + /* Yes, Virginia, this makes it scrollable */ + overflow: hidden; +} + +/* ::::: lines connecting cells ::::: */ +tree:not([treelines="true"]) > treechildren::-moz-tree-line { + visibility: hidden; +} + +treechildren::-moz-tree-cell(ltr) { + direction: ltr !important; +} + +/********** deck & stack *********/ + +deck { + display: -moz-deck; + -moz-binding: url("chrome://global/content/bindings/general.xml#deck"); +} + +stack, bulletinboard { + display: -moz-stack; +} + +/********** tabbox *********/ + +tabbox { + -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tabbox"); + -moz-box-orient: vertical; +} + +tabs { + -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tabs"); + -moz-box-orient: horizontal; +} + +tab { + -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tab"); + -moz-box-align: center; + -moz-box-pack: center; +} + +tab[selected="true"]:not([ignorefocus="true"]) { + -moz-user-focus: normal; +} + +tabpanels { + -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tabpanels"); + display: -moz-deck; +} + +/********** progressmeter **********/ + +progressmeter { + -moz-binding: url("chrome://global/content/bindings/progressmeter.xml#progressmeter"); +} + +/********** basic rule for anonymous content that needs to pass box properties through + ********** to an insertion point parent that holds the real kids **************/ + +.box-inherit { + -moz-box-orient: inherit; + -moz-box-pack: inherit; + -moz-box-align: inherit; + -moz-box-direction: inherit; +} + +/********** textbox **********/ + +textbox { + -moz-binding: url("chrome://global/content/bindings/textbox.xml#textbox"); + -moz-user-select: text; + text-shadow: none; +} + +textbox[multiline="true"] { + -moz-binding: url("chrome://global/content/bindings/textbox.xml#textarea"); +} + +.textbox-input-box { + -moz-binding: url("chrome://global/content/bindings/textbox.xml#input-box"); +} + +html|textarea.textbox-textarea { + resize: none; +} + +textbox[resizable="true"] > .textbox-input-box > html|textarea.textbox-textarea { + resize: both; +} + +.textbox-input-box[spellcheck="true"] { + -moz-binding: url("chrome://global/content/bindings/textbox.xml#input-box-spell"); +} + +textbox[type="timed"] { + -moz-binding: url("chrome://global/content/bindings/textbox.xml#timed-textbox"); +} + +textbox[type="search"] { + -moz-binding: url("chrome://global/content/bindings/textbox.xml#search-textbox"); +} + +textbox[type="number"] { + -moz-binding: url("chrome://global/content/bindings/numberbox.xml#numberbox"); +} + +.textbox-contextmenu:-moz-locale-dir(rtl) { + direction: rtl; +} + +/********** autocomplete textbox **********/ + +/* SeaMonkey does not use the new toolkit's autocomplete widget */ +%ifdef BINOC_NAVIGATOR + +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"); +} + +.autocomplete-history-dropmarker { + -moz-binding: url("chrome://global/content/autocomplete.xml#history-dropmarker"); +} + +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-richlistitem { + -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem"); + -moz-box-orient: vertical; + overflow: -moz-hidden-unscrollable; +} + +%else + +textbox[type="autocomplete"] { + -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete"); +} + +panel[type="autocomplete"] { + -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-result-popup"); +} + +panel[type="autocomplete-richlistbox"] { + -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup"); +} + +/* FIXME: bug 616258 */ + +.autocomplete-tree { + -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-tree"); + -moz-user-focus: ignore; +} + +.autocomplete-treebody { + -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-treebody"); +} + +.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-richlistitem { + -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem"); + -moz-box-orient: vertical; + overflow: -moz-hidden-unscrollable; +} + +.autocomplete-treerows { + -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-treerows"); +} + +.autocomplete-history-dropmarker { + display: none; +} + +.autocomplete-history-dropmarker[enablehistory="true"] { + display: -moz-box; + -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#history-dropmarker"); +} + +%endif + +/* the C++ implementation of widgets is too eager to make popups visible. + this causes problems (bug 120155 and others), thus this workaround: */ +popup[type="autocomplete"][hidden="true"] { + visibility: hidden; +} + +/* The following rule is here to fix bug 96899 (and now 117952). + Somehow trees create a situation + in which a popupset flows itself as if its popup child is directly within it + instead of the placeholder child that should actually be inside the popupset. + This is a stopgap measure, and it does not address the real bug. */ +.autocomplete-result-popupset { + max-width: 0px; + width: 0 !important; + min-width: 0%; + min-height: 0%; +} + +/********** colorpicker **********/ + +colorpicker { + -moz-binding: url("chrome://global/content/bindings/colorpicker.xml#colorpicker"); +} + +colorpicker[type="button"] { + -moz-binding: url("chrome://global/content/bindings/colorpicker.xml#colorpicker-button"); +} + +.colorpickertile { + -moz-binding: url("chrome://global/content/bindings/colorpicker.xml#colorpickertile"); +} + +/********** menulist **********/ + +menulist { + -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist"); +} + +menulist[popuponly="true"] { + -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist-popuponly"); + -moz-appearance: none !important; + margin: 0 !important; + height: 0 !important; + border: 0 !important; +} + +menulist[editable="true"] { + -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist-editable"); +} + +menulist[type="description"] { + -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist-description"); +} + +menulist > menupopup > menuitem { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic-noaccel"); +} + +menulist > menupopup > menucaption { + -moz-binding: url("chrome://global/content/bindings/menu.xml#menucaption-inmenulist"); +} + +dropmarker { + -moz-binding: url("chrome://global/content/bindings/general.xml#dropmarker"); +} + +/********** splitter **********/ + +splitter { + -moz-binding: url("chrome://global/content/bindings/splitter.xml#splitter"); +} + +grippy { + -moz-binding: url("chrome://global/content/bindings/splitter.xml#grippy"); +} + +.tree-splitter { + width: 0px; + max-width: 0px; + min-width: 0% ! important; + min-height: 0% ! important; + -moz-box-ordinal-group: 2147483646; +} + +/******** scrollbar ********/ + +slider { + /* This is a hint to layerization that the scrollbar thumb can never leave + the scrollbar track. */ + overflow: hidden; +} + +/******** scrollbox ********/ + +scrollbox { + -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#scrollbox"); + /* This makes it scrollable! */ + overflow: hidden; +} + +arrowscrollbox { + -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#arrowscrollbox"); +} + +arrowscrollbox[clicktoscroll="true"] { + -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll"); +} + +autorepeatbutton { + -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#autorepeatbutton"); +} + +/********** statusbar **********/ + +statusbar { + -moz-binding: url("chrome://global/content/bindings/general.xml#statusbar"); +} + +statusbarpanel { + -moz-binding: url("chrome://global/content/bindings/general.xml#statusbarpanel"); +} + +.statusbarpanel-iconic { + -moz-binding: url("chrome://global/content/bindings/general.xml#statusbarpanel-iconic"); +} + +.statusbarpanel-iconic-text { + -moz-binding: url("chrome://global/content/bindings/general.xml#statusbarpanel-iconic-text"); +} + +.statusbarpanel-menu-iconic { + -moz-binding: url("chrome://global/content/bindings/general.xml#statusbarpanel-menu-iconic"); +} + +/********** spinbuttons ***********/ + +spinbuttons { + -moz-binding: url("chrome://global/content/bindings/spinbuttons.xml#spinbuttons"); +} + +.spinbuttons-button { + -moz-user-focus: ignore; +} + +/********** stringbundle **********/ + +stringbundleset { + -moz-binding: url("chrome://global/content/bindings/stringbundle.xml#stringbundleset"); + visibility: collapse; +} + +stringbundle { + -moz-binding: url("chrome://global/content/bindings/stringbundle.xml#stringbundle"); + visibility: collapse; +} + +/********** dialog **********/ + +dialog, +dialog:root /* override :root from above */ { + -moz-binding: url("chrome://global/content/bindings/dialog.xml#dialog"); + -moz-box-orient: vertical; +} + +dialogheader { + -moz-binding: url("chrome://global/content/bindings/dialog.xml#dialogheader"); +} + +/********* page ************/ + +page { + -moz-box-orient: vertical; +} + +/********** wizard **********/ + +wizard, +wizard:root /* override :root from above */ { + -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizard"); + -moz-box-orient: vertical; + width: 40em; + height: 30em; +} + +wizardpage { + -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizardpage"); + -moz-box-orient: vertical; + overflow: auto; +} + +.wizard-header { + -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizard-header"); +} + +.wizard-buttons { + -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizard-buttons"); +} + +/********** preferences ********/ + +prefwindow, +prefwindow:root /* override :root from above */ { + -moz-binding: url("chrome://global/content/bindings/preferences.xml#prefwindow"); + -moz-box-orient: vertical; +} + +prefpane { + -moz-binding: url("chrome://global/content/bindings/preferences.xml#prefpane"); + -moz-box-orient: vertical; +} + +prefwindow > .paneDeckContainer { + overflow: hidden; +} + +prefpane > .content-box { + overflow: hidden; +} + +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://global/content/bindings/preferences.xml#preferences"); + visibility: collapse; +} + +preference { + -moz-binding: url("chrome://global/content/bindings/preferences.xml#preference"); + visibility: collapse; +} + +radio[pane] { + -moz-binding: url("chrome://global/content/bindings/preferences.xml#panebutton") !important; + -moz-box-orient: vertical; + -moz-box-align: center; +} + +prefwindow[chromehidden~="toolbar"] .chromeclass-toolbar { + display: none; +} + +/********** expander ********/ + +expander { + -moz-binding: url("chrome://global/content/bindings/expander.xml#expander"); + -moz-box-orient: vertical; +} + + +/********** Rich Listbox ********/ + +richlistbox { + -moz-binding: url('chrome://global/content/bindings/richlistbox.xml#richlistbox'); + -moz-user-focus: normal; + -moz-box-orient: vertical; +} + +richlistitem { + -moz-binding: url('chrome://global/content/bindings/richlistbox.xml#richlistitem'); +} + +richlistbox > listheader { + -moz-box-ordinal-group: 1; +} + +/********** datepicker and timepicker ********/ + +datepicker { + -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#datepicker'); +} + +datepicker[type="popup"] { + -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#datepicker-popup'); +} + +datepicker[type="grid"] { + -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#datepicker-grid'); +} + +timepicker { + -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#timepicker'); +} + + +/*********** findbar ************/ +findbar { + -moz-binding: url('chrome://global/content/bindings/findbar.xml#findbar'); +} + +.findbar-textbox { + -moz-binding: url("chrome://global/content/bindings/findbar.xml#findbar-textbox"); +} + + +/*********** filefield ************/ +filefield { + -moz-binding: url("chrome://global/content/bindings/filefield.xml#filefield"); +} + +/*********** tabmodalprompt ************/ +tabmodalprompt { + -moz-binding: url("chrome://global/content/tabprompts.xml#tabmodalprompt"); + overflow: hidden; + text-shadow: none; +} |