summaryrefslogtreecommitdiff
path: root/components/global/content
diff options
context:
space:
mode:
Diffstat (limited to 'components/global/content')
-rw-r--r--components/global/content/TopLevelVideoDocument.js48
-rw-r--r--components/global/content/XPCNativeWrapper.js7
-rw-r--r--components/global/content/about.js54
-rw-r--r--components/global/content/about.xhtml42
-rw-r--r--components/global/content/aboutAbout.js47
-rw-r--r--components/global/content/aboutAbout.xhtml25
-rw-r--r--components/global/content/aboutNetworking.js414
-rw-r--r--components/global/content/aboutNetworking.xhtml168
-rw-r--r--components/global/content/aboutProfiles.js337
-rw-r--r--components/global/content/aboutProfiles.xhtml38
-rw-r--r--components/global/content/aboutRights-unbranded.xhtml59
-rw-r--r--components/global/content/aboutRights.xhtml88
-rw-r--r--components/global/content/aboutServiceWorkers.js184
-rw-r--r--components/global/content/aboutServiceWorkers.xhtml34
-rw-r--r--components/global/content/aboutSupport.js997
-rw-r--r--components/global/content/aboutSupport.xhtml557
-rw-r--r--components/global/content/aboutwebrtc/aboutWebrtc.css97
-rw-r--r--components/global/content/aboutwebrtc/aboutWebrtc.html21
-rw-r--r--components/global/content/aboutwebrtc/aboutWebrtc.js843
-rw-r--r--components/global/content/autocomplete.css40
-rw-r--r--components/global/content/browser-child.js598
-rw-r--r--components/global/content/browser-content.js1777
-rw-r--r--components/global/content/buildconfig.html62
-rw-r--r--components/global/content/contentAreaUtils.js1297
-rw-r--r--components/global/content/customizeToolbar.css39
-rw-r--r--components/global/content/customizeToolbar.js852
-rw-r--r--components/global/content/customizeToolbar.xul67
-rw-r--r--components/global/content/datepicker.xhtml60
-rw-r--r--components/global/content/dialogOverlay.js107
-rw-r--r--components/global/content/dialogOverlay.xul58
-rw-r--r--components/global/content/directionDetector.html13
-rw-r--r--components/global/content/editMenuOverlay.js39
-rw-r--r--components/global/content/editMenuOverlay.xul108
-rw-r--r--components/global/content/filepicker.properties12
-rw-r--r--components/global/content/findUtils.js111
-rw-r--r--components/global/content/finddialog.js151
-rw-r--r--components/global/content/finddialog.xul58
-rw-r--r--components/global/content/globalOverlay.js161
-rw-r--r--components/global/content/globalOverlay.xul38
-rw-r--r--components/global/content/gmp-sources/openh264.json57
-rw-r--r--components/global/content/gmp-sources/widevinecdm.json49
-rw-r--r--components/global/content/inlineSpellCheckUI.js7
-rw-r--r--components/global/content/logopage.xhtml43
-rw-r--r--components/global/content/menulist.css11
-rw-r--r--components/global/content/minimal-xul.css133
-rw-r--r--components/global/content/mozilla.xhtml76
-rw-r--r--components/global/content/nsClipboard.js64
-rw-r--r--components/global/content/nsUserSettings.js108
-rw-r--r--components/global/content/plugins.css88
-rw-r--r--components/global/content/plugins.html207
-rw-r--r--components/global/content/process-content.js84
-rw-r--r--components/global/content/resetProfile.css15
-rw-r--r--components/global/content/resetProfile.js20
-rw-r--r--components/global/content/resetProfile.xul35
-rw-r--r--components/global/content/resetProfileProgress.xul25
-rw-r--r--components/global/content/select-child.js26
-rw-r--r--components/global/content/strres.js28
-rw-r--r--components/global/content/tests/reftests/reftest-stylo.list6
-rw-r--r--components/global/content/tests/reftests/reftest.list2
-rw-r--r--components/global/content/textbox.css35
-rw-r--r--components/global/content/timepicker.xhtml36
-rw-r--r--components/global/content/treeUtils.js78
-rw-r--r--components/global/content/viewZoomOverlay.js117
-rw-r--r--components/global/content/xul.css1198
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
+ + '&lt;<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 += "/&gt;";
+ }
+ else {
+ str += "&gt;";
+ 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
+ + '&lt;/<span class="end-tag">' + node.nodeName + '</span>&gt;';
+ }
+ 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 = {
+ '&': '&amp;<span class="entity">amp;</span>',
+ '<': '&amp;<span class="entity">lt;</span>',
+ '>': '&amp;<span class="entity">gt;</span>',
+ '"': '&amp;<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 '&amp;<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="&copyCmd.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="&copyCmd.label;"
+ key="key_copy" accesskey="&copyCmd.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="&copyCmd.label;"
+ accesskey="&copyCmd.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;
+}