merge projects/devtools to mozilla-central
authorRob Campbell <rcampbell@mozilla.com>
Mon, 09 May 2011 13:42:21 -0300
changeset 69443 6e68a1d9538beb26ff3f7f543751fb30f60d9f19
parent 69402 af5c044d29aca671b29703a585536047009b31d6 (current diff)
parent 69442 c9cf95f38c54e7ea3d8ca3d44a7c26097de2d326 (diff)
child 69444 437f175609b8712a7dcb3fe6831f225a2ee565fa
push id76
push userbzbarsky@mozilla.com
push dateTue, 05 Jul 2011 17:00:57 +0000
treeherdermozilla-beta@d3a2732c35f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone6.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge projects/devtools to mozilla-central
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -987,16 +987,22 @@ pref("services.sync.prefs.sync.signon.re
 pref("services.sync.prefs.sync.spellchecker.dictionary", true);
 pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
 #endif
 
 // Disable the error console and inspector
 pref("devtools.errorconsole.enabled", false);
 pref("devtools.inspector.enabled", false);
 
+// Enable the Scratchpad tool.
+pref("devtools.scratchpad.enabled", true);
+
+// Enable tools for Chrome development.
+pref("devtools.chrome.enabled", false);
+
 // The last Web Console height. This is initially 0 which means that the Web
 // Console will use the default height next time it shows.
 // Change to -1 if you do not want the Web Console to remember its last height.
 pref("devtools.hud.height", 0);
 
 // Whether the character encoding menu is under the main Firefox button. This
 // preference is a string so that localizers can alter it.
 pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");
--- a/browser/base/content/browser-appmenu.inc
+++ b/browser/base/content/browser-appmenu.inc
@@ -182,16 +182,21 @@
                     oncommand="HUDConsoleUI.toggleHUD();"
                     key="key_webConsole"/>
           <menuitem id="appmenu_pageInspect"
                     hidden="true"
                     label="&inspectMenu.label;"
                     type="checkbox"
                     command="Tools:Inspect"
                     key="key_inspect"/>
+          <menuitem id="appmenu_scratchpad"
+                    hidden="true"
+                    label="&scratchpad.label;"
+                    key="key_scratchpad"
+                    command="Tools:Scratchpad"/>
           <menuitem id="appmenu_pageSource"
                     label="&viewPageSourceCmd.label;"
                     command="View:PageSource"
                     key="key_viewSource"/>
           <menuitem id="appmenu_errorConsole"
                     hidden="true"
                     label="&errorConsoleCmd.label;"
                     key="key_errorConsole"
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -310,21 +310,16 @@
                               accesskey="&pageStylePersistentOnly.accesskey;"
                               type="radio"
                               checked="true"/>
                     <menuseparator/>
                   </menupopup>
                 </menu>
 #include browser-charsetmenu.inc
                 <menuseparator/>
-                <menuitem id="menu_pageSource"
-                          accesskey="&pageSourceCmd.accesskey;"
-                          label="&pageSourceCmd.label;"
-                          key="key_viewSource"
-                          command="View:PageSource"/>
                 <menuitem id="fullScreenItem"
                           accesskey="&fullScreenCmd.accesskey;"
                           label="&fullScreenCmd.label;"
                           key="key_fullScreen"
                           type="checkbox"
                           observes="View:FullScreen"/>
                 <menuitem id="menu_showAllTabs"
                           hidden="true"
@@ -534,34 +529,51 @@
                         oncommand="gSyncUI.openSetup()"/>
               <menuitem id="sync-syncnowitem"
                         label="&syncSyncNowItem.label;"
                         accesskey="&syncSyncNowItem.accesskey;"
                         observes="sync-syncnow-state"
                         oncommand="gSyncUI.doSync(event);"/>
 #endif
               <menuseparator id="devToolsSeparator"/>
-              <menuitem id="menu_pageinspect"
-                        type="checkbox"
-                        hidden="true"
-                        label="&inspectMenu.label;"
-                        accesskey="&inspectMenu.accesskey;"
-                        key="key_inspect"
-                        command="Tools:Inspect"/>
-              <menuitem id="javascriptConsole"
-                        hidden="true"
-                        label="&errorConsoleCmd.label;"
-                        accesskey="&errorConsoleCmd.accesskey;"
-                        key="key_errorConsole"
-                        oncommand="toJavaScriptConsole();"/>
-              <menuitem id="webConsole"
-                        label="&webConsoleCmd.label;"
-                        accesskey="&webConsoleCmd.accesskey;"
-                        key="key_webConsole"
-                        oncommand="HUDConsoleUI.toggleHUD();"/>
+              <menu id="webDeveloperMenu"
+                    label="&webDeveloperMenu.label;"
+                    accesskey="webDeveloperMenu.accesskey;">
+                <menupopup id="menuWebDeveloperPopup">
+                  <menuitem id="webConsole"
+                            label="&webConsoleCmd.label;"
+                            accesskey="&webConsoleCmd.accesskey;"
+                            key="key_webConsole"
+                            oncommand="HUDConsoleUI.toggleHUD();"/>
+                  <menuitem id="menu_pageinspect"
+                            type="checkbox"
+                            hidden="true"
+                            label="&inspectMenu.label;"
+                            accesskey="&inspectMenu.accesskey;"
+                            key="key_inspect"
+                            command="Tools:Inspect"/>
+                  <menuitem id="menu_scratchpad"
+                            hidden="true"
+                            label="&scratchpad.label;"
+                            accesskey="&scratchpad.accesskey;"
+                            key="key_scratchpad"
+                            command="Tools:Scratchpad"/>
+                  <menuitem id="menu_pageSource"
+                            accesskey="&pageSourceCmd.accesskey;"
+                            label="&pageSourceCmd.label;"
+                            key="key_viewSource"
+                            command="View:PageSource"/>
+                  <menuitem id="javascriptConsole"
+                            hidden="true"
+                            label="&errorConsoleCmd.label;"
+                            accesskey="&errorConsoleCmd.accesskey;"
+                            key="key_errorConsole"
+                            oncommand="toJavaScriptConsole();"/>
+                </menupopup>
+              </menu>
               <menuitem id="menu_pageInfo"
                         accesskey="&pageInfoCmd.accesskey;"
                         label="&pageInfoCmd.label;"
 #ifndef XP_WIN
                         key="key_viewInfo"
 #endif
                         command="View:PageInfo"/>
               <menuseparator id="sanitizeSeparator"/>
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -120,16 +120,17 @@
     <command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/>
     <command id="cmd_fullZoomReset"   oncommand="FullZoom.reset()"/>
     <command id="cmd_fullZoomToggle"  oncommand="ZoomManager.toggleZoom();"/>
     <command id="Browser:OpenLocation" oncommand="openLocation();"/>
 
     <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
     <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
     <command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();" disabled="true"/>
+    <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true"/>
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
     <command id="Tools:Sanitize"
      oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
     <command id="Tools:PrivateBrowsing" oncommand="gPrivateBrowsingUI.toggleMode();"/>
     <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
     <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
     <command id="Browser:ToggleAddonBar" oncommand="toggleAddonBar();"/>
   </commandset>
@@ -236,16 +237,18 @@
     <key id="key_openDownloads" key="&downloadsUnix.commandkey;" command="Tools:Downloads" modifiers="accel,shift"/>
 #else
     <key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/>
 #endif
     <key id="key_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/>
     <key id="key_errorConsole" key="&errorConsoleCmd.commandkey;" oncommand="toJavaScriptConsole();" modifiers="accel,shift" disabled="true"/>
     <key id="key_webConsole" key="&webConsoleCmd.commandkey;" oncommand="HUDConsoleUI.toggleHUD();" modifiers="accel,shift"/>
     <key id="key_inspect" key="&inspectMenu.commandkey;" command="Tools:Inspect" modifiers="accel,shift"/>
+    <key id="key_scratchpad" keycode="&scratchpad.keycode;"
+         keytext="&scratchpad.keytext;" command="Tools:Scratchpad"/>
     <key id="openFileKb" key="&openFileCmd.commandkey;" command="Browser:OpenFile"  modifiers="accel"/>
     <key id="key_savePage" key="&savePageCmd.commandkey;" command="Browser:SavePage" modifiers="accel"/>
     <key id="printKb" key="&printCmd.commandkey;" command="cmd_print"  modifiers="accel"/>
     <key id="key_close" key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
     <key id="key_closeWindow" key="&closeCmd.key;" command="cmd_closeWindow" modifiers="accel,shift"/>
     <key id="key_undo"
          key="&undoCmd.key;"
          modifiers="accel"/>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -49,16 +49,17 @@
 #   Nils Maier <maierman@web.de>
 #   Rob Arnold <robarnold@cmu.edu>
 #   Dietrich Ayala <dietrich@mozilla.com>
 #   Gavin Sharp <gavin@gavinsharp.com>
 #   Justin Dolske <dolske@mozilla.com>
 #   Rob Campbell <rcampbell@mozilla.com>
 #   David Dahl <ddahl@mozilla.com>
 #   Patrick Walton <pcwalton@mozilla.com>
+#   Mihai Sucan <mihai.sucan@gmail.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
@@ -1648,16 +1649,26 @@ function delayedStartup(isLoadingBlank, 
   if (consoleEnabled) {
     document.getElementById("javascriptConsole").hidden = false;
     document.getElementById("key_errorConsole").removeAttribute("disabled");
 #ifdef MENUBAR_CAN_AUTOHIDE
     document.getElementById("appmenu_errorConsole").hidden = false;
 #endif
   }
 
+  // Enable Scratchpad in the UI, if the preference allows this.
+  let scratchpadEnabled = gPrefService.getBoolPref(Scratchpad.prefEnabledName);
+  if (scratchpadEnabled) {
+    document.getElementById("menu_scratchpad").hidden = false;
+    document.getElementById("Tools:Scratchpad").removeAttribute("disabled");
+#ifdef MENUBAR_CAN_AUTOHIDE
+    document.getElementById("appmenu_scratchpad").hidden = false;
+#endif
+  }
+
 #ifdef MENUBAR_CAN_AUTOHIDE
   // If the user (or the locale) hasn't enabled the top-level "Character
   // Encoding" menu via the "browser.menu.showCharacterEncoding" preference,
   // hide it.
   if ("true" != gPrefService.getComplexValue("browser.menu.showCharacterEncoding",
                                              Ci.nsIPrefLocalizedString).data)
     document.getElementById("appmenu_charsetMenu").hidden = true;
 #endif
@@ -8626,16 +8637,29 @@ let AddonsMgrListener = {
   onDisabled: function(aAddon) this.onUninstalled(),
 };
 
 function toggleAddonBar() {
   let addonBar = document.getElementById("addon-bar");
   setToolbarVisibility(addonBar, addonBar.collapsed);
 }
 
+var Scratchpad = {
+  prefEnabledName: "devtools.scratchpad.enabled",
+
+  openScratchpad: function SP_openScratchpad() {
+    const SCRATCHPAD_WINDOW_URL = "chrome://browser/content/scratchpad.xul";
+    const SCRATCHPAD_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+    return Services.ww.openWindow(null, SCRATCHPAD_WINDOW_URL, "_blank",
+                                  SCRATCHPAD_WINDOW_FEATURES, null);
+  },
+};
+
+
 XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () {
 #ifdef XP_WIN
   // Only show resizers on Windows 2000 and XP
   let sysInfo = Components.classes["@mozilla.org/system-info;1"]
                           .getService(Components.interfaces.nsIPropertyBag2);
   return parseFloat(sysInfo.getProperty("version")) < 6;
 #else
   return false;
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -235,17 +235,17 @@
     <panel id="inspector-tree-panel"
            orient="vertical"
            hidden="true"
            ignorekeys="true"
            noautofocus="true"
            noautohide="true"
            titlebar="normal"
            close="true"
-           onpopuphiding="InspectorUI.closeInspectorUI(true);"
+           onpopuphiding="InspectorUI.closeInspectorUI();"
            label="&inspectPanelTitle.label;">
       <toolbar id="inspector-toolbar"
                nowindowdrag="true">
         <toolbarbutton id="inspector-inspect-toolbutton"
                        label="&inspectButton.label;"
                        accesskey="&inspectButton.accesskey;"
                        class="toolbarbutton-text"
                        command="Inspector:Inspect"/>
--- a/browser/base/content/inspector.js
+++ b/browser/base/content/inspector.js
@@ -342,17 +342,17 @@ var InspectorUI = {
    * Toggle the inspector interface elements on or off.
    *
    * @param aEvent
    *        The event that requested the UI change. Toolbar button or menu.
    */
   toggleInspectorUI: function IUI_toggleInspectorUI(aEvent)
   {
     if (this.isTreePanelOpen) {
-      this.closeInspectorUI(true);
+      this.closeInspectorUI();
     } else {
       this.openInspectorUI();
     }
   },
 
   /**
    * Toggle the status of the inspector, starting or stopping it. Invoked
    * from the toolbar's Inspect button.
@@ -733,28 +733,30 @@ var InspectorUI = {
     }
   },
 
   /**
    * Close inspector UI and associated panels. Unhighlight and stop inspecting.
    * Remove event listeners for document scrolling, resize,
    * tabContainer.TabSelect and others.
    *
-   * @param boolean aClearStore tells if you want the store associated to the
-   * current tab/window to be cleared or not.
+   * @param boolean aKeepStore
+   *        Tells if you want the store associated to the current tab/window to
+   *        be cleared or not. Set this to true to not clear the store, or false
+   *        otherwise.
    */
-  closeInspectorUI: function IUI_closeInspectorUI(aClearStore)
+  closeInspectorUI: function IUI_closeInspectorUI(aKeepStore)
   {
     if (this.closing || !this.win || !this.browser) {
       return;
     }
 
     this.closing = true;
 
-    if (aClearStore) {
+    if (!aKeepStore) {
       InspectorStore.deleteStore(this.winID);
       this.win.removeEventListener("pagehide", this, true);
     } else {
       // Update the store before closing.
       if (this.selection) {
         InspectorStore.setValue(this.winID, "selectedNode",
           this.selection);
       }
@@ -1023,17 +1025,17 @@ var InspectorUI = {
           if (this.isTreePanelOpen && this.isStylePanelOpen &&
               this.isDOMPanelOpen && this.treeLoaded) {
             this.notifyReady();
           }
         break;
       case "TabSelect":
         winID = this.getWindowID(gBrowser.selectedBrowser.contentWindow);
         if (this.isTreePanelOpen && winID != this.winID) {
-          this.closeInspectorUI(false);
+          this.closeInspectorUI(true);
           inspectorClosed = true;
         }
 
         if (winID && InspectorStore.hasID(winID)) {
           if (inspectorClosed && this.closing) {
             Services.obs.addObserver(function () {
               InspectorUI.openInspectorUI();
             }, "inspector-closed", false);
new file mode 100644
--- /dev/null
+++ b/browser/base/content/scratchpad.js
@@ -0,0 +1,627 @@
+/* vim:set ts=2 sw=2 sts=2 et:
+ * ***** 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 Scratchpad.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Rob Campbell <robcee@mozilla.com> (original author)
+ *   Erik Vold <erikvvold@gmail.com>
+ *   David Dahl <ddahl@mozilla.com>
+ *   Mihai Sucan <mihai.sucan@gmail.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 *****/
+
+/*
+ * Original version history can be found here:
+ * https://github.com/mozilla/workspace
+ *
+ * Copied and relicensed from the Public Domain.
+ * See bug 653934 for details.
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=653934
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource:///modules/PropertyPanel.jsm");
+
+const SCRATCHPAD_CONTEXT_CONTENT = 1;
+const SCRATCHPAD_CONTEXT_CHROME = 2;
+const SCRATCHPAD_WINDOW_URL = "chrome://browser/content/scratchpad.xul";
+const SCRATCHPAD_L10N = "chrome://browser/locale/scratchpad.properties";
+const SCRATCHPAD_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
+
+/**
+ * The scratchpad object handles the Scratchpad window functionality.
+ */
+var Scratchpad = {
+  /**
+   * The script execution context. This tells Scratchpad in which context the
+   * script shall execute.
+   *
+   * Possible values:
+   *   - SCRATCHPAD_CONTEXT_CONTENT to execute code in the context of the current
+   *   tab content window object.
+   *   - SCRATCHPAD_CONTEXT_CHROME to execute code in the context of the
+   *   currently active chrome window object.
+   */
+  executionContext: SCRATCHPAD_CONTEXT_CONTENT,
+
+  /**
+   * Retrieve the xul:textbox DOM element. This element holds the source code
+   * the user writes and executes.
+   */
+  get textbox() document.getElementById("scratchpad-textbox"),
+
+  /**
+   * Retrieve the xul:statusbarpanel DOM element. The status bar tells the
+   * current code execution context.
+   */
+  get statusbarStatus() document.getElementById("scratchpad-status"),
+
+  /**
+   * Get the selected text from the textbox.
+   */
+  get selectedText()
+  {
+    return this.textbox.value.substring(this.textbox.selectionStart,
+                                        this.textbox.selectionEnd);
+  },
+
+  /**
+   * Get the most recent chrome window of type navigator:browser.
+   */
+  get browserWindow() Services.wm.getMostRecentWindow("navigator:browser"),
+
+  /**
+   * Reference to the last chrome window of type navigator:browser. We use this
+   * to check if the chrome window changed since the last code evaluation.
+   */
+  _previousWindow: null,
+
+  /**
+   * Get the gBrowser object of the most recent browser window.
+   */
+  get gBrowser()
+  {
+    let recentWin = this.browserWindow;
+    return recentWin ? recentWin.gBrowser : null;
+  },
+
+  /**
+   * Cached Cu.Sandbox object for the active tab content window object.
+   */
+  _contentSandbox: null,
+
+  /**
+   * Get the Cu.Sandbox object for the active tab content window object. Note
+   * that the returned object is cached for later reuse. The cached object is
+   * kept only for the current browser window and it is reset for each context
+   * switch or navigator:browser window switch.
+   */
+  get contentSandbox()
+  {
+    if (!this.browserWindow) {
+      Cu.reportError(this.strings.
+                     GetStringFromName("browserWindow.unavailable"));
+      return;
+    }
+
+    if (!this._contentSandbox ||
+        this.browserWindow != this._previousBrowserWindow) {
+      let contentWindow = this.gBrowser.selectedBrowser.contentWindow;
+      this._contentSandbox = new Cu.Sandbox(contentWindow,
+        { sandboxPrototype: contentWindow, wantXrays: false });
+
+      this._previousBrowserWindow = this.browserWindow;
+    }
+
+    return this._contentSandbox;
+  },
+
+  /**
+   * Cached Cu.Sandbox object for the most recently active navigator:browser
+   * chrome window object.
+   */
+  _chromeSandbox: null,
+
+  /**
+   * Get the Cu.Sandbox object for the most recently active navigator:browser
+   * chrome window object. Note that the returned object is cached for later
+   * reuse. The cached object is kept only for the current browser window and it
+   * is reset for each context switch or navigator:browser window switch.
+   */
+  get chromeSandbox()
+  {
+    if (!this.browserWindow) {
+      Cu.reportError(this.strings.
+                     GetStringFromName("browserWindow.unavailable"));
+      return;
+    }
+
+    if (!this._chromeSandbox ||
+        this.browserWindow != this._previousBrowserWindow) {
+      this._chromeSandbox = new Cu.Sandbox(this.browserWindow,
+        { sandboxPrototype: this.browserWindow, wantXrays: false });
+
+      this._previousBrowserWindow = this.browserWindow;
+    }
+
+    return this._chromeSandbox;
+  },
+
+  /**
+   * Drop the textbox selection.
+   */
+  deselect: function SP_deselect()
+  {
+    this.textbox.selectionEnd = this.textbox.selectionStart;
+  },
+
+  /**
+   * Select a specific range in the Scratchpad xul:textbox.
+   *
+   * @param number aStart
+   *        Selection range start.
+   * @param number aEnd
+   *        Selection range end.
+   */
+  selectRange: function SP_selectRange(aStart, aEnd)
+  {
+    this.textbox.selectionStart = aStart;
+    this.textbox.selectionEnd = aEnd;
+  },
+
+  /**
+   * Evaluate a string in the active tab content window.
+   *
+   * @param string aString
+   *        The script you want evaluated.
+   * @return mixed
+   *         The script evaluation result.
+   */
+  evalInContentSandbox: function SP_evalInContentSandbox(aString)
+  {
+    let result;
+    try {
+      result = Cu.evalInSandbox(aString, this.contentSandbox, "1.8",
+                                "Scratchpad", 1);
+    }
+    catch (ex) {
+      this.openWebConsole();
+
+      let contentWindow = this.gBrowser.selectedBrowser.contentWindow;
+
+      let scriptError = Cc["@mozilla.org/scripterror;1"].
+                        createInstance(Ci.nsIScriptError2);
+
+      scriptError.initWithWindowID(ex.message + "\n" + ex.stack, ex.fileName,
+                                   "", ex.lineNumber, 0, scriptError.errorFlag,
+                                   "content javascript",
+                                   this.getWindowId(contentWindow));
+
+      Services.console.logMessage(scriptError);
+    }
+
+    return result;
+  },
+
+  /**
+   * Evaluate a string in the most recent navigator:browser chrome window.
+   *
+   * @param string aString
+   *        The script you want evaluated.
+   * @return mixed
+   *         The script evaluation result.
+   */
+  evalInChromeSandbox: function SP_evalInChromeSandbox(aString)
+  {
+    let result;
+    try {
+      result = Cu.evalInSandbox(aString, this.chromeSandbox, "1.8",
+                                "Scratchpad", 1);
+    }
+    catch (ex) {
+      Cu.reportError(ex);
+      Cu.reportError(ex.stack);
+      this.openErrorConsole();
+    }
+
+    return result;
+  },
+
+  /**
+   * Evaluate a string in the currently desired context, that is either the
+   * chrome window or the tab content window object.
+   *
+   * @param string aString
+   *        The script you want to evaluate.
+   * @return mixed
+   *         The script evaluation result.
+   */
+  evalForContext: function SP_evaluateForContext(aString)
+  {
+    return this.executionContext == SCRATCHPAD_CONTEXT_CONTENT ?
+           this.evalInContentSandbox(aString) :
+           this.evalInChromeSandbox(aString);
+  },
+
+  /**
+   * Execute the selected text (if any) or the entire textbox content in the
+   * current context.
+   */
+  execute: function SP_execute()
+  {
+    let selection = this.selectedText || this.textbox.value;
+    let result = this.evalForContext(selection);
+    this.deselect();
+    return [selection, result];
+  },
+
+  /**
+   * Execute the selected text (if any) or the entire textbox content in the
+   * current context. The resulting object is opened up in the Property Panel
+   * for inspection.
+   */
+  inspect: function SP_inspect()
+  {
+    let [selection, result] = this.execute();
+
+    if (result) {
+      this.openPropertyPanel(selection, result);
+    }
+  },
+
+  /**
+   * Execute the selected text (if any) or the entire textbox content in the
+   * current context. The evaluation result is "printed" in the textbox after
+   * the selected text, or at the end of the textbox value if there is no
+   * selected text.
+   */
+  print: function SP_print()
+  {
+    let selectionStart = this.textbox.selectionStart;
+    let selectionEnd = this.textbox.selectionEnd;
+    if (selectionStart == selectionEnd) {
+      selectionEnd = this.textbox.value.length;
+    }
+
+    let [selection, result] = this.execute();
+    if (!result) {
+      return;
+    }
+
+    let firstPiece = this.textbox.value.slice(0, selectionEnd);
+    let lastPiece = this.textbox.value.
+                    slice(selectionEnd, this.textbox.value.length);
+
+    let newComment = "/*\n" + result.toString() + "\n*/";
+
+    this.textbox.value = firstPiece + newComment + lastPiece;
+
+    // Select the added comment.
+    this.selectRange(firstPiece.length, firstPiece.length + newComment.length);
+  },
+
+  /**
+   * Open the Property Panel to inspect the given object.
+   *
+   * @param string aEvalString
+   *        The string that was evaluated. This is re-used when the user updates
+   *        the properties list, by clicking the Update button.
+   * @param object aOutputObject
+   *        The object to inspect, which is the aEvalString evaluation result.
+   * @return object
+   *         The PropertyPanel object instance.
+   */
+  openPropertyPanel: function SP_openPropertyPanel(aEvalString, aOutputObject)
+  {
+    let self = this;
+    let propPanel;
+    // The property panel has a button:
+    // `Update`: reexecutes the string executed on the command line. The
+    // result will be inspected by this panel.
+    let buttons = [];
+
+    // If there is a evalString passed to this function, then add a `Update`
+    // button to the panel so that the evalString can be reexecuted to update
+    // the content of the panel.
+    if (aEvalString !== null) {
+      buttons.push({
+        label: this.strings.
+               GetStringFromName("propertyPanel.updateButton.label"),
+        accesskey: this.strings.
+                   GetStringFromName("propertyPanel.updateButton.accesskey"),
+        oncommand: function () {
+          try {
+            let result = self.evalForContext(aEvalString);
+
+            if (result !== undefined) {
+              propPanel.treeView.data = result;
+            }
+          }
+          catch (ex) { }
+        }
+      });
+    }
+
+    let doc = this.browserWindow.document;
+    let parent = doc.getElementById("mainPopupSet");
+    let title = aOutputObject.toString();
+    propPanel = new PropertyPanel(parent, doc, title, aOutputObject, buttons);
+
+    let panel = propPanel.panel;
+    panel.setAttribute("class", "scratchpad_propertyPanel");
+    panel.openPopup(null, "after_pointer", 0, 0, false, false);
+    panel.sizeTo(200, 400);
+
+    return propPanel;
+  },
+
+  // Menu Operations
+
+  /**
+   * Open a new Scratchpad window.
+   */
+  openScratchpad: function SP_openScratchpad()
+  {
+    Services.ww.openWindow(null, SCRATCHPAD_WINDOW_URL, "_blank",
+                           SCRATCHPAD_WINDOW_FEATURES, null);
+  },
+
+  /**
+   * Export the textbox content to a file.
+   *
+   * @param nsILocalFile aFile
+   *        The file where you want to save the textbox content.
+   * @param boolean aNoConfirmation
+   *        If the file already exists, ask for confirmation?
+   * @param boolean aSilentError
+   *        True if you do not want to display an error when file save fails,
+   *        false otherwise.
+   * @param function aCallback
+   *        Optional function you want to call when file save completes. It will
+   *        get the following arguments:
+   *        1) the nsresult status code for the export operation.
+   */
+  exportToFile: function SP_exportToFile(aFile, aNoConfirmation, aSilentError,
+                                         aCallback)
+  {
+    if (!aNoConfirmation && aFile.exists() &&
+        !window.confirm(this.strings.
+                        GetStringFromName("export.fileOverwriteConfirmation"))) {
+      return;
+    }
+
+    let fs = Cc["@mozilla.org/network/file-output-stream;1"].
+             createInstance(Ci.nsIFileOutputStream);
+    let modeFlags = 0x02 | 0x08 | 0x20;
+    fs.init(aFile, modeFlags, 0644, fs.DEFER_OPEN);
+
+    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+                    createInstance(Ci.nsIScriptableUnicodeConverter);
+    converter.charset = "UTF-8";
+    let input = converter.convertToInputStream(this.textbox.value);
+
+    let self = this;
+    NetUtil.asyncCopy(input, fs, function(aStatus) {
+      if (!aSilentError && !Components.isSuccessCode(aStatus)) {
+        window.alert(self.strings.GetStringFromName("saveFile.failed"));
+      }
+
+      if (aCallback) {
+        aCallback.call(self, aStatus);
+      }
+    });
+  },
+
+  /**
+   * Read the content of a file and put it into the textbox.
+   *
+   * @param nsILocalFile aFile
+   *        The file you want to save the textbox content into.
+   * @param boolean aSilentError
+   *        True if you do not want to display an error when file load fails,
+   *        false otherwise.
+   * @param function aCallback
+   *        Optional function you want to call when file load completes. It will
+   *        get the following arguments:
+   *        1) the nsresult status code for the import operation.
+   *        2) the data that was read from the file, if any.
+   */
+  importFromFile: function SP_importFromFile(aFile, aSilentError, aCallback)
+  {
+    // Prevent file type detection.
+    let channel = NetUtil.newChannel(aFile);
+    channel.contentType = "application/javascript";
+
+    let self = this;
+    NetUtil.asyncFetch(channel, function(aInputStream, aStatus) {
+      let content = null;
+
+      if (Components.isSuccessCode(aStatus)) {
+        content = NetUtil.readInputStreamToString(aInputStream,
+                                                  aInputStream.available());
+        self.textbox.value = content;
+      }
+      else if (!aSilentError) {
+        window.alert(self.strings.GetStringFromName("openFile.failed"));
+      }
+
+      if (aCallback) {
+        aCallback.call(self, aStatus, content);
+      }
+    });
+  },
+
+  /**
+   * Open a file to edit in the Scratchpad.
+   */
+  openFile: function SP_openFile()
+  {
+    let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+    fp.init(window, this.strings.GetStringFromName("openFile.title"),
+            Ci.nsIFilePicker.modeOpen);
+    fp.defaultString = "";
+    if (fp.show() != Ci.nsIFilePicker.returnCancel) {
+      document.title = this.filename = fp.file.path;
+      this.importFromFile(fp.file);
+    }
+  },
+
+  /**
+   * Save the textbox content to the currently open file.
+   */
+  saveFile: function SP_saveFile()
+  {
+    if (!this.filename) {
+      return this.saveFileAs();
+    }
+
+    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+    file.initWithPath(this.filename);
+    this.exportToFile(file, true);
+  },
+
+  /**
+   * Save the textbox content to a new file.
+   */
+  saveFileAs: function SP_saveFileAs()
+  {
+    let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+    fp.init(window, this.strings.GetStringFromName("saveFileAs"),
+            Ci.nsIFilePicker.modeSave);
+    fp.defaultString = "scratchpad.js";
+    if (fp.show() != Ci.nsIFilePicker.returnCancel) {
+      document.title = this.filename = fp.file.path;
+      this.exportToFile(fp.file);
+    }
+  },
+
+  /**
+   * Open the Error Console.
+   */
+  openErrorConsole: function SP_openErrorConsole()
+  {
+    this.browserWindow.toJavaScriptConsole();
+  },
+
+  /**
+   * Open the Web Console.
+   */
+  openWebConsole: function SP_openWebConsole()
+  {
+    if (!this.browserWindow.HUDConsoleUI.getOpenHUD()) {
+      this.browserWindow.HUDConsoleUI.toggleHUD();
+    }
+    this.browserWindow.focus();
+  },
+
+  /**
+   * Set the current execution context to be the active tab content window.
+   */
+  setContentContext: function SP_setContentContext()
+  {
+    let content = document.getElementById("sp-menu-content");
+    document.getElementById("sp-menu-chrome").removeAttribute("checked");
+    content.setAttribute("checked", true);
+    this.statusbarStatus.label = content.getAttribute("label");
+    this.executionContext = SCRATCHPAD_CONTEXT_CONTENT;
+    this.resetContext();
+  },
+
+  /**
+   * Set the current execution context to be the most recent chrome window.
+   */
+  setChromeContext: function SP_setChromeContext()
+  {
+    let chrome = document.getElementById("sp-menu-chrome");
+    document.getElementById("sp-menu-content").removeAttribute("checked");
+    chrome.setAttribute("checked", true);
+    this.statusbarStatus.label = chrome.getAttribute("label");
+    this.executionContext = SCRATCHPAD_CONTEXT_CHROME;
+    this.resetContext();
+  },
+
+  /**
+   * Reset the cached Cu.Sandbox object for the current context.
+   */
+  resetContext: function SP_resetContext()
+  {
+    this._chromeSandbox = null;
+    this._contentSandbox = null;
+    this._previousWindow = null;
+  },
+
+  /**
+   * Gets the ID of the outer window of the given DOM window object.
+   *
+   * @param nsIDOMWindow aWindow
+   * @return integer
+   *         the outer window ID
+   */
+  getWindowId: function SP_getWindowId(aWindow)
+  {
+    return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+           getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+  },
+
+  /**
+   * The Scratchpad window DOMContentLoaded event handler.
+   */
+  onLoad: function SP_onLoad()
+  {
+    let chromeContextMenu = document.getElementById("sp-menu-chrome");
+    let errorConsoleMenu = document.getElementById("sp-menu-errorConsole");
+    let errorConsoleCommand = document.getElementById("sp-cmd-errorConsole");
+    let chromeContextCommand = document.getElementById("sp-cmd-chromeContext");
+
+    let chrome = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED);
+    if (chrome) {
+      chromeContextMenu.removeAttribute("hidden");
+      errorConsoleMenu.removeAttribute("hidden");
+      errorConsoleCommand.removeAttribute("disabled");
+      chromeContextCommand.removeAttribute("disabled");
+    }
+  },
+};
+
+XPCOMUtils.defineLazyGetter(Scratchpad, "strings", function () {
+  return Services.strings.createBundle(SCRATCHPAD_L10N);
+});
+
+addEventListener("DOMContentLoaded", Scratchpad.onLoad.bind(Scratchpad), false);
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/scratchpad.xul
@@ -0,0 +1,338 @@
+<?xml version="1.0"?>
+#ifdef 0
+<!-- ***** 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 Scratchpad.
+   -
+   - The Initial Developer of the Original Code is
+   - The Mozilla Foundation.
+   - Portions created by the Initial Developer are Copyright (C) 2011
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Rob Campbell <robcee@mozilla.com> (original author)
+   -   Mihai Sucan <mihai.sucan@gmail.com>
+   -   Erik Vold <erikvvold@gmail.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 ***** -->
+#endif
+<!DOCTYPE window [
+<!ENTITY % scratchpadDTD SYSTEM "chrome://browser/locale/scratchpad.dtd" >
+ %scratchpadDTD;
+]>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+
+<window id="main-window"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        title="&window.title;"
+        windowtype="devtools:scratchpad"
+        screenX="4" screenY="4"
+        width="640" height="480"
+        persist="screenX screenY width height sizemode">
+
+<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+<script type="application/javascript" src="chrome://browser/content/scratchpad.js"/>
+
+<commandset id="editMenuCommands"/>
+
+<commandset id="sp-commandset">
+  <command id="sp-cmd-newWindow" oncommand="Scratchpad.openScratchpad();"/>
+  <command id="sp-cmd-openFile" oncommand="Scratchpad.openFile();"/>
+  <command id="sp-cmd-save" oncommand="Scratchpad.saveFile();"/>
+  <command id="sp-cmd-saveas" oncommand="Scratchpad.saveFileAs();"/>
+
+  <!-- TODO: bug 650340 - implement printFile()
+  <command id="sp-cmd-printFile" oncommand="Scratchpad.printFile();" disabled="true"/>
+ -->
+
+  <command id="sp-cmd-close" oncommand="window.close();"/>
+  <command id="sp-cmd-execute" oncommand="Scratchpad.execute();"/>
+  <command id="sp-cmd-inspect" oncommand="Scratchpad.inspect();"/>
+  <command id="sp-cmd-print" oncommand="Scratchpad.print();"/>
+  <command id="sp-cmd-contentContext" oncommand="Scratchpad.setContentContext();"/>
+  <command id="sp-cmd-chromeContext" oncommand="Scratchpad.setChromeContext();" disabled="true"/>
+  <command id="sp-cmd-resetContext" oncommand="Scratchpad.resetContext();"/>
+  <command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/>
+  <command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/>
+</commandset>
+
+<keyset id="sp-keyset">
+  <key id="sp-key-window"
+       key="&newWindowCmd.commandkey;"
+       command="sp-cmd-newWindow"
+       modifiers="accel"/>
+  <key id="sp-key-open"
+       key="&openFileCmd.commandkey;"
+       command="sp-cmd-openFile"
+       modifiers="accel"/>
+  <key id="sp-key-save"
+       key="&saveFileCmd.commandkey;"
+       command="sp-cmd-save"
+       modifiers="accel"/>
+  <key id="sp-key-close"
+       key="&closeCmd.key;"
+       command="sp-cmd-close"
+       modifiers="accel"/>
+
+  <!-- TODO: bug 650340 - implement printFile
+  <key id="sp-key-printFile"
+       key="&printCmd.commandkey;"
+       command="sp-cmd-printFile"
+       modifiers="accel"/>
+  -->
+
+  <key id="key_cut"
+       key="&cutCmd.key;"
+       modifiers="accel"/>
+
+  <key id="key_copy"
+       key="&copyCmd.key;"
+       modifiers="accel"/>
+  <key id="key_paste"
+       key="&pasteCmd.key;"
+       modifiers="accel"/>
+  <key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
+  <key id="key_undo" key="&undoCmd.key;" modifiers="accel"/>
+  <key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift"/>
+  <key id="sp-key-execute"
+       key="&execute.key;"
+       command="sp-cmd-execute"
+       modifiers="accel"/>
+  <key id="sp-key-inspect"
+       key="&inspect.key;"
+       command="sp-cmd-inspect"
+       modifiers="accel"/>
+  <key id="sp-key-print"
+       key="&print.key;"
+       command="sp-cmd-print"
+       modifiers="accel"/>
+  <key id="sp-key-errorConsole"
+       key="&errorConsoleCmd.commandkey;"
+       command="sp-cmd-errorConsole"
+       modifiers="accel,shift"/>
+  <key id="sp-key-webConsole"
+       key="&webConsoleCmd.commandkey;"
+       command="sp-cmd-webConsole"
+       modifiers="accel,shift"/>
+</keyset>
+
+
+<menubar id="sp-menubar">
+  <menu id="sp-file-menu" label="&fileMenu.label;"
+        accesskey="&fileMenu.accesskey;">
+    <menupopup id="sp-menu-filepopup">
+      <menuitem id="sp-menu-newscratchpad"
+                label="&newWindowCmd.label;"
+                accesskey="&newWindowCmd.accesskey;"
+                key="sp-key-window"
+                command="sp-cmd-newWindow"/>
+      <menuseparator/>
+      <menuitem id="sp-menu-open"
+                label="&openFileCmd.label;"
+                command="sp-cmd-openFile"
+                key="sp-key-open"
+                accesskey="&openFileCmd.accesskey;"/>
+      <menuitem id="sp-menu-save"
+                label="&saveFileCmd.label;"
+                accesskey="&saveFileCmd.accesskey;"
+                key="sp-key-save"
+                command="sp-cmd-save"/>
+      <menuitem id="sp-menu-saveas"
+                label="&saveFileAsCmd.label;"
+                accesskey="&saveFileAsCmd.accesskey;"
+                command="sp-cmd-saveas"/>
+      <menuseparator/>
+
+      <!-- TODO: bug 650340 - implement printFile
+      <menuitem id="sp-menu-print"
+                label="&printCmd.label;"
+                accesskey="&printCmd.accesskey;"
+                command="sp-cmd-printFile"/>
+      <menuseparator/>
+      -->
+
+      <menuitem id="sp-menu-close"
+                label="&closeCmd.label;"
+                key="sp-key-close"
+                accesskey="&closeCmd.accesskey;"
+                command="sp-cmd-close"/>
+    </menupopup>
+  </menu>
+
+  <menu id="sp-edit-menu" label="&editMenu.label;"
+        accesskey="&editMenu.accesskey;">
+    <menupopup id="sp-menu_editpopup">
+      <menuitem id="sp-menu_undo"
+                label="&undoCmd.label;"
+                key="key_undo"
+                accesskey="&undoCmd.accesskey;"
+                disabled="true"
+                oncommand="cmd_undo"/>
+      <menuitem id="sp-menu-redo"
+                label="&redoCmd.label;"
+                key="key_redo"
+                disabled="true"
+                accesskey="&redoCmd.accesskey;"
+                command="cmd_redo"/>
+      <menuseparator/>
+      <menuitem id="sp-menu-cut"
+                label="&cutCmd.label;"
+                key="key_cut"
+                accesskey="&cutCmd.accesskey;"
+                command="cmd_cut"/>
+      <menuitem id="sp-menu-copy"
+                label="&copyCmd.label;"
+                key="key_copy"
+                accesskey="&copyCmd.accesskey;"
+                command="cmd_copy"/>
+      <menuitem id="sp-menu-paste"
+                label="&pasteCmd.label;"
+                key="key_paste"
+                accesskey="&pasteCmd.accesskey;"
+                command="cmd_paste"/>
+      <menuseparator/>
+      <menuitem id="sp-menu-selectAll"
+                label="&selectAllCmd.label;"
+                key="key_selectAll"
+                accesskey="&selectAllCmd.accesskey;"
+                command="cmd_selectAll"/>
+      <menuseparator/>
+
+      <!-- TODO: bug 650345 - implement search and replace
+      <menuitem id="sp-menu-find"
+                label="&findOnCmd.label;"
+                accesskey="&findOnCmd.accesskey;"
+                key="key_find"
+                disabled="true"
+                command="cmd_find"/>
+      <menuitem id="sp-menu-findAgain"
+                label="&findAgainCmd.label;"
+                accesskey="&findAgainCmd.accesskey;"
+                key="key_findAgain"
+                disabled="true"
+                command="cmd_findAgain"/>
+      <menuseparator id="sp-execute-separator"/>
+      -->
+
+      <menuitem id="sp-text-execute"
+                label="&execute.label;"
+                accesskey="&execute.accesskey;"
+                key="sp-key-execute"
+                command="sp-cmd-execute"/>
+      <menuitem id="sp-text-inspect"
+                label="&inspect.label;"
+                accesskey="&inspect.accesskey;"
+                key="sp-key-inspect"
+                command="sp-cmd-inspect"/>
+      <menuitem id="sp-text-print"
+                label="&print.label;"
+                accesskey="&print.accesskey;"
+                key="sp-key-print"
+                command="sp-cmd-print"/>
+    </menupopup>
+  </menu>
+
+  <menu id="sp-context-menu"
+        label="&contextMenu.label;"
+        accesskey="&contextMenu.accesskey;">
+    <menupopup id="sp-menu-context">
+      <menuitem id="sp-menu-content"
+                label="&contentContext.label;"
+                accesskey="&contentContext.accesskey;"
+                command="sp-cmd-contentContext"
+                checked="true"
+                type="radio"/>
+      <menuitem id="sp-menu-chrome" hidden="true"
+                command="sp-cmd-chromeContext"
+                label="&chromeContext.label;"
+                accesskey="&chromeContext.accesskey;"
+                type="radio"/>
+      <menuseparator/>
+      <menuitem id="sp-menu-resetContext"
+                command="sp-cmd-resetContext"
+                label="&resetContext.label;"
+                accesskey="&resetContext.accesskey;"/>
+    </menupopup>
+  </menu>
+
+  <menu id="sp-tools-menu"
+        label="&toolsMenu.label;"
+        accesskey="&toolsMenu.accesskey;">
+    <menupopup id="sp-menu-tools">
+      <menuitem id="sp-menu-errorConsole" hidden="true"
+                label="&errorConsoleCmd.label;"
+                accesskey="&errorConsoleCmd.accesskey;"
+                key="sp-key-errorConsole"
+                command="sp-cmd-errorConsole"/>
+      <menuitem id="sp-menu-webConsole"
+                label="&webConsoleCmd.label;"
+                accesskey="&webConsoleCmd.accesskey;"
+                key="sp-key-webConsole"
+                command="sp-cmd-webConsole"/>
+    </menupopup>
+  </menu>
+</menubar>
+
+<popupset id="scratchpad-popups">
+  <menupopup id="scratchpad-text-popup">
+    <menuitem id="menu_cut"/>
+    <menuitem id="menu_copy"/>
+    <menuitem id="menu_paste"/>
+    <menuitem id="menu_delete"/>
+    <menuseparator/>
+    <menuitem id="menu_selectAll"/>
+    <menuseparator/>
+    <menuitem id="sp-text-execute"
+              label="&execute.label;"
+              accesskey="&execute.accesskey;"
+              key="sp-key-execute"
+              command="sp-cmd-execute"/>
+    <menuitem id="sp-text-inspect"
+              label="&inspect.label;"
+              accesskey="&inspect.accesskey;"
+              key="sp-key-inspect"
+              command="sp-cmd-inspect"/>
+    <menuitem id="sp-text-print"
+              label="&print.label;"
+              accesskey="&print.accesskey;"
+              key="sp-key-print"
+              command="sp-cmd-print"/>
+  </menupopup>
+</popupset>
+
+<textbox id="scratchpad-textbox"
+         multiline="true"
+         flex="1"
+         context="scratchpad-text-popup"
+         placeholder="&textbox.placeholder;" />
+<statusbar id="scratchpad-statusbar" align="end">
+  <statusbarpanel id="scratchpad-status"
+                  label="&contentContext.label;"
+                  class="statusbarpanel-iconic-text"/>
+  <spacer flex="1"/>
+</statusbar>
+</window>
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -187,16 +187,23 @@ endif
                  browser_inspector_domPanel.js \
                  browser_inspector_iframeTest.js \
                  browser_inspector_scrolling.js \
                  browser_inspector_store.js \
                  browser_inspector_tab_switch.js \
                  browser_inspector_treePanel_output.js \
                  browser_inspector_treePanel_input.html \
                  browser_inspector_treePanel_result.html \
+                 browser_scratchpad_initialization.js \
+                 browser_scratchpad_contexts.js \
+                 browser_scratchpad_execute_print.js \
+                 browser_scratchpad_inspect.js \
+                 browser_scratchpad_files.js \
+                 browser_scratchpad_ui.js \
+                 browser_scratchpad_bug_646070_chrome_context_pref.js \
                  browser_overflowScroll.js \
                  browser_pageInfo.js \
                  browser_page_style_menu.js \
                  browser_pinnedTabs.js \
                  browser_plainTextLinks.js \
                  browser_pluginnotification.js \
                  browser_relatedTabs.js \
                  browser_sanitize-passwordDisabledHosts.js \
@@ -217,16 +224,17 @@ endif
                  browser_tabs_owner.js \
                  browser_urlHighlight.js \
                  browser_visibleFindSelection.js \
                  browser_visibleTabs.js \
                  browser_visibleTabs_contextMenu.js \
                  browser_visibleTabs_bookmarkAllPages.js \
                  browser_visibleTabs_bookmarkAllTabs.js \
                  browser_visibleTabs_tabPreview.js \
+                 browser_webdev_menu.js \
                  bug592338.html \
                  disablechrome.html \
                  discovery.html \
                  domplate_test.js \
                  moz.png \
                  test_bug435035.html \
                  test_bug462673.html \
                  page_style_sample.html \
--- a/browser/base/content/test/browser_inspector_initialization.js
+++ b/browser/base/content/test/browser_inspector_initialization.js
@@ -51,17 +51,17 @@ function runInspectorTests()
   Services.obs.removeObserver(runInspectorTests, "inspector-opened", false);
   Services.obs.addObserver(finishInspectorTests, "inspector-closed", false);
   let iframe = document.getElementById("inspector-tree-iframe");
   is(InspectorUI.treeIFrame, iframe, "Inspector IFrame matches");
   ok(InspectorUI.inspecting, "Inspector is highlighting");
   ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open");
   ok(InspectorUI.isStylePanelOpen, "Inspector Style Panel is open");
   ok(InspectorUI.isDOMPanelOpen, "Inspector DOM Panel is open");
-  InspectorUI.closeInspectorUI(true);
+  InspectorUI.closeInspectorUI();
 }
 
 function finishInspectorTests()
 {
   Services.obs.removeObserver(finishInspectorTests, "inspector-closed", false);
   ok(!InspectorUI.isDOMPanelOpen, "Inspector DOM Panel is closed");
   ok(!InspectorUI.isStylePanelOpen, "Inspector Style Panel is closed");
   ok(!InspectorUI.isTreePanelOpen, "Inspector Tree Panel is closed");
--- a/browser/base/content/test/browser_inspector_tab_switch.js
+++ b/browser/base/content/test/browser_inspector_tab_switch.js
@@ -152,16 +152,17 @@ function inspectorTabUnload1(evt)
   tab1window = tab1 = tab2 = div = null;
 
   // Make sure the Inspector is still open and that the state is correct.
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
   ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open");
   ok(InspectorUI.isStylePanelOpen, "Inspector Style Panel is open");
   is(InspectorStore.length, 1, "InspectorStore.length = 1");
 
+  InspectorUI.closeInspectorUI();
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_scratchpad_bug_646070_chrome_context_pref.js
@@ -0,0 +1,68 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Reference to the Scratchpad chrome window object.
+let gScratchpadWindow;
+
+let gOldPref;
+let DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gOldPref = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED);
+  Services.prefs.setBoolPref(DEVTOOLS_CHROME_ENABLED, true);
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    ok(Scratchpad, "Scratchpad variable exists");
+
+    gScratchpadWindow = Scratchpad.openScratchpad();
+    gScratchpadWindow.addEventListener("load", runTests, false);
+  }, true);
+
+  content.location = "data:text/html,Scratchpad test for bug 646070 - chrome context preference";
+}
+
+function runTests()
+{
+  gScratchpadWindow.removeEventListener("load", arguments.callee, false);
+
+  let sp = gScratchpadWindow.Scratchpad;
+  ok(sp, "Scratchpad object exists in new window");
+
+  let chromeContextMenu = gScratchpadWindow.document.
+                          getElementById("sp-menu-chrome");
+  ok(chromeContextMenu, "Chrome context menuitem element exists");
+  ok(!chromeContextMenu.hasAttribute("hidden"),
+     "Chrome context menuitem is visible");
+
+  let errorConsoleCommand = gScratchpadWindow.document.
+                            getElementById("sp-cmd-errorConsole");
+  ok(errorConsoleCommand, "Error console command element exists");
+  ok(!errorConsoleCommand.hasAttribute("disabled"),
+     "Error console command is enabled");
+
+  let errorConsoleMenu = gScratchpadWindow.document.
+                         getElementById("sp-menu-errorConsole");
+  ok(errorConsoleMenu, "Error console menu element exists");
+  ok(!errorConsoleMenu.hasAttribute("hidden"),
+     "Error console menuitem is visible");
+
+  let chromeContextCommand = gScratchpadWindow.document.
+                            getElementById("sp-cmd-chromeContext");
+  ok(chromeContextCommand, "Chrome context command element exists");
+  ok(!chromeContextCommand.hasAttribute("disabled"),
+     "Chrome context command is disabled");
+
+  Services.prefs.setBoolPref(DEVTOOLS_CHROME_ENABLED, gOldPref);
+
+  gScratchpadWindow.close();
+  gScratchpadWindow = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_scratchpad_contexts.js
@@ -0,0 +1,125 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Reference to the Scratchpad chrome window object.
+let gScratchpadWindow;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    gScratchpadWindow = Scratchpad.openScratchpad();
+    gScratchpadWindow.addEventListener("load", runTests, false);
+  }, true);
+
+  content.location = "data:text/html,test context switch in Scratchpad";
+}
+
+function runTests()
+{
+  gScratchpadWindow.removeEventListener("load", arguments.callee, false);
+
+  let sp = gScratchpadWindow.Scratchpad;
+
+  let contentMenu = gScratchpadWindow.document.getElementById("sp-menu-content");
+  let chromeMenu = gScratchpadWindow.document.getElementById("sp-menu-chrome");
+  let statusbar = sp.statusbarStatus;
+
+  ok(contentMenu, "found #sp-menu-content");
+  ok(chromeMenu, "found #sp-menu-chrome");
+  ok(statusbar, "found Scratchpad.statusbarStatus");
+
+  sp.setContentContext();
+
+  is(sp.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_CONTENT,
+     "executionContext is content");
+
+  is(contentMenu.getAttribute("checked"), "true",
+     "content menuitem is checked");
+
+  ok(!chromeMenu.hasAttribute("checked"),
+     "chrome menuitem is not checked");
+
+  is(statusbar.getAttribute("label"), contentMenu.getAttribute("label"),
+     "statusbar label is correct");
+
+  ok(sp.textbox, "textbox exists");
+  sp.textbox.value = "window.foobarBug636725 = 'aloha';";
+
+  ok(!content.wrappedJSObject.foobarBug636725,
+     "no content.foobarBug636725");
+
+  sp.execute();
+
+  is(content.wrappedJSObject.foobarBug636725, "aloha",
+     "content.foobarBug636725 has been set");
+
+  sp.setChromeContext();
+
+  is(sp.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_CHROME,
+     "executionContext is chrome");
+
+  is(chromeMenu.getAttribute("checked"), "true",
+     "chrome menuitem is checked");
+
+  ok(!contentMenu.hasAttribute("checked"),
+     "content menuitem is not checked");
+
+  is(statusbar.getAttribute("label"), chromeMenu.getAttribute("label"),
+     "statusbar label is correct");
+
+  sp.textbox.value = "window.foobarBug636725 = 'aloha2';";
+
+  ok(!window.foobarBug636725, "no window.foobarBug636725");
+
+  sp.execute();
+
+  is(window.foobarBug636725, "aloha2", "window.foobarBug636725 has been set");
+
+  sp.textbox.value = "window.gBrowser";
+
+  is(typeof sp.execute()[1].addTab, "function",
+     "chrome context has access to chrome objects");
+
+  // Check that the sandbox is cached.
+
+  sp.textbox.value = "typeof foobarBug636725cache;";
+  is(sp.execute()[1], "undefined", "global variable does not exist");
+
+  sp.textbox.value = "var foobarBug636725cache = 'foo';";
+  sp.execute();
+
+  sp.textbox.value = "typeof foobarBug636725cache;";
+  is(sp.execute()[1], "string",
+     "global variable exists across two different executions");
+
+  sp.resetContext();
+
+  is(sp.execute()[1], "undefined",
+     "global variable no longer exists after calling resetContext()");
+
+  sp.textbox.value = "var foobarBug636725cache2 = 'foo';";
+  sp.execute();
+
+  sp.textbox.value = "typeof foobarBug636725cache2;";
+  is(sp.execute()[1], "string",
+     "global variable exists across two different executions");
+
+  sp.setContentContext();
+
+  is(sp.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_CONTENT,
+     "executionContext is content");
+
+  is(sp.execute()[1], "undefined",
+     "global variable no longer exists after changing the context");
+
+  gScratchpadWindow.close();
+  gScratchpadWindow = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_scratchpad_execute_print.js
@@ -0,0 +1,115 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Reference to the Scratchpad chrome window object.
+let gScratchpadWindow;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    gScratchpadWindow = Scratchpad.openScratchpad();
+    gScratchpadWindow.addEventListener("load", runTests, false);
+  }, true);
+
+  content.location = "data:text/html,<p>test execute() and print() in Scratchpad";
+}
+
+function runTests()
+{
+  gScratchpadWindow.removeEventListener("load", arguments.callee, false);
+
+  let sp = gScratchpadWindow.Scratchpad;
+
+  content.wrappedJSObject.foobarBug636725 = 1;
+
+  ok(sp.textbox, "textbox exists");
+  sp.textbox.value = "++window.foobarBug636725";
+
+  let exec = sp.execute();
+  is(exec[0], sp.textbox.value, "execute()[0] is correct");
+  is(exec[1], content.wrappedJSObject.foobarBug636725,
+     "execute()[1] is correct");
+
+  is(sp.textbox.value, "++window.foobarBug636725",
+     "execute() does not change the textbox value");
+
+  is(content.wrappedJSObject.foobarBug636725, 2,
+     "execute() updated window.foobarBug636725");
+
+  sp.print();
+
+  is(content.wrappedJSObject.foobarBug636725, 3,
+     "print() updated window.foobarBug636725");
+
+  is(sp.textbox.value, "++window.foobarBug636725/*\n3\n*/",
+     "print() shows evaluation result in the textbox");
+
+  is(sp.selectedText, "/*\n3\n*/", "selectedText is correct");
+  is(sp.textbox.selectionStart, 24, "selectionStart is correct");
+  is(sp.textbox.selectionEnd, 31, "selectionEnd is correct");
+
+  // Test selection execute() and print().
+
+  sp.textbox.value = "window.foobarBug636725 = 'a';\n" +
+                     "window.foobarBug636725 = 'b';";
+
+  sp.selectRange(1, 2);
+
+  is(sp.textbox.selectionStart, 1, "selectionStart is 1");
+  is(sp.textbox.selectionEnd, 2, "selectionEnd is 2");
+
+  sp.selectRange(0, 29);
+
+  is(sp.textbox.selectionStart, 0, "selectionStart is 0");
+  is(sp.textbox.selectionEnd, 29, "selectionEnd is 29");
+
+  exec = sp.execute();
+
+  is(exec[0], "window.foobarBug636725 = 'a';",
+     "execute()[0] is correct");
+  is(exec[1], "a",
+     "execute()[1] is correct");
+
+  is(sp.textbox.value, "window.foobarBug636725 = 'a';\n" +
+                       "window.foobarBug636725 = 'b';",
+     "execute() does not change the textbox value");
+
+  is(content.wrappedJSObject.foobarBug636725, "a",
+     "execute() worked for the selected range");
+
+  sp.textbox.value = "window.foobarBug636725 = 'c';\n" +
+                     "window.foobarBug636725 = 'b';";
+
+  sp.selectRange(0, 22);
+
+  sp.print();
+
+  is(content.wrappedJSObject.foobarBug636725, "a",
+     "print() worked for the selected range");
+
+  is(sp.textbox.value, "window.foobarBug636725" +
+                       "/*\na\n*/" +
+                       " = 'c';\n" +
+                       "window.foobarBug636725 = 'b';",
+     "print() shows evaluation result in the textbox");
+
+  is(sp.selectedText, "/*\na\n*/", "selectedText is correct");
+  is(sp.textbox.selectionStart, 22, "selectionStart is correct");
+  is(sp.textbox.selectionEnd, 29, "selectionEnd is correct");
+
+  sp.deselect();
+
+  ok(!sp.selectedText, "selectedText is empty");
+  is(sp.textbox.selectionStart, sp.textbox.selectionEnd, "deselect() works");
+
+  gScratchpadWindow.close();
+  gScratchpadWindow = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_scratchpad_files.js
@@ -0,0 +1,149 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+// Reference to the Scratchpad chrome window object.
+let gScratchpadWindow;
+
+// Reference to the Scratchpad object.
+let gScratchpad;
+
+// Reference to the temporary nsIFile we will work with.
+let gFile;
+
+// The temporary file content.
+let gFileContent = "hello.world('bug636725');";
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    gScratchpadWindow = Scratchpad.openScratchpad();
+    gScratchpadWindow.addEventListener("load", runTests, false);
+  }, true);
+
+  content.location = "data:text/html,<p>test file open and save in Scratchpad";
+}
+
+function runTests()
+{
+  gScratchpadWindow.removeEventListener("load", arguments.callee, false);
+
+  gScratchpad = gScratchpadWindow.Scratchpad;
+
+  // Create a temporary file.
+  gFile = FileUtils.getFile("TmpD", ["fileForBug636725.tmp"]);
+  gFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
+
+  // Write the temporary file.
+  let fout = Cc["@mozilla.org/network/file-output-stream;1"].
+             createInstance(Ci.nsIFileOutputStream);
+  fout.init(gFile.QueryInterface(Ci.nsILocalFile), 0x02 | 0x08 | 0x20,
+            0644, fout.DEFER_OPEN);
+
+  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+                  createInstance(Ci.nsIScriptableUnicodeConverter);
+  converter.charset = "UTF-8";
+  let fileContentStream = converter.convertToInputStream(gFileContent);
+
+  NetUtil.asyncCopy(fileContentStream, fout, tempFileSaved);
+}
+
+function tempFileSaved(aStatus)
+{
+  ok(Components.isSuccessCode(aStatus),
+     "the temporary file was saved successfully");
+
+  // Import the file into Scratchpad.
+  gScratchpad.importFromFile(gFile.QueryInterface(Ci.nsILocalFile),  true,
+                            fileImported);
+}
+
+function fileImported(aStatus, aFileContent)
+{
+  ok(Components.isSuccessCode(aStatus),
+     "the temporary file was imported successfully with Scratchpad");
+
+  is(aFileContent, gFileContent,
+     "received data is correct");
+
+  is(gScratchpad.textbox.value, gFileContent,
+     "the textbox.value is correct");
+
+  // Save the file after changes.
+  gFileContent += "// omg, saved!";
+  gScratchpad.textbox.value = gFileContent;
+
+  gScratchpad.exportToFile(gFile.QueryInterface(Ci.nsILocalFile), true, true,
+                          fileExported);
+}
+
+function fileExported(aStatus)
+{
+  ok(Components.isSuccessCode(aStatus),
+     "the temporary file was exported successfully with Scratchpad");
+
+  let oldContent = gFileContent;
+
+  // Attempt another file save, with confirmation which returns false.
+  gFileContent += "// omg, saved twice!";
+  gScratchpad.textbox.value = gFileContent;
+
+  let oldConfirm = gScratchpadWindow.confirm;
+  let askedConfirmation = false;
+  gScratchpadWindow.confirm = function() {
+    askedConfirmation = true;
+    return false;
+  };
+
+  gScratchpad.exportToFile(gFile.QueryInterface(Ci.nsILocalFile), false, true,
+                          fileExported2);
+
+  gScratchpadWindow.confirm = oldConfirm;
+
+  ok(askedConfirmation, "exportToFile() asked for overwrite confirmation");
+
+  gFileContent = oldContent;
+
+  let channel = NetUtil.newChannel(gFile);
+  channel.contentType = "application/javascript";
+
+  // Read back the temporary file.
+  NetUtil.asyncFetch(channel, fileRead);
+}
+
+function fileExported2()
+{
+  ok(false, "exportToFile() did not cancel file overwrite");
+}
+
+function fileRead(aInputStream, aStatus)
+{
+  ok(Components.isSuccessCode(aStatus),
+     "the temporary file was read back successfully");
+
+  let updatedContent =
+    NetUtil.readInputStreamToString(aInputStream, aInputStream.available());;
+
+  is(updatedContent, gFileContent, "file properly updated");
+
+  // Done!
+  gFile.remove(false);
+  gFile = null;
+  gScratchpad = null;
+  gScratchpadWindow.close();
+  gScratchpadWindow = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_scratchpad_initialization.js
@@ -0,0 +1,63 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Reference to the Scratchpad chrome window object.
+let gScratchpadWindow;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    ok(Scratchpad, "Scratchpad variable exists");
+
+    gScratchpadWindow = Scratchpad.openScratchpad();
+    gScratchpadWindow.addEventListener("load", runTests, false);
+  }, true);
+
+  content.location = "data:text/html,initialization test for Scratchpad";
+}
+
+function runTests()
+{
+  gScratchpadWindow.removeEventListener("load", arguments.callee, false);
+
+  let sp = gScratchpadWindow.Scratchpad;
+  ok(sp, "Scratchpad object exists in new window");
+  is(typeof sp.execute, "function", "Scratchpad.execute() exists");
+  is(typeof sp.inspect, "function", "Scratchpad.inspect() exists");
+  is(typeof sp.print, "function", "Scratchpad.print() exists");
+
+  let chromeContextMenu = gScratchpadWindow.document.
+                          getElementById("sp-menu-chrome");
+  ok(chromeContextMenu, "Chrome context menuitem element exists");
+  is(chromeContextMenu.getAttribute("hidden"), "true",
+     "Chrome context menuitem is hidden");
+
+  let errorConsoleCommand = gScratchpadWindow.document.
+                            getElementById("sp-cmd-errorConsole");
+  ok(errorConsoleCommand, "Error console command element exists");
+  is(errorConsoleCommand.getAttribute("disabled"), "true",
+     "Error console command is disabled");
+
+  let errorConsoleMenu = gScratchpadWindow.document.
+                         getElementById("sp-menu-errorConsole");
+  ok(errorConsoleMenu, "Error console menu element exists");
+  is(errorConsoleMenu.getAttribute("hidden"), "true",
+     "Error console menu item is hidden");
+
+  let chromeContextCommand = gScratchpadWindow.document.
+                            getElementById("sp-cmd-chromeContext");
+  ok(chromeContextCommand, "Chrome context command element exists");
+  is(chromeContextCommand.getAttribute("disabled"), "true",
+     "Chrome context command is disabled");
+
+  gScratchpadWindow.close();
+  gScratchpadWindow = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_scratchpad_inspect.js
@@ -0,0 +1,65 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Reference to the Scratchpad chrome window object.
+let gScratchpadWindow;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    gScratchpadWindow = Scratchpad.openScratchpad();
+    gScratchpadWindow.addEventListener("load", runTests, false);
+  }, true);
+
+  content.location = "data:text/html,<title>foobarBug636725</title>" +
+    "<p>test inspect() in Scratchpad";
+}
+
+function runTests()
+{
+  gScratchpadWindow.removeEventListener("load", arguments.callee, false);
+
+  let sp = gScratchpadWindow.Scratchpad;
+
+  ok(sp.textbox, "textbox exists");
+  sp.textbox.value = "document";
+
+  sp.inspect();
+
+  let propPanel = document.querySelector(".scratchpad_propertyPanel");
+  ok(propPanel, "property panel is open");
+
+  propPanel.addEventListener("popupshown", function() {
+    propPanel.removeEventListener("popupshown", arguments.callee, false);
+
+    let tree = propPanel.querySelector("tree");
+    ok(tree, "property panel tree found");
+
+    let column = tree.columns[0];
+    let found = false;
+
+    for (let i = 0; i < tree.view.rowCount; i++) {
+      let cell = tree.view.getCellText(i, column);
+      if (cell == 'title: "foobarBug636725"') {
+        found = true;
+        break;
+      }
+    }
+    ok(found, "found the document.title property");
+
+    executeSoon(function() {
+      propPanel.hidePopup();
+
+      gScratchpadWindow.close();
+      gScratchpadWindow = null;
+      gBrowser.removeCurrentTab();
+      finish();
+    });
+  }, false);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_scratchpad_ui.js
@@ -0,0 +1,81 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Reference to the Scratchpad chrome window object.
+let gScratchpadWindow;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    gScratchpadWindow = Scratchpad.openScratchpad();
+    gScratchpadWindow.addEventListener("load", runTests, false);
+  }, true);
+
+  content.location = "data:text/html,<title>foobarBug636725</title>" +
+    "<p>test inspect() in Scratchpad";
+}
+
+function runTests()
+{
+  gScratchpadWindow.removeEventListener("load", arguments.callee, false);
+
+  let sp = gScratchpadWindow.Scratchpad;
+  let doc = gScratchpadWindow.document;
+
+  let methodsAndItems = {
+    "sp-menu-newscratchpad": "openScratchpad",
+    "sp-menu-open": "openFile",
+    "sp-menu-save": "saveFile",
+    "sp-menu-saveas": "saveFileAs",
+    "sp-text-execute": "execute",
+    "sp-text-inspect": "inspect",
+    "sp-menu-content": "setContentContext",
+    "sp-menu-chrome": "setChromeContext",
+    "sp-menu-resetContext": "resetContext",
+    "sp-menu-errorConsole": "openErrorConsole",
+    "sp-menu-webConsole": "openWebConsole",
+  };
+
+  let lastMethodCalled = null;
+  sp.__noSuchMethod__ = function(aMethodName) {
+    lastMethodCalled = aMethodName;
+  };
+
+  for (let id in methodsAndItems) {
+    lastMethodCalled = null;
+
+    let methodName = methodsAndItems[id];
+    let oldMethod = sp[methodName];
+    ok(oldMethod, "found method " + methodName + " in Scratchpad object");
+
+    delete sp[methodName];
+
+    let menu = doc.getElementById(id);
+    ok(menu, "found menuitem #" + id);
+
+    try {
+      menu.doCommand();
+    }
+    catch (ex) {
+      ok(false, "exception thrown while executing the command of menuitem #" + id);
+    }
+
+    ok(lastMethodCalled == methodName,
+       "method " + methodName + " invoked by the associated menuitem");
+
+    sp[methodName] = oldMethod;
+  }
+
+  delete sp.__noSuchMethod__;
+
+  gScratchpadWindow.close();
+  gScratchpadWindow = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_webdev_menu.js
@@ -0,0 +1,43 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function menuTest()
+{
+  gBrowser.selectedBrowser.removeEventListener("load", menuTest, true);
+
+  let menuContents = [
+    "menu_pageinspect",
+    "webConsole",
+    "menu_scratchpad",
+    "menu_pageSource",
+    "javascriptConsole"
+  ];
+
+  let menu = document.getElementById("webDeveloperMenu");
+  ok(menu, "we have the menu");
+
+  let popup = menu.firstChild;
+  is(popup.id, "menuWebDeveloperPopup", "menu first child is menuWebDeveloperPopup");
+
+  is(popup.childNodes.length, menuContents.length, "popup childNodes.length matches");
+
+  for(let a = 0; a < popup.children.length; a++) {
+    isnot(menuContents.indexOf(popup.children[a].id), -1, "menuitem " + popup.children[a].id + " in popup");
+  };
+
+  gBrowser.removeCurrentTab();
+  finish();  
+}
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", menuTest, true);
+
+  content.location = "data:text/html,<title>Web Developer Menu Test</title>" +
+    "<p>testing the Web Developer Menu";
+}
+
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -24,16 +24,18 @@ browser.jar:
         content/browser/aboutRobots-icon.png          (content/aboutRobots-icon.png)
         content/browser/aboutRobots-widget-left.png   (content/aboutRobots-widget-left.png)
 *       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/fullscreen-video.xhtml        (content/fullscreen-video.xhtml)
 *       content/browser/inspector.html                (content/inspector.html)
