Bug 518607 - Move the Troubleshooting Information page into toolkit so other apps like Thunderbird and SeaMonkey can use it, r=gavin
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 20 Feb 2010 19:36:46 -0800
changeset 41305 744994e4f7cfea41e17fb6578b8910348d20638c
parent 41304 c6b1ea51deadd875393e1395b0ab3be2e923a9df
child 41306 bd8020e651d0ea41c110e00d97ad4c7fa6d25af9
push idunknown
push userunknown
push dateunknown
reviewersgavin
bugs518607
milestone1.9.3a5pre
Bug 518607 - Move the Troubleshooting Information page into toolkit so other apps like Thunderbird and SeaMonkey can use it, r=gavin
browser/base/content/aboutSupport.xhtml
browser/base/jar.mn
browser/components/about/AboutRedirector.cpp
browser/components/build/nsModule.cpp
browser/locales/en-US/chrome/browser/aboutSupport.dtd
browser/locales/jar.mn
docshell/base/nsAboutRedirector.cpp
docshell/build/nsDocShellModule.cpp
toolkit/content/aboutSupport.xhtml
toolkit/content/jar.mn
toolkit/locales/en-US/chrome/global/aboutSupport.dtd
toolkit/locales/jar.mn
deleted file mode 100644
--- a/browser/base/content/aboutSupport.xhtml
+++ /dev/null
@@ -1,536 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is aboutSupport.xhtml.
-#
-# The Initial Developer of the Original Code is
-# Mozilla Corporation
-# Portions created by the Initial Developer are Copyright (C) 2009
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-#   Curtis Bartley <cbartley@mozilla.com>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
-
-<!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://browser/locale/aboutSupport.dtd"> %aboutSupportDTD;
-]>
-
-<html xmlns="http://www.w3.org/1999/xhtml">
-  <head>
-    <title>&aboutSupport.pageTitle;</title>
-
-    <style type="text/css"><![CDATA[
-
-html {
-  background-color: -moz-Field;
-  color: -moz-FieldText;
-  font: message-box;
-}
-
-body {
-  text-align: justify;
-  width: 90%;
-  margin-left: 5%;
-  margin-right: 5%;
-}
-
-.page-subtitle {
-  margin-bottom: 3em; 
-}
-
-.major-section {
-  margin-top: 2em;
-  margin-bottom: 1em;
-  font-size: large;
-  text-align: start;
-  font-weight: bold;
-}
-
-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: 1px dotted ThreeDShadow;
-  padding: 3px;
-}
-
-thead th {
-  text-align: center;
-}
-
-th {
-  text-align: start;
-  background-color: Highlight;
-  color: HighlightText;
-}
-
-th.column {
-  white-space: nowrap;
-  width: 0px; 
-}
-
-td {
-  text-align: start;
-  border-top: 1px dotted ThreeDShadow;
-}
-
-.prefs-table {
-  width: 100%;
-  table-layout: fixed;
-}
-
-.pref-name {
-  width: 70%;
-  white-space: nowrap;
-  overflow: hidden;
-}
-
-.pref-value {
-  width: 30%;
-  white-space: nowrap;
-  overflow: hidden;
-}
-
-    ]]></style>
-
-    <script type="application/javascript;version=1.7"><![CDATA[
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-
-let gPrefService = Cc["@mozilla.org/preferences-service;1"]
-                     .getService(Ci.nsIPrefService)
-                     .QueryInterface(Ci.nsIPrefBranch2);
-
-const ELLIPSIS = gPrefService.getComplexValue("intl.ellipsis",
-                                              Ci.nsIPrefLocalizedString).data;
-
-// We use a preferences whitelist to make sure we only show preferences that
-// are useful for support and won't compromise the user's privacy.  Note that
-// entries are *prefixes*: for example, "accessibility." applies to all prefs
-// under the "accessibility.*" branch.
-const PREFS_WHITELIST = [
-  "accessibility.",
-  "browser.fixup.",
-  "browser.history_expire_",
-  "browser.link.open_newwindow",
-  "browser.mousewheel.",
-  "browser.places.",
-  "browser.startup.homepage",
-  "browser.tabs.",
-  "browser.zoom.",
-  "dom.",
-  "extensions.checkCompatibility",
-  "extensions.lastAppVersion",
-  "font.",
-  "general.useragent.",
-  "gfx.color_management.mode",
-  "javascript.",
-  "keyword.", 
-  "layout.css.dpi",
-  "network.",
-  "places.",
-  "print.",
-  "privacy.",
-  "security."
-];
-
-// The blacklist, unlike the whitelist, is a list of regular expressions.
-const PREFS_BLACKLIST = [
-  /^network[.]proxy[.]/,
-  /[.]print_to_filename$/,
-];
-
-window.onload = function () {
-  // Get the FUEL Application object.
-  let Application = Cc["@mozilla.org/fuel/application;1"]
-                      .getService(Ci.fuelIApplication);
-
-  // Get the support URL.
-  let urlFormatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"]
-                       .getService(Ci.nsIURLFormatter);
-  let supportUrl = urlFormatter.formatURLPref("app.support.baseURL");
-
-  // Update the application basics section.
-  document.getElementById("application-box").textContent = Application.name;
-  document.getElementById("version-box").textContent = Application.version;
-  document.getElementById("supportLink").href = supportUrl;
-
-  // Update the other sections.
-  populateExtensionsSection();
-  populatePreferencesSection();
-}
-
-function populateExtensionsSection() {
-  let extensions = Application.extensions.all;
-  let trExtensions = [];
-  for (let i = 0; i < extensions.length; i++) {
-    let extension = extensions[i];
-    let tr = createParentElement("tr", [
-      createElement("td", extension.name),
-      createElement("td", extension.version),
-      createElement("td", extension.enabled),
-      createElement("td", extension.id),
-    ]);
-    trExtensions.push(tr);
-  }
-  appendChildren(document.getElementById("extensions-tbody"), trExtensions);
-}
-
-function populatePreferencesSection() {
-  let modifiedPrefs = getModifiedPrefs();
-
-  function comparePrefs(pref1, pref2) {
-    if (pref1.name < pref2.name)
-      return -1;
-    if (pref1.name > pref2.name)
-      return 1;
-    return 0;
-  }
-
-  let sortedPrefs = modifiedPrefs.sort(comparePrefs);
-
-  let trPrefs = [];
-  sortedPrefs.forEach(function (pref) {
-    let tdName = createElement("td", pref.name, "pref-name");
-    let tdValue = createElement("td", formatPrefValue(pref.value), "pref-value");
-    let tr = createParentElement("tr", [tdName, tdValue]);
-    trPrefs.push(tr);
-  });
-
-  appendChildren(document.getElementById("prefs-tbody"), trPrefs);
-}
-
-function formatPrefValue(prefValue) {
-  // Some pref values are really long and don't have spaces.  This can cause
-  // problems when copying and pasting into some WYSIWYG editors.  In general
-  // the exact contents of really long pref values aren't particularly useful,
-  // so we truncate them to some reasonable length. 
-  let maxPrefValueLen = 120;
-  let text = "" + prefValue;
-  if (text.length > maxPrefValueLen)
-    text = text.substring(0, maxPrefValueLen) + ELLIPSIS;
-  return text;
-}
-
-function getModifiedPrefs() {
-  // We use the low-level prefs API to identify prefs that have been
-  // modified, rather that Application.prefs.all since the latter is
-  // much, much slower.  Application.prefs.all also gets slower each
-  // time it's called.  See bug 517312.
-  let prefNames = getWhitelistedPrefNames();
-  let prefs = [Application.prefs.get(prefName)
-                      for each (prefName in prefNames)
-                          if (gPrefService.prefHasUserValue(prefName)
-                            && !isBlacklisted(prefName))];
-  return prefs;
-}
-
-function getWhitelistedPrefNames() {
-  let results = [];
-  PREFS_WHITELIST.forEach(function (prefStem) {
-    let prefNames = gPrefService.getChildList(prefStem);
-    results = results.concat(prefNames);
-  });
-  return results;
-}
-
-function isBlacklisted(prefName) {
-  return PREFS_BLACKLIST.some(function (re) re.test(prefName));
-}
-
-function createParentElement(tagName, childElems) {
-  let elem = document.createElement(tagName);
-  appendChildren(elem, childElems);
-  return elem;
-}
-
-function createElement(tagName, textContent, opt_class) {
-  let elem = document.createElement(tagName);
-  elem.textContent = textContent;
-  elem.className = opt_class || "";
-  return elem; 
-}
-
-function appendChildren(parentElem, childNodes) {
-  for (let i = 0; i < childNodes.length; i++)
-    parentElem.appendChild(childNodes[i]);
-}
-
-function copyContentsToClipboard() {
-  // Get the HTML and text representations for the important part of the page.
-  let contentsDiv = document.getElementById("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);  
-
-  // 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) {
-  // Generate the initial text.
-  let textFragmentAccumulator = [];
-  generateTextForElement(elem, "", textFragmentAccumulator);
-  let text = textFragmentAccumulator.join("");
-
-  // Trim extraneous whitespace before newlines, then squash extraneous
-  // blank lines.
-  text = text.replace(/[ \t]+\n/g, "\n");
-  text = text.replace(/\n\n\n+/g, "\n\n");
-  
-  // Actual CR/LF pairs are needed for some Windows text editors.
-#ifdef XP_WIN
-  text = text.replace(/\n/g, "\r\n");
-#endif
-
-  return text;
-}
-
-function generateTextForElement(elem, indent, textFragmentAccumulator) {
-  // Add a little extra spacing around most elements.
-  if (elem.tagName != "td")
-    textFragmentAccumulator.push("\n"); 
-
-  // Generate the text representation for each child node.
-  let node = elem.firstChild;
-  while (node) {
-
-    if (node.nodeType == Node.TEXT_NODE) {
-      // Text belonging to this element uses its indentation level.
-      generateTextForTextNode(node, indent, textFragmentAccumulator);
-    }
-    else if (node.nodeType == Node.ELEMENT_NODE) {
-      // Recurse on the child element with an extra level of indentation.
-      generateTextForElement(node, indent + "  ", textFragmentAccumulator); 
-    }
-
-    // Advance!
-    node = node.nextSibling;
-  }
-}
-
-function generateTextForTextNode(node, indent, textFragmentAccumulator) {
-  // If the text node is the first of a run of text nodes, then start
-  // a new line and add the initial indentation.
-  let prevNode = node.previousSibling;
-  if (!prevNode || prevNode.nodeType == Node.TEXT_NODE)
-    textFragmentAccumulator.push("\n" + indent);
-
-  // Trim the text node's text content and add proper indentation after 
-  // any internal line breaks.  
-  let text = node.textContent.trim().replace("\n", "\n" + indent, "g");
-  textFragmentAccumulator.push(text);
-}
-
-function openProfileDirectory() {
-  // Get the profile directory.
-  let propertiesService = Cc["@mozilla.org/file/directory_service;1"]
-                            .getService(Ci.nsIProperties);
-  let currProfD = propertiesService.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();
-}
-
-    ]]></script>
-
-  </head>
-
-  <body dir="&locale.dir;">
-
-    <h1>
-      &aboutSupport.pageTitle;
-    </h1>
-
-    <div class="page-subtitle">
-        &aboutSupport.pageSubtitle;
-    </div>
-
-    <div>
-      <button onclick="copyContentsToClipboard()">
-        &aboutSupport.copyToClipboard.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.appBasicsProfileDir;
-            </th>
-
-            <td>
-              <button onclick="openProfileDirectory()">
-#ifdef XP_MACOSX
-                &aboutSupport.showMac.label;
-#else
-                &aboutSupport.show.label;
-#endif
-               </button>
-            </td>
-          </tr>
-
-          <tr>
-            <th class="column">
-              &aboutSupport.appBasicsPlugins;
-            </th>
-
-            <td>
-              <a href="about:plugins">about:plugins</a>
-            </td>
-          </tr>
-            
-          <tr>
-            <th class="column">
-              &aboutSupport.appBasicsBuildConfig;
-            </th>
-
-            <td>
-              <a href="about:buildconfig">about:buildconfig</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.modifiedPrefsTitle;
-      </h2>
-
-      <table class="prefs-table">
-        <thead>
-          <th class="name">
-            &aboutSupport.modifiedPrefsName;
-          </th>
-          
-          <th class="value">
-            &aboutSupport.modifiedPrefsValue;
-          </th>
-        </thead>
-
-        <tbody id="prefs-tbody">
-        </tbody>
-      </table>
-
-      <!-- - - - - - - - - - - - - - - - - - - - - -->
-
-    </div>
-
-  </body>
-
-</html>
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -13,17 +13,16 @@ browser.jar:
 *       content/browser/aboutDialog.xul               (content/aboutDialog.xul)
 *       content/browser/aboutDialog.js                (content/aboutDialog.js)
         content/browser/aboutDialog.css               (content/aboutDialog.css)
 *       content/browser/aboutRobots.xhtml             (content/aboutRobots.xhtml)
         content/browser/aboutRobots-icon.png          (content/aboutRobots-icon.png)
         content/browser/aboutRobots-icon-rtl.png      (content/aboutRobots-icon-rtl.png)
         content/browser/aboutRobots-widget-left.png   (content/aboutRobots-widget-left.png)
         content/browser/aboutRobots-widget-right.png  (content/aboutRobots-widget-right.png)
-*       content/browser/aboutSupport.xhtml            (content/aboutSupport.xhtml)
 *       content/browser/browser.css                   (content/browser.css)
 *       content/browser/browser.js                    (content/browser.js)
 *       content/browser/browser.xul                   (content/browser.xul)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
 *       content/browser/credits.xhtml                 (content/credits.xhtml)
 *       content/browser/fullscreen-video.xhtml        (content/fullscreen-video.xhtml)
 *       content/browser/pageinfo/pageInfo.xul         (content/pageinfo/pageInfo.xul)
 *       content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -91,18 +91,16 @@ static RedirEntry kRedirMap[] = {
 #endif
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT },
   { "robots", "chrome://browser/content/aboutRobots.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT },
   { "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
-  { "support", "chrome://browser/content/aboutSupport.xhtml",
-    nsIAboutModule::ALLOW_SCRIPT },
 };
 static const int kRedirTotal = NS_ARRAY_LENGTH(kRedirMap);
 
 static nsCAutoString
 GetAboutModuleName(nsIURI *aURI)
 {
   nsCAutoString path;
   aURI->GetPath(path);
--- a/browser/components/build/nsModule.cpp
+++ b/browser/components/build/nsModule.cpp
@@ -184,21 +184,16 @@ static const nsModuleComponentInfo compo
     NS_ABOUT_MODULE_CONTRACTID_PREFIX "robots",
     AboutRedirector::Create },
 
   { "about:sessionrestore",
     NS_BROWSER_ABOUT_REDIRECTOR_CID,
     NS_ABOUT_MODULE_CONTRACTID_PREFIX "sessionrestore",
     AboutRedirector::Create },
 
-  { "about:support",
-    NS_BROWSER_ABOUT_REDIRECTOR_CID,
-    NS_ABOUT_MODULE_CONTRACTID_PREFIX "support",
-    AboutRedirector::Create },
-
 #ifndef WINCE
 
   { "Profile Migrator",
     NS_FIREFOX_PROFILEMIGRATOR_CID,
     NS_PROFILEMIGRATOR_CONTRACTID,
     nsProfileMigratorConstructor },
 
 #if defined(XP_WIN) && !defined(__MINGW32__)
deleted file mode 100644
--- a/browser/locales/en-US/chrome/browser/aboutSupport.dtd
+++ /dev/null
@@ -1,37 +0,0 @@
-<!ENTITY aboutSupport.pageTitle "Troubleshooting Information">
-
-<!-- LOCALIZATION NOTE (aboutSupport.pageSubtitle): don't change the 'supportLink' id. -->
-<!ENTITY aboutSupport.pageSubtitle "
-  This page contains technical information that might be useful when you're 
-  trying to solve a problem. If you are looking for answers to common questions
-  about &brandShortName;, check out our <a id='supportLink'>support web site</a>.
-">
-
-<!ENTITY aboutSupport.extensionsTitle "Extensions">
-<!ENTITY aboutSupport.extensionName "Name">
-<!ENTITY aboutSupport.extensionEnabled "Enabled">
-<!ENTITY aboutSupport.extensionVersion "Version">
-<!ENTITY aboutSupport.extensionId "ID">
-
-<!ENTITY aboutSupport.appBasicsTitle "Application Basics">
-<!ENTITY aboutSupport.appBasicsName "Name">
-<!ENTITY aboutSupport.appBasicsVersion "Version">
-<!ENTITY aboutSupport.appBasicsProfileDir "Profile Directory">
-<!ENTITY aboutSupport.appBasicsPlugins "Installed Plugins">
-<!ENTITY aboutSupport.appBasicsBuildConfig "Build Configuration">
-
-<!ENTITY aboutSupport.show.label "Open Containing Folder">
-
-<!-- LOCALIZATION NOTE (aboutSupport.showMac.label): This is the Mac-specific
-variant of aboutSupport.show.label.  This allows us to use the preferred
-"Finder" terminology on Mac. -->
-<!ENTITY aboutSupport.showMac.label "Show in Finder">
-
-<!ENTITY aboutSupport.modifiedPrefsTitle "Modified Preferences">
-<!ENTITY aboutSupport.modifiedPrefsName "Name">
-<!ENTITY aboutSupport.modifiedPrefsValue "Value">
-
-<!ENTITY aboutSupport.installationHistoryTitle "Installation History">
-<!ENTITY aboutSupport.updateHistoryTitle "Update History">
-
-<!ENTITY aboutSupport.copyToClipboard.label "Copy all to clipboard">
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -2,17 +2,16 @@
 
 @AB_CD@.jar:
 % locale browser @AB_CD@ %locale/browser/
     locale/browser/aboutCertError.dtd              (%chrome/browser/aboutCertError.dtd)
     locale/browser/aboutDialog.dtd                 (%chrome/browser/aboutDialog.dtd)
     locale/browser/aboutPrivateBrowsing.dtd        (%chrome/browser/aboutPrivateBrowsing.dtd)
     locale/browser/aboutRobots.dtd                 (%chrome/browser/aboutRobots.dtd)
     locale/browser/aboutSessionRestore.dtd         (%chrome/browser/aboutSessionRestore.dtd)
-    locale/browser/aboutSupport.dtd                (%chrome/browser/aboutSupport.dtd)
     locale/browser/credits.dtd                     (%chrome/browser/credits.dtd)
 *   locale/browser/browser.dtd                     (%chrome/browser/browser.dtd)
     locale/browser/baseMenuOverlay.dtd             (%chrome/browser/baseMenuOverlay.dtd)
     locale/browser/browser.properties              (%chrome/browser/browser.properties)
     locale/browser/openLocation.dtd                (%chrome/browser/openLocation.dtd)
     locale/browser/openLocation.properties         (%chrome/browser/openLocation.properties)
 *   locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
     locale/browser/pageInfo.properties             (%chrome/browser/pageInfo.properties)
--- a/docshell/base/nsAboutRedirector.cpp
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -82,16 +82,18 @@ static RedirEntry kRedirMap[] = {
       nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT },
     { "licence", "chrome://global/content/license.html",
       nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT },
     { "neterror", "chrome://global/content/netError.xhtml",
       nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
       nsIAboutModule::ALLOW_SCRIPT |
       nsIAboutModule::HIDE_FROM_ABOUTABOUT },
     { "memory", "chrome://global/content/aboutMemory.xhtml",
+      nsIAboutModule::ALLOW_SCRIPT },
+    { "support", "chrome://global/content/aboutSupport.xhtml",
       nsIAboutModule::ALLOW_SCRIPT }
 };
 static const int kRedirTotal = NS_ARRAY_LENGTH(kRedirMap);
 
 NS_IMETHODIMP
 nsAboutRedirector::NewChannel(nsIURI *aURI, nsIChannel **result)
 {
     NS_ENSURE_ARG_POINTER(aURI);
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -209,16 +209,21 @@ static const nsModuleComponentInfo gDocS
       NS_ABOUT_MODULE_CONTRACTID_PREFIX "neterror",
       nsAboutRedirector::Create
     },
     { "about:memory",
       NS_ABOUT_REDIRECTOR_MODULE_CID,
       NS_ABOUT_MODULE_CONTRACTID_PREFIX "memory",
       nsAboutRedirector::Create
     },
+    { "about:support",
+      NS_ABOUT_REDIRECTOR_MODULE_CID,
+      NS_ABOUT_MODULE_CONTRACTID_PREFIX "support",
+      nsAboutRedirector::Create
+    },
 
     // uriloader
   { "Netscape URI Loader Service", NS_URI_LOADER_CID, NS_URI_LOADER_CONTRACTID, nsURILoaderConstructor, },
   { "Netscape Doc Loader Service", NS_DOCUMENTLOADER_SERVICE_CID, NS_DOCUMENTLOADER_SERVICE_CONTRACTID, 
      nsDocLoaderConstructor, },
   { "Netscape External Helper App Service", NS_EXTERNALHELPERAPPSERVICE_CID, NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, 
      nsOSHelperAppServiceConstructor, },
   { "Netscape External Helper App Service", NS_EXTERNALHELPERAPPSERVICE_CID, NS_EXTERNALPROTOCOLSERVICE_CONTRACTID, 
new file mode 100644
--- /dev/null
+++ b/toolkit/content/aboutSupport.xhtml
@@ -0,0 +1,532 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is aboutSupport.xhtml.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation
+# Portions created by the Initial Developer are Copyright (C) 2009
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Curtis Bartley <cbartley@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+<!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;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>&aboutSupport.pageTitle;</title>
+
+    <style type="text/css"><![CDATA[
+
+html {
+  background-color: -moz-Field;
+  color: -moz-FieldText;
+  font: message-box;
+}
+
+body {
+  text-align: justify;
+  width: 90%;
+  margin-left: 5%;
+  margin-right: 5%;
+}
+
+.page-subtitle {
+  margin-bottom: 3em; 
+}
+
+.major-section {
+  margin-top: 2em;
+  margin-bottom: 1em;
+  font-size: large;
+  text-align: start;
+  font-weight: bold;
+}
+
+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: 1px dotted ThreeDShadow;
+  padding: 3px;
+}
+
+thead th {
+  text-align: center;
+}
+
+th {
+  text-align: start;
+  background-color: Highlight;
+  color: HighlightText;
+}
+
+th.column {
+  white-space: nowrap;
+  width: 0px; 
+}
+
+td {
+  text-align: start;
+  border-top: 1px dotted ThreeDShadow;
+}
+
+.prefs-table {
+  width: 100%;
+  table-layout: fixed;
+}
+
+.pref-name {
+  width: 70%;
+  white-space: nowrap;
+  overflow: hidden;
+}
+
+.pref-value {
+  width: 30%;
+  white-space: nowrap;
+  overflow: hidden;
+}
+
+    ]]></style>
+
+    <script type="application/javascript;version=1.7"><![CDATA[
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+let gPrefService = Cc["@mozilla.org/preferences-service;1"]
+                     .getService(Ci.nsIPrefService)
+                     .QueryInterface(Ci.nsIPrefBranch2);
+
+const ELLIPSIS = gPrefService.getComplexValue("intl.ellipsis",
+                                              Ci.nsIPrefLocalizedString).data;
+
+// We use a preferences whitelist to make sure we only show preferences that
+// are useful for support and won't compromise the user's privacy.  Note that
+// entries are *prefixes*: for example, "accessibility." applies to all prefs
+// under the "accessibility.*" branch.
+const PREFS_WHITELIST = [
+  "accessibility.",
+  "browser.fixup.",
+  "browser.history_expire_",
+  "browser.link.open_newwindow",
+  "browser.mousewheel.",
+  "browser.places.",
+  "browser.startup.homepage",
+  "browser.tabs.",
+  "browser.zoom.",
+  "dom.",
+  "extensions.checkCompatibility",
+  "extensions.lastAppVersion",
+  "font.",
+  "general.useragent.",
+  "gfx.color_management.mode",
+  "javascript.",
+  "keyword.", 
+  "layout.css.dpi",
+  "network.",
+  "places.",
+  "print.",
+  "privacy.",
+  "security."
+];
+
+// The blacklist, unlike the whitelist, is a list of regular expressions.
+const PREFS_BLACKLIST = [
+  /^network[.]proxy[.]/,
+  /[.]print_to_filename$/,
+];
+
+window.onload = function () {
+  // Get the support URL.
+  let urlFormatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"]
+                       .getService(Ci.nsIURLFormatter);
+  let supportUrl = urlFormatter.formatURLPref("app.support.baseURL");
+
+  // Update the application basics section.
+  document.getElementById("application-box").textContent = Application.name;
+  document.getElementById("version-box").textContent = Application.version;
+  document.getElementById("supportLink").href = supportUrl;
+
+  // Update the other sections.
+  populateExtensionsSection();
+  populatePreferencesSection();
+}
+
+function populateExtensionsSection() {
+  let extensions = Application.extensions.all;
+  let trExtensions = [];
+  for (let i = 0; i < extensions.length; i++) {
+    let extension = extensions[i];
+    let tr = createParentElement("tr", [
+      createElement("td", extension.name),
+      createElement("td", extension.version),
+      createElement("td", extension.enabled),
+      createElement("td", extension.id),
+    ]);
+    trExtensions.push(tr);
+  }
+  appendChildren(document.getElementById("extensions-tbody"), trExtensions);
+}
+
+function populatePreferencesSection() {
+  let modifiedPrefs = getModifiedPrefs();
+
+  function comparePrefs(pref1, pref2) {
+    if (pref1.name < pref2.name)
+      return -1;
+    if (pref1.name > pref2.name)
+      return 1;
+    return 0;
+  }
+
+  let sortedPrefs = modifiedPrefs.sort(comparePrefs);
+
+  let trPrefs = [];
+  sortedPrefs.forEach(function (pref) {
+    let tdName = createElement("td", pref.name, "pref-name");
+    let tdValue = createElement("td", formatPrefValue(pref.value), "pref-value");
+    let tr = createParentElement("tr", [tdName, tdValue]);
+    trPrefs.push(tr);
+  });
+
+  appendChildren(document.getElementById("prefs-tbody"), trPrefs);
+}
+
+function formatPrefValue(prefValue) {
+  // Some pref values are really long and don't have spaces.  This can cause
+  // problems when copying and pasting into some WYSIWYG editors.  In general
+  // the exact contents of really long pref values aren't particularly useful,
+  // so we truncate them to some reasonable length. 
+  let maxPrefValueLen = 120;
+  let text = "" + prefValue;
+  if (text.length > maxPrefValueLen)
+    text = text.substring(0, maxPrefValueLen) + ELLIPSIS;
+  return text;
+}
+
+function getModifiedPrefs() {
+  // We use the low-level prefs API to identify prefs that have been
+  // modified, rather that Application.prefs.all since the latter is
+  // much, much slower.  Application.prefs.all also gets slower each
+  // time it's called.  See bug 517312.
+  let prefNames = getWhitelistedPrefNames();
+  let prefs = [Application.prefs.get(prefName)
+                      for each (prefName in prefNames)
+                          if (gPrefService.prefHasUserValue(prefName)
+                            && !isBlacklisted(prefName))];
+  return prefs;
+}
+
+function getWhitelistedPrefNames() {
+  let results = [];
+  PREFS_WHITELIST.forEach(function (prefStem) {
+    let prefNames = gPrefService.getChildList(prefStem);
+    results = results.concat(prefNames);
+  });
+  return results;
+}
+
+function isBlacklisted(prefName) {
+  return PREFS_BLACKLIST.some(function (re) re.test(prefName));
+}
+
+function createParentElement(tagName, childElems) {
+  let elem = document.createElement(tagName);
+  appendChildren(elem, childElems);
+  return elem;
+}
+
+function createElement(tagName, textContent, opt_class) {
+  let elem = document.createElement(tagName);
+  elem.textContent = textContent;
+  elem.className = opt_class || "";
+  return elem; 
+}
+
+function appendChildren(parentElem, childNodes) {
+  for (let i = 0; i < childNodes.length; i++)
+    parentElem.appendChild(childNodes[i]);
+}
+
+function copyContentsToClipboard() {
+  // Get the HTML and text representations for the important part of the page.
+  let contentsDiv = document.getElementById("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);  
+
+  // 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) {
+  // Generate the initial text.
+  let textFragmentAccumulator = [];
+  generateTextForElement(elem, "", textFragmentAccumulator);
+  let text = textFragmentAccumulator.join("");
+
+  // Trim extraneous whitespace before newlines, then squash extraneous
+  // blank lines.
+  text = text.replace(/[ \t]+\n/g, "\n");
+  text = text.replace(/\n\n\n+/g, "\n\n");
+  
+  // Actual CR/LF pairs are needed for some Windows text editors.
+#ifdef XP_WIN
+  text = text.replace(/\n/g, "\r\n");
+#endif
+
+  return text;
+}
+
+function generateTextForElement(elem, indent, textFragmentAccumulator) {
+  // Add a little extra spacing around most elements.
+  if (elem.tagName != "td")
+    textFragmentAccumulator.push("\n"); 
+
+  // Generate the text representation for each child node.
+  let node = elem.firstChild;
+  while (node) {
+
+    if (node.nodeType == Node.TEXT_NODE) {
+      // Text belonging to this element uses its indentation level.
+      generateTextForTextNode(node, indent, textFragmentAccumulator);
+    }
+    else if (node.nodeType == Node.ELEMENT_NODE) {
+      // Recurse on the child element with an extra level of indentation.
+      generateTextForElement(node, indent + "  ", textFragmentAccumulator); 
+    }
+
+    // Advance!
+    node = node.nextSibling;
+  }
+}
+
+function generateTextForTextNode(node, indent, textFragmentAccumulator) {
+  // If the text node is the first of a run of text nodes, then start
+  // a new line and add the initial indentation.
+  let prevNode = node.previousSibling;
+  if (!prevNode || prevNode.nodeType == Node.TEXT_NODE)
+    textFragmentAccumulator.push("\n" + indent);
+
+  // Trim the text node's text content and add proper indentation after 
+  // any internal line breaks.  
+  let text = node.textContent.trim().replace("\n", "\n" + indent, "g");
+  textFragmentAccumulator.push(text);
+}
+
+function openProfileDirectory() {
+  // Get the profile directory.
+  let propertiesService = Cc["@mozilla.org/file/directory_service;1"]
+                            .getService(Ci.nsIProperties);
+  let currProfD = propertiesService.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();
+}
+
+    ]]></script>
+
+  </head>
+
+  <body dir="&locale.dir;">
+
+    <h1>
+      &aboutSupport.pageTitle;
+    </h1>
+
+    <div class="page-subtitle">
+        &aboutSupport.pageSubtitle;
+    </div>
+
+    <div>
+      <button onclick="copyContentsToClipboard()">
+        &aboutSupport.copyToClipboard.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.appBasicsProfileDir;
+            </th>
+
+            <td>
+              <button onclick="openProfileDirectory()">
+#ifdef XP_MACOSX
+                &aboutSupport.showMac.label;
+#else
+                &aboutSupport.show.label;
+#endif
+               </button>
+            </td>
+          </tr>
+
+          <tr>
+            <th class="column">
+              &aboutSupport.appBasicsPlugins;
+            </th>
+
+            <td>
+              <a href="about:plugins">about:plugins</a>
+            </td>
+          </tr>
+            
+          <tr>
+            <th class="column">
+              &aboutSupport.appBasicsBuildConfig;
+            </th>
+
+            <td>
+              <a href="about:buildconfig">about:buildconfig</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.modifiedPrefsTitle;
+      </h2>
+
+      <table class="prefs-table">
+        <thead>
+          <th class="name">
+            &aboutSupport.modifiedPrefsName;
+          </th>
+          
+          <th class="value">
+            &aboutSupport.modifiedPrefsValue;
+          </th>
+        </thead>
+
+        <tbody id="prefs-tbody">
+        </tbody>
+      </table>
+
+      <!-- - - - - - - - - - - - - - - - - - - - - -->
+
+    </div>
+
+  </body>
+
+</html>
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -9,16 +9,17 @@ toolkit.jar:
    content/global/XPCNativeWrapper.js         (XPCNativeWrapper.js)
 *  content/global/xul.css                     (xul.css)
    content/global/textbox.css                 (textbox.css)
    content/global/menulist.css                (menulist.css)
 *  content/global/about.xhtml                 (about.xhtml)
 *  content/global/aboutAbout.xhtml            (aboutAbout.xhtml)
 *  content/global/aboutRights.xhtml           (aboutRights.xhtml)
 *  content/global/aboutRights-unbranded.xhtml (aboutRights-unbranded.xhtml)
+*  content/global/aboutSupport.xhtml
    content/global/directionDetector.html
    content/global/plugins.html
    content/global/plugins.css
 *+  content/global/buildconfig.html            (buildconfig.html)
 +  content/global/charsetOverlay.js           (charsetOverlay.js)
 +  content/global/charsetOverlay.xul          (charsetOverlay.xul)
 *+ content/global/commonDialog.js             (commonDialog.js)
 *+ content/global/commonDialog.xul            (commonDialog.xul)
new file mode 100644
--- /dev/null
+++ b/toolkit/locales/en-US/chrome/global/aboutSupport.dtd
@@ -0,0 +1,37 @@
+<!ENTITY aboutSupport.pageTitle "Troubleshooting Information">
+
+<!-- LOCALIZATION NOTE (aboutSupport.pageSubtitle): don't change the 'supportLink' id. -->
+<!ENTITY aboutSupport.pageSubtitle "
+  This page contains technical information that might be useful when you're
+  trying to solve a problem. If you are looking for answers to common questions
+  about &brandShortName;, check out our <a id='supportLink'>support web site</a>.
+">
+
+<!ENTITY aboutSupport.extensionsTitle "Extensions">
+<!ENTITY aboutSupport.extensionName "Name">
+<!ENTITY aboutSupport.extensionEnabled "Enabled">
+<!ENTITY aboutSupport.extensionVersion "Version">
+<!ENTITY aboutSupport.extensionId "ID">
+
+<!ENTITY aboutSupport.appBasicsTitle "Application Basics">
+<!ENTITY aboutSupport.appBasicsName "Name">
+<!ENTITY aboutSupport.appBasicsVersion "Version">
+<!ENTITY aboutSupport.appBasicsProfileDir "Profile Directory">
+<!ENTITY aboutSupport.appBasicsPlugins "Installed Plugins">
+<!ENTITY aboutSupport.appBasicsBuildConfig "Build Configuration">
+
+<!ENTITY aboutSupport.show.label "Open Containing Folder">
+
+<!-- LOCALIZATION NOTE (aboutSupport.showMac.label): This is the Mac-specific
+variant of aboutSupport.show.label.  This allows us to use the preferred
+"Finder" terminology on Mac. -->
+<!ENTITY aboutSupport.showMac.label "Show in Finder">
+
+<!ENTITY aboutSupport.modifiedPrefsTitle "Modified Preferences">
+<!ENTITY aboutSupport.modifiedPrefsName "Name">
+<!ENTITY aboutSupport.modifiedPrefsValue "Value">
+
+<!ENTITY aboutSupport.installationHistoryTitle "Installation History">
+<!ENTITY aboutSupport.updateHistoryTitle "Update History">
+
+<!ENTITY aboutSupport.copyToClipboard.label "Copy all to clipboard">
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -1,16 +1,17 @@
 #filter substitution
 
 @AB_CD@.jar:
 % locale global @AB_CD@ %locale/@AB_CD@/global/
   locale/@AB_CD@/global/about.dtd                       (%chrome/global/about.dtd)
   locale/@AB_CD@/global/aboutAbout.dtd                  (%chrome/global/aboutAbout.dtd)
   locale/@AB_CD@/global/aboutRights.dtd                 (%chrome/global/aboutRights.dtd)
   locale/@AB_CD@/global/aboutRights.properties          (%chrome/global/aboutRights.properties)
+  locale/@AB_CD@/global/aboutSupport.dtd                (%chrome/global/aboutSupport.dtd)
   locale/@AB_CD@/global/actions.dtd                     (%chrome/global/actions.dtd)
   locale/@AB_CD@/global/appPicker.dtd                   (%chrome/global/appPicker.dtd)
   locale/@AB_CD@/global/brand.dtd                       (generic/chrome/global/brand.dtd)
 + locale/@AB_CD@/global/browser.properties              (%chrome/global/browser.properties)
 + locale/@AB_CD@/global/charsetOverlay.dtd              (%chrome/global/charsetOverlay.dtd)
 + locale/@AB_CD@/global/commonDialog.dtd                (%chrome/global/commonDialog.dtd)
 + locale/@AB_CD@/global/commonDialogs.properties        (%chrome/global/commonDialogs.properties)
 + locale/@AB_CD@/global/config.dtd                      (%chrome/global/config.dtd)