+*       content/browser/scratchpad.xul                (content/scratchpad.xul)
+*       content/browser/scratchpad.js                 (content/scratchpad.js)
 *       content/browser/pageinfo/pageInfo.xul         (content/pageinfo/pageInfo.xul)
 *       content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
 *       content/browser/pageinfo/pageInfo.css         (content/pageinfo/pageInfo.css)
 *       content/browser/pageinfo/pageInfo.xml         (content/pageinfo/pageInfo.xml)
 *       content/browser/pageinfo/feeds.js             (content/pageinfo/feeds.js)
 *       content/browser/pageinfo/feeds.xml            (content/pageinfo/feeds.xml)
 *       content/browser/pageinfo/permissions.js       (content/pageinfo/permissions.js)
 *       content/browser/pageinfo/security.js          (content/pageinfo/security.js)
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -174,28 +174,46 @@ can reach it easily. -->
 <!ENTITY downloads.tooltip            "Display the progress of ongoing downloads">
 <!ENTITY downloads.accesskey          "D">
 <!ENTITY downloads.commandkey         "j">
 <!ENTITY downloadsUnix.commandkey     "y">
 <!ENTITY addons.label                 "Add-ons">
 <!ENTITY addons.accesskey             "A">
 <!ENTITY addons.commandkey            "A">
 
+<!ENTITY webDeveloperMenu.label       "Web Developer">
+<!ENTITY webDeveloperMenu.accesskey   "W">
+
 <!ENTITY errorConsoleCmd.label        "Error Console">
 <!ENTITY errorConsoleCmd.accesskey    "C">
 <!ENTITY errorConsoleCmd.commandkey   "j">
 
 <!ENTITY webConsoleCmd.label          "Web Console">
 <!ENTITY webConsoleCmd.accesskey      "W">
 <!ENTITY webConsoleCmd.commandkey     "k">
 
 <!ENTITY inspectMenu.label            "Inspect">
 <!ENTITY inspectMenu.accesskey        "T">
 <!ENTITY inspectMenu.commandkey       "I">
 
+<!-- LOCALIZATION NOTE (scratchpad.label): This menu item label appears
+  -  in the Tools menu. See bug 653093.
+  -  The Scratchpad is intended to provide a simple text editor for creating
+  -  and evaluating bits of JavaScript code for the purposes of function
+  -  prototyping, experimentation and convenient scripting.
+  -
+  -  It's quite possible that you won't have a good analogue for the word
+  -  "Scratchpad" in your locale. You should feel free to find a close
+  -  approximation to it or choose a word (or words) that means
+  -  "simple discardable text editor". -->
+<!ENTITY scratchpad.label             "Scratchpad">
+<!ENTITY scratchpad.accesskey         "r">
+<!ENTITY scratchpad.keycode           "VK_F4">
+<!ENTITY scratchpad.keytext           "F4">
+
 <!ENTITY inspectPanelTitle.label      "HTML">
 <!ENTITY inspectButton.label          "Inspect">
 <!ENTITY inspectButton.accesskey      "I">
 <!ENTITY inspectNextButton.label      "Next">
 <!ENTITY inspectNextButton.accesskey  "N">
 <!ENTITY inspectPreviousButton.label  "Previous">
 <!ENTITY inspectPreviousButton.accesskey "P">
 <!ENTITY inspectStyleButton.label     "Style">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/scratchpad.dtd
@@ -0,0 +1,103 @@
+<!-- LOCALIZATION NOTE : FILE This file contains the Scratchpad window strings -->
+<!-- LOCALIZATION NOTE : FILE Do not translate commandkeys -->
+
+<!-- LOCALIZATION NOTE (scratchpad.title):
+  -  The Scratchpad is intended to provide a simple text editor for creating
+  -  and evaluating bits of JavaScript code for the purposes of function
+  -  prototyping, experimentation and convenient scripting.
+  -
+  -  It's quite possible that you won't have a good analogue for the word
+  -  "Scratchpad" in your locale. You should feel free to find a close
+  -  approximation to it or choose a word (or words) that means
+  -  "simple discardable text editor". -->
+<!ENTITY window.title                 "Scratchpad">
+
+<!ENTITY fileMenu.label               "File">
+<!ENTITY fileMenu.accesskey           "F">
+
+<!ENTITY newWindowCmd.label           "New Window">
+<!ENTITY newWindowCmd.accesskey       "N">
+<!ENTITY newWindowCmd.commandkey      "n">
+
+<!ENTITY openFileCmd.label            "Open File…">
+<!ENTITY openFileCmd.accesskey        "O">
+<!ENTITY openFileCmd.commandkey       "o">
+
+<!ENTITY saveFileCmd.label            "Save">
+<!ENTITY saveFileCmd.accesskey        "S">
+<!ENTITY saveFileCmd.commandkey       "s">
+
+<!ENTITY saveFileAsCmd.label          "Save As…">
+<!ENTITY saveFileAsCmd.accesskey      "A">
+
+<!ENTITY closeCmd.label               "Close">
+<!ENTITY closeCmd.key                 "W">
+<!ENTITY closeCmd.accesskey           "C">
+
+<!ENTITY editMenu.label               "Edit">
+<!ENTITY editMenu.accesskey           "E">
+
+<!ENTITY undoCmd.label                "Undo">
+<!ENTITY undoCmd.key                  "Z">
+<!ENTITY undoCmd.accesskey            "U">
+
+<!ENTITY redoCmd.label                "Redo">
+<!ENTITY redoCmd.key                  "Y">
+<!ENTITY redoCmd.accesskey            "R">
+
+<!ENTITY cutCmd.label                 "Cut">
+<!ENTITY cutCmd.key                   "X">
+<!ENTITY cutCmd.accesskey             "t">
+
+<!ENTITY copyCmd.label                "Copy">
+<!ENTITY copyCmd.key                  "C">
+<!ENTITY copyCmd.accesskey            "C">
+
+<!ENTITY pasteCmd.label               "Paste">
+<!ENTITY pasteCmd.key                 "V">
+<!ENTITY pasteCmd.accesskey           "P">
+
+<!ENTITY selectAllCmd.label           "Select All">
+<!ENTITY selectAllCmd.key             "A">
+<!ENTITY selectAllCmd.accesskey       "A">
+
+<!ENTITY execute.label                "Execute">
+<!ENTITY execute.accesskey            "E">
+<!ENTITY execute.key                  "t">
+
+<!ENTITY inspect.label                "Inspect">
+<!ENTITY inspect.accesskey            "I">
+<!ENTITY inspect.key                  "i">
+
+<!ENTITY print.label                  "Print">
+<!ENTITY print.accesskey              "p">
+<!ENTITY print.key                    "r">
+
+<!ENTITY contextMenu.label            "Context">
+<!ENTITY contextMenu.accesskey        "C">
+
+<!ENTITY contentContext.label         "Content">
+<!ENTITY contentContext.accesskey     "C">
+
+<!ENTITY chromeContext.label          "Chrome">
+<!ENTITY chromeContext.accesskey      "H">
+
+<!-- LOCALIZATION NOTE (resetContext.label): This command allows the developer
+  -  to reset/clear the global object of the context where the code executes.
+  -->
+<!ENTITY resetContext.label           "Reset">
+<!ENTITY resetContext.accesskey       "R">
+
+<!ENTITY toolsMenu.label              "Tools">
+<!ENTITY toolsMenu.accesskey          "T">
+
+<!ENTITY errorConsoleCmd.label        "Error Console">
+<!ENTITY errorConsoleCmd.accesskey    "C">
+<!ENTITY errorConsoleCmd.commandkey   "j">
+
+<!ENTITY webConsoleCmd.label          "Web Console">
+<!ENTITY webConsoleCmd.accesskey      "W">
+<!ENTITY webConsoleCmd.commandkey     "k">
+
+<!ENTITY textbox.placeholder          "// Enter some JavaScript, select it, right click and select Execute, Inspect or Print.">
+
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/scratchpad.properties
@@ -0,0 +1,30 @@
+# LOCALIZATION NOTE  (propertyPanel.updateButton.label): Used in the Property
+# Panel that is opened by the Scratchpad window when inspecting an object. This
+# is the Update button label.
+propertyPanel.updateButton.label=Update
+propertyPanel.updateButton.accesskey=U
+
+# LOCALIZATION NOTE  (export.fileOverwriteConfirmation): This is displayed when
+# the user attempts to save to an already existing file.
+export.fileOverwriteConfirmation=File exists. Overwrite?
+
+# LOCALIZATION NOTE  (browserWindow.unavailable): This error message is shown
+# when Scratchpad does not find any recently active window of navigator:browser
+# type.
+browserWindow.unavailable=Scratchpad cannot find any browser window to execute the code in.
+
+# LOCALIZATION NOTE  (openFile.title): This is the file picker title, when you
+# open a file from Scratchpad.
+openFile.title=Open File
+
+# LOCALIZATION NOTE  (openFile.failed): This is the message displayed when file
+# open fails.
+openFile.failed=Failed to read the file.
+
+# LOCALIZATION NOTE  (saveFileAs): This is the file picker title, when you save
+# a file in Scratchpad.
+saveFileAs=Save File As
+
+# LOCALIZATION NOTE  (saveFile.failed): This is the message displayed when file
+# save fails.
+saveFile.failed=The file save operation failed.
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -10,16 +10,18 @@
     locale/browser/aboutSessionRestore.dtd         (%chrome/browser/aboutSessionRestore.dtd)
 #ifdef MOZ_SERVICES_SYNC
     locale/browser/aboutSyncTabs.dtd               (%chrome/browser/aboutSyncTabs.dtd)
 #endif
 *   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/inspector.properties            (%chrome/browser/inspector.properties)
+    locale/browser/scratchpad.properties           (%chrome/browser/scratchpad.properties)
+    locale/browser/scratchpad.dtd                  (%chrome/browser/scratchpad.dtd)
     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)
     locale/browser/quitDialog.properties           (%chrome/browser/quitDialog.properties)
 *   locale/browser/safeMode.dtd                    (%chrome/browser/safeMode.dtd)
     locale/browser/sanitize.dtd                    (%chrome/browser/sanitize.dtd)
     locale/browser/search.properties               (%chrome/browser/search.properties)
--- a/dom/base/ConsoleAPI.js
+++ b/dom/base/ConsoleAPI.js
@@ -16,16 +16,17 @@
  * The Initial Developer of the Original Code is Mozilla Foundation
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *  David Dahl <ddahl@mozilla.com>  (Original Author)
  *  Ryan Flint <rflint@mozilla.com>
  *  Rob Campbell <rcampbell@mozilla.com>
+ *  Mihai Sucan <mihai.sucan@gmail.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
@@ -74,37 +75,42 @@ ConsoleAPI.prototype = {
         self.notifyObservers(id, "warn", arguments);
       },
       error: function CA_error() {
         self.notifyObservers(id, "error", arguments);
       },
       debug: function CA_debug() {
         self.notifyObservers(id, "log", arguments);
       },
+      trace: function CA_trace() {
+        self.notifyObservers(id, "trace", self.getStackTrace());
+      },
       __exposedProps__: {
         log: "r",
         info: "r",
         warn: "r",
         error: "r",
         debug: "r",
+        trace: "r",
       }
     };
 
     // We need to return an actual content object here, instead of a wrapped
     // chrome object. This allows things like console.log.bind() to work.
     let sandbox = Cu.Sandbox(aWindow);
     let contentObject = Cu.evalInSandbox(
         "(function(x) {\
           var bind = Function.bind;\
           var obj = {\
             log: bind.call(x.log, x),\
             info: bind.call(x.info, x),\
             warn: bind.call(x.warn, x),\
             error: bind.call(x.error, x),\
             debug: bind.call(x.debug, x),\
+            trace: bind.call(x.trace, x),\
             __noSuchMethod__: function() {}\
           };\
           Object.defineProperty(obj, '__mozillaConsole__', { value: true });\
           return obj;\
         })", sandbox)(chromeObject);
 
       return contentObject;
   },
@@ -121,12 +127,37 @@ ConsoleAPI.prototype = {
       level: aLevel,
       arguments: aArguments
     };
 
     consoleEvent.wrappedJSObject = consoleEvent;
 
     Services.obs.notifyObservers(consoleEvent,
                                  "console-api-log-event", aID);
-  }
+  },
+
+  /**
+   * Build the stacktrace array for the console.trace() call.
+   *
+   * @return array
+   *         Each element is a stack frame that holds the following properties:
+   *         filename, lineNumber, functionName and language.
+   **/
+  getStackTrace: function CA_getStackTrace() {
+    let stack = [];
+    let frame = Components.stack.caller;
+    while (frame = frame.caller) {
+      if (frame.language == Ci.nsIProgrammingLanguage.JAVASCRIPT ||
+          frame.language == Ci.nsIProgrammingLanguage.JAVASCRIPT2) {
+        stack.push({
+          filename: frame.filename,
+          lineNumber: frame.lineNumber,
+          functionName: frame.name,
+          language: frame.language,
+        });
+      }
+    }
+
+    return stack;
+  },
 };
 
 let NSGetFactory = XPCOMUtils.generateNSGetFactory([ConsoleAPI]);
--- a/dom/tests/browser/browser_ConsoleAPITests.js
+++ b/dom/tests/browser/browser_ConsoleAPITests.js
@@ -16,16 +16,17 @@
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *  David Dahl <ddahl@mozilla.com>
  *  Rob Campbell <rcampbell@mozilla.com>
+ *  Mihai Sucan <mihai.sucan@gmail.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
@@ -68,25 +69,49 @@ var gWindow;
 
 function testConsoleData(aMessageObject) {
   let messageWindow = getWindowByWindowId(aMessageObject.ID);
   is(messageWindow, gWindow, "found correct window by window ID");
 
   is(aMessageObject.level, gLevel, "expected level received");
   ok(aMessageObject.arguments, "we have arguments");
   is(aMessageObject.arguments.length, gArgs.length, "arguments.length matches");
-  gArgs.forEach(function (a, i) {
-    is(aMessageObject.arguments[i], a, "correct arg " + i);
-  });
 
-  if (aMessageObject.level == "error") {
+  if (gLevel == "trace") {
+    is(aMessageObject.arguments.toSource(), gArgs.toSource(),
+       "stack trace is correct");
+
     // Test finished
     ConsoleObserver.destroy();
     finish();
   }
+  else {
+    gArgs.forEach(function (a, i) {
+      is(aMessageObject.arguments[i], a, "correct arg " + i);
+    });
+  }
+
+  if (aMessageObject.level == "error") {
+    // Now test console.trace()
+    startTraceTest();
+  }
+}
+
+function startTraceTest() {
+  gLevel = "trace";
+  gArgs = [
+    {filename: TEST_URI, lineNumber: 6, functionName: null, language: 2},
+    {filename: TEST_URI, lineNumber: 11, functionName: "foobar585956b", language: 2},
+    {filename: TEST_URI, lineNumber: 15, functionName: "foobar585956a", language: 2},
+    {filename: TEST_URI, lineNumber: 1, functionName: "onclick", language: 2}
+  ];
+
+  let button = gWindow.document.getElementById("test-trace");
+  ok(button, "found #test-trace button");
+  EventUtils.synthesizeMouse(button, 2, 2, {}, gWindow);
 }
 
 var gLevel, gArgs;
 function expect(level) {
   gLevel = level;
   gArgs = Array.slice(arguments, 1);
 }
 
@@ -109,16 +134,17 @@ function consoleAPISanityTest() {
   let win = XPCNativeWrapper.unwrap(gWindow);
   ok(win.console, "we have a console attached");
   ok(win.console, "we have a console attached, 2nd attempt");
 
   ok(win.console.log, "console.log is here");
   ok(win.console.info, "console.info is here");
   ok(win.console.warn, "console.warn is here");
   ok(win.console.error, "console.error is here");
+  ok(win.console.trace, "console.trace is here");
 }
 
 var ConsoleObserver = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   init: function CO_init() {
     Services.obs.addObserver(this, "console-api-log-event", false);
   },
--- a/dom/tests/browser/test-console-api.html
+++ b/dom/tests/browser/test-console-api.html
@@ -1,19 +1,33 @@
 <!DOCTYPE HTML>
 <html dir="ltr" xml:lang="en-US" lang="en-US"><head>
     <title>Console API test page</title>
     <script type="text/javascript">
+      window.foobar585956c = function(a) {
+        console.trace();
+        return a+"c";
+      };
+
+      function foobar585956b(a) {
+        return foobar585956c(a+"b");
+      }
+
+      function foobar585956a(omg) {
+        return foobar585956b(omg + "a");
+      }
+
       function test() {
         var str = "Test Message."
         console.foobar(str); // if this throws, we don't execute following funcs
         console.log(str);
         console.info(str);
         console.warn(str);
         console.error(str);
       }
     </script>
   </head>
   <body>
     <h1>Console API Test Page</h1>
     <button onclick="test();">Log stuff</button>
+    <button id="test-trace" onclick="foobar585956a('omg');">Test trace</button>
   </body>
 </html>
--- a/dom/tests/mochitest/general/test_consoleAPI.html
+++ b/dom/tests/mochitest/general/test_consoleAPI.html
@@ -21,16 +21,17 @@ function doTest() {
   }
 
   var expectedProps = {
     "log": "function",
     "info": "function",
     "warn": "function",
     "error": "function",
     "debug": "function",
+    "trace": "function",
     "__noSuchMethod__": "function"
   };
 
   var foundProps = 0;
   for (var prop in console) {
     foundProps++;
     is(typeof(console[prop]), expectedProps[prop], "expect console prop " + prop + " exists");
   }
--- a/toolkit/components/console/hudservice/HUDService.jsm
+++ b/toolkit/components/console/hudservice/HUDService.jsm
@@ -140,16 +140,17 @@ const SEVERITY_LOG = 3;
 
 // A mapping from the console API log event levels to the Web Console
 // severities.
 const LEVELS = {
   error: SEVERITY_ERROR,
   warn: SEVERITY_WARNING,
   info: SEVERITY_INFO,
   log: SEVERITY_LOG,
+  trace: SEVERITY_LOG,
 };
 
 // The lowest HTTP response code (inclusive) that is considered an error.
 const MIN_HTTP_ERROR_CODE = 400;
 // The highest HTTP response code (exclusive) that is considered an error.
 const MAX_HTTP_ERROR_CODE = 600;
 
 // HTTP status codes.
@@ -1433,28 +1434,34 @@ HUD_SERVICE.prototype =
    * Deactivate a HeadsUpDisplay for the given tab context.
    *
    * @param nsIDOMWindow aContext
    * @param aAnimated animate closing the web console?
    * @returns void
    */
   deactivateHUDForContext: function HS_deactivateHUDForContext(aContext, aAnimated)
   {
-    let window = aContext.linkedBrowser.contentWindow;
+    let browser = aContext.linkedBrowser;
+    let window = browser.contentWindow;
     let nBox = aContext.ownerDocument.defaultView.
       getNotificationBox(window);
     let hudId = "hud_" + nBox.id;
     let displayNode = nBox.querySelector("#" + hudId);
 
     if (hudId in this.hudReferences && displayNode) {
       if (!aAnimated) {
         this.storeHeight(hudId);
       }
 
+      let hud = this.hudReferences[hudId];
+      browser.webProgress.removeProgressListener(hud.progressListener);
+      delete hud.progressListener;
+
       this.unregisterDisplay(displayNode);
+
       window.focus();
     }
   },
 
   /**
    * Clear the specified HeadsUpDisplay
    *
    * @param string|nsIDOMNode aHUD
@@ -1976,22 +1983,99 @@ HUD_SERVICE.prototype =
                                                          aLevel,
                                                          aArguments)
   {
     // Pipe the message to createMessageNode().
     let hud = HUDService.hudReferences[aHUDId];
     function formatResult(x) {
       return (typeof(x) == "string") ? x : hud.jsterm.formatResult(x);
     }
-    let mappedArguments = Array.map(aArguments, formatResult);
-    let joinedArguments = Array.join(mappedArguments, " ");
+
+    let body = null;
+    let clipboardText = null;
+    let sourceURL = null;
+    let sourceLine = 0;
+
+    switch (aLevel) {
+      case "log":
+      case "info":
+      case "warn":
+      case "error":
+      case "debug":
+        let mappedArguments = Array.map(aArguments, formatResult);
+        body = Array.join(mappedArguments, " ");
+        break;
+
+      case "trace":
+        let filename = ConsoleUtils.abbreviateSourceURL(aArguments[0].filename);
+        let functionName = aArguments[0].functionName ||
+                           this.getStr("stacktrace.anonymousFunction");
+        let lineNumber = aArguments[0].lineNumber;
+
+        body = this.getFormatStr("stacktrace.outputMessage",
+                                 [filename, functionName, lineNumber]);
+
+        sourceURL = aArguments[0].filename;
+        sourceLine = aArguments[0].lineNumber;
+
+        clipboardText = "";
+
+        aArguments.forEach(function(aFrame) {
+          clipboardText += aFrame.filename + " :: " +
+                           aFrame.functionName + " :: " +
+                           aFrame.lineNumber + "\n";
+        });
+
+        clipboardText = clipboardText.trimRight();
+        break;
+
+      default:
+        Cu.reportError("Unknown Console API log level: " + aLevel);
+        return;
+    }
+
     let node = ConsoleUtils.createMessageNode(hud.outputNode.ownerDocument,
                                               CATEGORY_WEBDEV,
                                               LEVELS[aLevel],
-                                              joinedArguments);
+                                              body,
+                                              sourceURL,
+                                              sourceLine,
+                                              clipboardText);
+
+    // Make the node bring up the property panel, to allow the user to inspect
+    // the stack trace.
+    if (aLevel == "trace") {
+      node._stacktrace = aArguments;
+
+      let linkNode = node.querySelector(".webconsole-msg-body");
+      linkNode.classList.add("hud-clickable");
+      linkNode.setAttribute("aria-haspopup", "true");
+
+      node.addEventListener("mousedown", function(aEvent) {
+        this._startX = aEvent.clientX;
+        this._startY = aEvent.clientY;
+      }, false);
+
+      node.addEventListener("click", function(aEvent) {
+        if (aEvent.detail != 1 || aEvent.button != 0 ||
+            (this._startX != aEvent.clientX &&
+             this._startY != aEvent.clientY)) {
+          return;
+        }
+
+        if (!this._panelOpen) {
+          let propPanel = hud.jsterm.openPropertyPanel(null,
+                                                       node._stacktrace,
+                                                       this);
+          propPanel.panel.setAttribute("hudId", aHUDId);
+          this._panelOpen = true;
+        }
+      }, false);
+    }
+
     ConsoleUtils.outputMessageNode(node, aHUDId);
   },
 
   /**
    * Get OutputNode by Id
    *
    * @param string aId
    * @returns nsIDOMNode (richlistbox)
@@ -2636,45 +2720,24 @@ HUD_SERVICE.prototype =
     2: "typeException", // JSREPORT_EXCEPTION
     4: "typeError", // JSREPORT_STRICT | JSREPORT_ERROR
     5: "typeStrict", // JSREPORT_STRICT | JSREPORT_WARNING
     8: "typeError", // JSREPORT_STRICT_MODE_ERROR
     13: "typeWarning", // JSREPORT_STRICT_MODE_ERROR | JSREPORT_WARNING | JSREPORT_ERROR
   },
 
   /**
-   * Closes the Console, if any, that resides on the given tab.
-   *
-   * @param nsIDOMNode aTab
-   *        The tab on which to close the console.
-   * @returns void
-   */
-  closeConsoleOnTab: function HS_closeConsoleOnTab(aTab)
-  {
-    let xulDocument = aTab.ownerDocument;
-    let xulWindow = xulDocument.defaultView;
-    let gBrowser = xulWindow.gBrowser;
-    let linkedBrowser = aTab.linkedBrowser;
-    let notificationBox = gBrowser.getNotificationBox(linkedBrowser);
-    let hudId = "hud_" + notificationBox.getAttribute("id");
-    let outputNode = xulDocument.getElementById(hudId);
-    if (outputNode != null) {
-      this.unregisterDisplay(outputNode);
-    }
-  },
-
-  /**
    * onTabClose event handler function
    *
    * @param aEvent
    * @returns void
    */
   onTabClose: function HS_onTabClose(aEvent)
   {
-    this.closeConsoleOnTab(aEvent.target);
+    this.deactivateHUDForContext(aEvent.target, false);
   },
 
   /**
    * Called whenever a browser window closes. Cleans up any consoles still
    * around.
    *
    * @param nsIDOMEvent aEvent
    *        The dispatched event.
@@ -2682,17 +2745,17 @@ HUD_SERVICE.prototype =
    */
   onWindowUnload: function HS_onWindowUnload(aEvent)
   {
     let gBrowser = aEvent.target.defaultView.gBrowser;
     let tabContainer = gBrowser.tabContainer;
 
     let tab = tabContainer.firstChild;
     while (tab != null) {
-      this.closeConsoleOnTab(tab);
+      this.deactivateHUDForContext(tab, false);
       tab = tab.nextSibling;
     }
   },
 
   /**
    * windowInitializer - checks what Gecko app is running and inits the HUD
    *
    * @param nsIDOMWindow aContentWindow
@@ -2754,16 +2817,21 @@ HUD_SERVICE.prototype =
                      contentWindow: aContentWindow
                    };
 
       hud = new HeadsUpDisplay(config);
 
       HUDService.registerHUDReference(hud);
       let windowId = this.getWindowId(aContentWindow.top);
       this.windowIds[windowId] = hudId;
+
+      hud.progressListener = new ConsoleProgressListener(hudId);
+
+      _browser.webProgress.addProgressListener(hud.progressListener,
+        Ci.nsIWebProgress.NOTIFY_STATE_ALL);
     }
     else {
       hud = this.hudReferences[hudId];
       if (aContentWindow == aContentWindow.top) {
         // TODO: name change?? doesn't actually re-attach the console
         hud.reattachConsole(aContentWindow);
       }
     }
@@ -2908,18 +2976,24 @@ HUD_SERVICE.prototype =
    * @returns void
    */
   copySelectedItems: function HS_copySelectedItems(aOutputNode)
   {
     // Gather up the selected items and concatenate their clipboard text.
 
     let strings = [];
     let newGroup = false;
-    for (let i = 0; i < aOutputNode.selectedCount; i++) {
-      let item = aOutputNode.selectedItems[i];
+
+    let children = aOutputNode.children;
+
+    for (let i = 0; i < children.length; i++) {
+      let item = children[i];
+      if (!item.selected) {
+        continue;
+      }
 
       // Add dashes between groups so that group boundaries show up in the
       // copied output.
       if (i > 0 && item.classList.contains("webconsole-new-group")) {
         newGroup = true;
       }
 
       // Ensure the selected item hasn't been filtered by type or string.
@@ -3522,17 +3596,17 @@ let ConsoleAPIObserver = {
       // Find the HUD ID for the topmost window
       let hudId = HUDService.getHudIdByWindow(win.top);
       if (!hudId)
         return;
 
       HUDService.logConsoleAPIMessage(hudId, aMessage.level, aMessage.arguments);
     }
     else if (aTopic == "quit-application-granted") {
-      this.shutdown();
+      HUDService.shutdown();
     }
   },
 
   shutdown: function CAO_shutdown()
   {
     Services.obs.removeObserver(this, "quit-application-granted");
     Services.obs.removeObserver(this, "console-api-log-event");
   }
@@ -3782,16 +3856,20 @@ function JSPropertyProvider(aScope, aInp
   return {
     matchProp: matchProp,
     matches: matches
   };
 }
 
 function isIteratorOrGenerator(aObject)
 {
+  if (aObject === null) {
+    return false;
+  }
+
   if (typeof aObject == "object") {
     if (typeof aObject.__iterator__ == "function" ||
         aObject.constructor && aObject.constructor.name == "Iterator") {
       return true;
     }
 
     let str = aObject.toString();
     if (typeof aObject.next == "function" &&
@@ -5440,16 +5518,28 @@ HeadsUpDisplayUICommands = {
     }
     else {
       HUDService.activateHUDForContext(gBrowser.selectedTab, true);
       HUDService.animate(hudId, ANIMATE_IN);
     }
   },
 
   /**
+   * Find the hudId for the active chrome window.
+   * @return string|null
+   *         The hudId or null if the active chrome window has no open Web
+   *         Console.
+   */
+  getOpenHUD: function UIC_getOpenHUD() {
+    let chromeWindow = HUDService.currentContext();
+    let contentWindow = chromeWindow.gBrowser.selectedBrowser.contentWindow;
+    return HUDService.getHudIdByWindow(contentWindow);
+  },
+
+  /**
    * The event handler that is called whenever a user switches a filter on or
    * off.
    *
    * @param nsIDOMEvent aEvent
    *        The event that triggered the filter change.
    * @return boolean
    */
   toggleFilter: function UIC_toggleFilter(aEvent) {
@@ -5840,17 +5930,17 @@ HUDWindowObserver = {
     else if (aTopic == "xpcom-shutdown") {
       this.uninit();
     }
   },
 
   uninit: function HWO_uninit()
   {
     Services.obs.removeObserver(this, "content-document-global-created");
-    HUDService.shutdown();
+    Services.obs.removeObserver(this, "xpcom-shutdown");
     this.initialConsoleCreated = false;
   },
 
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 // CommandController
 ///////////////////////////////////////////////////////////////////////////////
@@ -6002,16 +6092,97 @@ HUDConsoleObserver = {
 
       default:
         HUDService.reportPageError(CATEGORY_JS, aSubject);
         return;
     }
   }
 };
 
+/**
+ * A WebProgressListener that listens for location changes, to update HUDService
+ * state information on page navigation.
+ *
+ * @constructor
+ * @param string aHudId
+ *        The HeadsUpDisplay ID.
+ */
+function ConsoleProgressListener(aHudId)
+{
+  this.hudId = aHudId;
+}
+
+ConsoleProgressListener.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                         Ci.nsISupportsWeakReference]),
+
+  onStateChange: function CPL_onStateChange(aProgress, aRequest, aState,
+                                            aStatus)
+  {
+    if (!(aState & Ci.nsIWebProgressListener.STATE_START)) {
+      return;
+    }
+
+    let uri = null;
+    if (aRequest instanceof Ci.imgIRequest) {
+      let imgIRequest = aRequest.QueryInterface(Ci.imgIRequest);
+      uri = imgIRequest.URI;
+    }
+    else if (aRequest instanceof Ci.nsIChannel) {
+      let nsIChannel = aRequest.QueryInterface(Ci.nsIChannel);
+      uri = nsIChannel.URI;
+    }
+
+    if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) {
+      return;
+    }
+
+    let outputNode = HUDService.hudReferences[this.hudId].outputNode;
+
+    let chromeDocument = outputNode.ownerDocument;
+    let msgNode = chromeDocument.createElementNS(HTML_NS, "html:span");
+
+    // Create the clickable URL part of the message.
+    let linkNode = chromeDocument.createElementNS(HTML_NS, "html:span");
+    linkNode.appendChild(chromeDocument.createTextNode(uri.spec));
+    linkNode.classList.add("hud-clickable");
+    linkNode.classList.add("webconsole-msg-url");
+
+    linkNode.addEventListener("mousedown", function(aEvent) {
+      this._startX = aEvent.clientX;
+      this._startY = aEvent.clientY;
+    }, false);
+
+    linkNode.addEventListener("click", function(aEvent) {
+      if (aEvent.detail == 1 && aEvent.button == 0 &&
+          this._startX == aEvent.clientX && this._startY == aEvent.clientY) {
+        let viewSourceUtils = chromeDocument.defaultView.gViewSourceUtils;
+        viewSourceUtils.viewSource(uri.spec, null, chromeDocument);
+      }
+    }, false);
+
+    msgNode.appendChild(linkNode);
+
+    let messageNode = ConsoleUtils.createMessageNode(chromeDocument,
+                                                     CATEGORY_NETWORK,
+                                                     SEVERITY_LOG,
+                                                     msgNode,
+                                                     null,
+                                                     null,
+                                                     uri.spec);
+
+    ConsoleUtils.outputMessageNode(messageNode, this.hudId);
+  },
+
+  onLocationChange: function() {},
+  onStatusChange: function() {},
+  onProgressChange: function() {},
+  onSecurityChange: function() {},
+};
+
 ///////////////////////////////////////////////////////////////////////////
 // appName
 ///////////////////////////////////////////////////////////////////////////
 
 /**
  * Get the app's name so we can properly dispatch app-specific
  * methods per API call
  * @returns Gecko application name
--- a/toolkit/components/console/hudservice/PropertyPanel.jsm
+++ b/toolkit/components/console/hudservice/PropertyPanel.jsm
@@ -130,35 +130,66 @@ function presentableValueFor(aObject)
       return {
         type: TYPE_OBJECT,
         display: m ? m[1] : "Object"
       };
   }
 }
 
 /**
+ * Tells if the given function is native or not.
+ *
+ * @param function aFunction
+ *        The function you want to check if it is native or not.
+ *
+ * @return boolean
+ *         True if the given function is native, false otherwise.
+ */
+function isNativeFunction(aFunction)
+{
+  return typeof aFunction == "function" && !("prototype" in aFunction);
+}
+
+/**
  * Get an array of property name value pairs for the tree.
  *
  * @param object aObject
  *        The object to get properties for.
  * @returns array of object
  *          Objects have the name, value, display, type, children properties.
  */
 function namesAndValuesOf(aObject)
 {
   let pairs = [];
-  let value, presentable;
+  let value, presentable, getter;
+
+  let isDOMDocument = aObject instanceof Ci.nsIDOMDocument;
 
   for (var propName in aObject) {
-    try {
-      value = aObject[propName];
-      presentable = presentableValueFor(value);
+    // See bug 632275: skip deprecated width and height properties.
+    if (isDOMDocument && (propName == "width" || propName == "height")) {
+      continue;
     }
-    catch (ex) {
-      continue;
+
+    // Also skip non-native getters.
+    // TODO: implement a safer way to skip non-native getters. See bug 647235.
+    getter = aObject.__lookupGetter__ ?
+             aObject.__lookupGetter__(propName) : null;
+    if (getter && !isNativeFunction(getter)) {
+      value = ""; // Value is never displayed.
+      presentable = {type: TYPE_OTHER, display: "Getter"};
+    }
+    else {
+      try {
+        value = aObject[propName];
+        presentable = presentableValueFor(value);
+      }
+      catch (ex) {
+        continue;
+      }
     }
 
     let pair = {};
     pair.name = propName;
     pair.display = propName + ": " + presentable.display;
     pair.type = presentable.type;
     pair.value = value;
 
--- a/toolkit/components/console/hudservice/tests/browser/Makefile.in
+++ b/toolkit/components/console/hudservice/tests/browser/Makefile.in
@@ -123,19 +123,23 @@ include $(topsrcdir)/config/rules.mk
 	browser_webconsole_bug_618078_network_exceptions.js \
 	browser_webconsole_bug_613280_jsterm_copy.js \
 	browser_webconsole_bug_630733_response_redirect_headers.js \
 	browser_webconsole_bug_621644_jsterm_dollar.js \
 	browser_webconsole_bug_632817.js \
 	browser_webconsole_bug_611795.js \
 	browser_webconsole_bug_618311_close_panels.js \
 	browser_webconsole_bug_618311_private_browsing.js \
+	browser_webconsole_bug_626484_output_copy_order.js \
 	browser_webconsole_bug_632347_iterators_generators.js \
 	browser_webconsole_bug_642108_refForOutputNode.js \
 	browser_webconsole_bug_642108_pruneTest.js \
+	browser_webconsole_bug_585956_console_trace.js \
+	browser_webconsole_bug_595223_file_uri.js \
+	browser_webconsole_bug_632275_getters_document_width.js \
 	head.js \
 	$(NULL)
 
 _BROWSER_TEST_PAGES = \
 	test-console.html \
 	test-network.html \
 	test-network-request.html \
 	test-mutation.html \
@@ -192,15 +196,17 @@ include $(topsrcdir)/config/rules.mk
 	test-console-extras.html \
 	test-bug-603750-websocket.html \
 	test-bug-603750-websocket.js \
 	test-bug-599725-response-headers.sjs \
 	test-bug-618078-network-exceptions.html \
 	test-bug-630733-response-redirect-headers.sjs \
 	test-bug-621644-jsterm-dollar.html \
 	test-bug-632347-iterators-generators.html \
+	test-bug-585956-console-trace.html \
+	test-bug-632275-getters.html \
 	$(NULL)
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
 
 libs:: $(_BROWSER_TEST_PAGES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_585956_console_trace.js
@@ -0,0 +1,76 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** 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 Web Console test suite.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mihai Sucan <mihai.sucan@gmail.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 ***** */
+
+const TEST_URI = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-bug-585956-console-trace.html";
+
+function test() {
+  addTab(TEST_URI);
+  browser.addEventListener("load", tabLoaded, true);
+}
+
+function tabLoaded() {
+  browser.removeEventListener("load", tabLoaded, true);
+
+  openConsole();
+
+  browser.addEventListener("load", tabReloaded, true);
+  content.location.reload();
+}
+
+function tabReloaded() {
+  browser.removeEventListener("load", tabReloaded, true);
+
+  // The expected stack trace object.
+  let stacktrace = [
+    { filename: TEST_URI, lineNumber: 9, functionName: null, language: 2 },
+    { filename: TEST_URI, lineNumber: 14, functionName: "foobar585956b", language: 2 },
+    { filename: TEST_URI, lineNumber: 18, functionName: "foobar585956a", language: 2 },
+    { filename: TEST_URI, lineNumber: 21, functionName: null, language: 2 }
+  ];
+
+  let hudId = HUDService.getHudIdByWindow(content);
+  let HUD = HUDService.hudReferences[hudId];
+
+  let node = HUD.outputNode.querySelector(".hud-log");
+  ok(node, "found trace log node");
+  ok(node._stacktrace, "found stacktrace object");
+  is(node._stacktrace.toSource(), stacktrace.toSource(), "stacktrace is correct");
+  isnot(node.textContent.indexOf("bug-585956"), -1, "found file name");
+
+  finishTest();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_595223_file_uri.js
@@ -0,0 +1,79 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** 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 Web Console test suite.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mihai Sucan <mihai.sucan@gmail.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 ***** */
+
+const TEST_FILE = "test-network.html";
+
+function tabLoad(aEvent) {
+  browser.removeEventListener(aEvent.type, arguments.callee, true);
+
+  openConsole();
+
+  let hudId = HUDService.getHudIdByWindow(content);
+  hud = HUDService.hudReferences[hudId];
+
+  browser.addEventListener("load", tabReload, true);
+
+  content.location.reload();
+}
+
+function tabReload(aEvent) {
+  browser.removeEventListener(aEvent.type, arguments.callee, true);
+
+  let textContent = hud.outputNode.textContent;
+  isnot(textContent.indexOf("test-network.html"), -1,
+        "found test-network.html");
+  isnot(textContent.indexOf("test-image.png"), -1, "found test-image.png");
+  isnot(textContent.indexOf("testscript.js"), -1, "found testscript.js");
+  isnot(textContent.indexOf("running network console logging tests"), -1,
+        "found the console.log() message from testscript.js");
+
+  finishTest();
+}
+
+function test() {
+  let jar = getJar(getRootDirectory(gTestPath));
+  let dir = jar ?
+            extractJarToTmp(jar) :
+            getChromeDir(getResolvedURI(gTestPath));
+  dir.append(TEST_FILE);
+
+  let uri = Services.io.newFileURI(dir);
+
+  addTab(uri.spec);
+  browser.addEventListener("load", tabLoad, true);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_626484_output_copy_order.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+let itemsSet;
+
+function test() {
+  addTab("data:text/html,Web Console test for bug 626484");
+  browser.addEventListener("load", tabLoaded, true);
+}
+
+function tabLoaded(aEvent) {
+  browser.removeEventListener(aEvent.type, arguments.callee, true);
+  openConsole();
+
+  let console = browser.contentWindow.wrappedJSObject.console;
+  console.log("The first line.");
+  console.log("The second line.");
+  console.log("The last line.");
+
+  let hudId = HUDService.getHudIdByWindow(content);
+  HUD = HUDService.hudReferences[hudId];
+  outputNode = HUD.outputNode;
+
+  itemsSet = [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1],
+    [2, 1, 0]];
+
+  nextTest();
+}
+
+function nextTest() {
+  if (itemsSet.length === 0) {
+    outputNode.clearSelection();
+    HUD.jsterm.clearOutput();
+    finish();
+  }
+  else {
+    outputNode.clearSelection();
+    let items = itemsSet.shift();
+    items.forEach(function (index) {
+      outputNode.addItemToSelection(outputNode.getItemAtIndex(index));
+    });
+    outputNode.focus();
+    waitForClipboard(getExpectedClipboardText(items.length),
+      clipboardSetup, nextTest, nextTest);
+  }
+}
+
+function getExpectedClipboardText(aItemCount) {
+  let expectedClipboardText = [];
+  for (let i = 0; i < aItemCount; i++) {
+    let item = outputNode.getItemAtIndex(i);
+    expectedClipboardText.push("[" +
+      ConsoleUtils.timestampString(item.timestamp) + "] " +
+      item.clipboardText);
+  }
+  return expectedClipboardText.join("\n");
+}
+
+function clipboardSetup() {
+  goDoCommand("cmd_copy");
+}
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_632275_getters_document_width.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "http://example.com/browser/toolkit/components/console/hudservice/tests/browser/test-bug-632275-getters.html";
+
+function test() {
+  addTab(TEST_URI);
+  browser.addEventListener("load", tabLoaded, true);
+}
+
+function tabLoaded() {
+  browser.removeEventListener("load", tabLoaded, true);
+  openConsole();
+
+  let hudId = HUDService.getHudIdByWindow(content);
+  let HUD = HUDService.hudReferences[hudId];
+  let jsterm = HUD.jsterm;
+
+  let doc = content.wrappedJSObject.document;
+
+  let panel = jsterm.openPropertyPanel("Test1", doc);
+
+  let rows = panel.treeView._rows;
+  let find = function(regex) {
+    return rows.some(function(row) {
+      return regex.test(row.display);
+    });
+  };
+
+  ok(!find(/^(width|height):/), "no document.width/height");
+
+  panel.destroy();
+
+  let getterValue = doc.foobar._val;
+
+  panel = jsterm.openPropertyPanel("Test2", doc.foobar);
+  rows = panel.treeView._rows;
+
+  is(getterValue, doc.foobar._val, "getter did not execute");
+  is(getterValue+1, doc.foobar.val, "getter executed");
+  is(getterValue+1, doc.foobar._val, "getter executed (recheck)");
+
+  ok(find(/^val: Getter$/),
+     "getter is properly displayed");
+
+  ok(find(new RegExp("^_val: " + getterValue + "$")),
+     "getter _val is properly displayed");
+
+  panel.destroy();
+
+  executeSoon(function() {
+    let textContent = HUD.outputNode.textContent;
+    is(textContent.indexOf("document.body.client"), -1,
+       "no document.width/height warning displayed");
+
+    finishTest();
+  });
+}
--- a/toolkit/components/console/hudservice/tests/browser/browser_webconsole_js_input_expansion.js
+++ b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_js_input_expansion.js
@@ -88,12 +88,15 @@ function testJSInputExpansion() {
   let newerHeight = getHeight();
 
   ok(newerHeight > newHeight, "height changed: " + newerHeight);
 
   // Test if the inputNode shrinks again.
   input.value = "";
   EventUtils.synthesizeKey("d", {});
   let height = getHeight();
-  // is(height, initialHeight, "height shrank to original size");
+  info("height: " + height);
+  info("initialHeight: " + initialHeight);
+  let finalHeightDifference = Math.abs(initialHeight - height);
+  ok(finalHeightDifference <= 1, "height shrank to original size within 1px");
 
   finishTest();
 }
--- a/toolkit/components/console/hudservice/tests/browser/browser_webconsole_jsterm.js
+++ b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_jsterm.js
@@ -148,10 +148,15 @@ function testJSTerm()
 
   // check that pprint(function) shows function source, bug 618344
   jsterm.clearOutput();
   jsterm.execute("pprint(print)");
   label = jsterm.outputNode.querySelector(".webconsole-msg-output");
   isnot(label.textContent.indexOf("SEVERITY_LOG"), -1,
         "pprint(function) shows function source");
 
+  // check that an evaluated null produces "null", bug 650780
+  jsterm.clearOutput();
+  jsterm.execute("null");
+  checkResult("null", "null is null", 1);
+
   finishTest();
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/test-bug-585956-console-trace.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>Web Console test for bug 585956 - console.trace()</title>
+    <!-- Any copyright is dedicated to the Public Domain.
+         http://creativecommons.org/publicdomain/zero/1.0/ -->
+<script type="application/javascript">
+window.foobar585956c = function(a) {
+  console.trace();
+  return a+"c";
+};
+
+function foobar585956b(a) {
+  return foobar585956c(a+"b");
+}
+
+function foobar585956a(omg) {
+  return foobar585956b(omg + "a");
+}
+
+foobar585956a("omg");
+</script>
+  </head>
+  <body>
+    <p>Web Console test for bug 585956 - console.trace().</p>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/console/hudservice/tests/browser/test-bug-632275-getters.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>Web Console test for bug 632275 - getters</title>
+    <!-- Any copyright is dedicated to the Public Domain.
+         http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<script type="application/javascript;version=1.8">
+  document.foobar = {
+    _val: 5,
+    get val() { return ++this._val; }
+  };
+</script>
+
+  </head>
+  <body>
+    <p>Web Console test for bug 632275 - getters.</p>
+  </body>
+</html>
--- a/toolkit/components/console/hudservice/tests/browser/test-console-extras.html
+++ b/toolkit/components/console/hudservice/tests/browser/test-console-extras.html
@@ -6,17 +6,16 @@
         console.log("start");
         console.time();
         console.timeEnd()
         console.exception()
         console.assert()
         console.clear()
         console.dir()
         console.dirxml()
-        console.trace()
         console.group()
         console.groupCollapsed()
         console.groupEnd()
         console.profile()
         console.profileEnd()
         console.count()
         console.table()
         console.log("end");
--- a/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
+++ b/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
@@ -109,8 +109,19 @@ NetworkPanel.imageSizeDeltaDurationMS=%Sx%Spx, Δ%Sms
 # NetworkPanel. E.g. any kind of text is easy to display, but some audio or
 # flash data received from the server can't be displayed.
 #
 # The %S is replaced by the content type, that can't be displayed, examples are
 #  o application/x-shockwave-flash
 #  o music/crescendo
 NetworkPanel.responseBodyUnableToDisplay.content=Unable to display responses of type "%S"
 ConsoleAPIDisabled=The Web Console logging API (console.log, console.info, console.warn, console.error) has been disabled by a script on this page.
+
+# LOCALIZATION NOTE (stacktrace.anonymousFunction):
+# This string is used to display JavaScript functions that have no given name -
+# they are said to be anonymous. See stacktrace.outputMessage.
+stacktrace.anonymousFunction=<anonymous>
+
+# LOCALIZATION NOTE (stacktrace.outputMessage):
+# This string is used in the Web Console output to identify a web developer call
+# to console.trace(). The stack trace of JavaScript function calls is displayed.
+# In this minimal message we only show the last call.
+stacktrace.outputMessage=Stack trace from %S, function %S, line %S.