Merge fx-team to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 20 Oct 2012 17:15:57 -0400
changeset 111016 4957ebb0625a1f1390928b06f37bd90f9aace47d
parent 111005 034634c4125ae8bfd14f9aa16b5df813c16a136b (current diff)
parent 111015 0c34dd4742b6a0581009894955fb739693e6474e (diff)
child 111024 ddca56ec6bfcec57c6739f49f5eee3796ab91e8e
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
milestone19.0a1
Merge fx-team to m-c.
--- a/b2g/chrome/content/dbg-browser-actors.js
+++ b/b2g/chrome/content/dbg-browser-actors.js
@@ -112,17 +112,17 @@ DeviceTabActor.prototype.grip = function
   let response = {
     'actor': this.actorID,
     'title': this.browser.title,
     'url': this.browser.document.documentURI
   };
 
   // Walk over tab actors added by extensions and add them to a new ActorPool.
   let actorPool = new ActorPool(this.conn);
-  this._createExtraActors(DebuggerServer.globalActorFactories, actorPool);
+  this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
   if (!actorPool.isEmpty()) {
     this._tabActorPool = actorPool;
     this.conn.addActorPool(this._tabActorPool);
   }
 
   this._appendExtraActors(response);
   return response;
 };
--- a/browser/base/content/browser-appmenu.inc
+++ b/browser/base/content/browser-appmenu.inc
@@ -147,16 +147,17 @@
           </menupopup>
       </splitmenu>
       <menuseparator class="appmenu-menuseparator"/>
       <menu id="appmenu_webDeveloper"
             label="&appMenuWebDeveloper.label;">
         <menupopup id="appmenu_webDeveloper_popup">
           <menuitem id="appmenu_devToolbar" observes="devtoolsMenuBroadcaster_DevToolbar"/>
           <menuitem id="appmenu_webConsole" observes="devtoolsMenuBroadcaster_WebConsole"/>
+          <menuitem id="appmenu_remoteWebConsole" observes="devtoolsMenuBroadcaster_RemoteWebConsole"/>
           <menuitem id="appmenu_pageinspect" observes="devtoolsMenuBroadcaster_Inspect"/>
           <menuitem id="appmenu_responsiveUI" observes="devtoolsMenuBroadcaster_ResponsiveUI"/>
           <menuitem id="appmenu_debugger" observes="devtoolsMenuBroadcaster_Debugger"/>
           <menuitem id="appmenu_remoteDebugger" observes="devtoolsMenuBroadcaster_RemoteDebugger"/>
           <menuitem id="appmenu_chromeDebugger" observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
           <menuitem id="appmenu_scratchpad" observes="devtoolsMenuBroadcaster_Scratchpad"/>
           <menuitem id="appmenu_styleeditor" observes="devtoolsMenuBroadcaster_StyleEditor"/>
           <menuitem id="appmenu_pageSource" observes="devtoolsMenuBroadcaster_PageSource"/>
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -529,16 +529,17 @@
 #endif
               <menuseparator id="devToolsSeparator"/>
               <menu id="webDeveloperMenu"
                     label="&webDeveloperMenu.label;"
                     accesskey="&webDeveloperMenu.accesskey;">
                 <menupopup id="menuWebDeveloperPopup">
                   <menuitem id="menu_devToolbar" observes="devtoolsMenuBroadcaster_DevToolbar" accesskey="&devToolbarMenu.accesskey;"/>
                   <menuitem id="webConsole" observes="devtoolsMenuBroadcaster_WebConsole" accesskey="&webConsoleCmd.accesskey;"/>
+                  <menuitem id="menu_remoteWebConsole" observes="devtoolsMenuBroadcaster_RemoteWebConsole"/>
                   <menuitem id="menu_pageinspect" observes="devtoolsMenuBroadcaster_Inspect" accesskey="&inspectMenu.accesskey;"/>
                   <menuitem id="menu_responsiveUI" observes="devtoolsMenuBroadcaster_ResponsiveUI" accesskey="&responsiveDesignTool.accesskey;"/>
                   <menuitem id="menu_debugger" observes="devtoolsMenuBroadcaster_Debugger" accesskey="&debuggerMenu.accesskey;"/>
                   <menuitem id="menu_remoteDebugger" observes="devtoolsMenuBroadcaster_RemoteDebugger"/>
                   <menuitem id="menu_chromeDebugger" observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
                   <menuitem id="menu_scratchpad" observes="devtoolsMenuBroadcaster_Scratchpad" accesskey="&scratchpad.accesskey;"/>
                   <menuitem id="menu_styleeditor" observes="devtoolsMenuBroadcaster_StyleEditor" accesskey="&styleeditor.accesskey;"/>
                   <menuitem id="menu_pageSource" observes="devtoolsMenuBroadcaster_PageSource" accesskey="&pageSourceCmd.accesskey;"/>
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -86,16 +86,17 @@
     <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:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
     <command id="Tools:WebConsole" oncommand="HUDConsoleUI.toggleHUD();"/>
+    <command id="Tools:RemoteWebConsole" oncommand="HUDConsoleUI.toggleRemoteHUD();" disabled="true" hidden="true"/>
     <command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();"/>
     <command id="Tools:Debugger" oncommand="DebuggerUI.toggleDebugger();" disabled="true" hidden="true"/>
     <command id="Tools:RemoteDebugger" oncommand="DebuggerUI.toggleRemoteDebugger();" disabled="true" hidden="true"/>
     <command id="Tools:ChromeDebugger" oncommand="DebuggerUI.toggleChromeDebugger();" disabled="true" hidden="true"/>
     <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true" hidden="true"/>
     <command id="Tools:StyleEditor" oncommand="StyleEditor.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
@@ -198,16 +199,20 @@
                  type="checkbox" autocheck="false"
                  command="Tools:DevToolbar"
                  key="key_devToolbar"/>
     <broadcaster id="devtoolsMenuBroadcaster_WebConsole"
                  label="&webConsoleCmd.label;"
                  type="checkbox" autocheck="false"
                  key="key_webConsole"
                  command="Tools:WebConsole"/>
+    <broadcaster id="devtoolsMenuBroadcaster_RemoteWebConsole"
+                 label="&remoteWebConsoleCmd.label;"
+                 type="checkbox" autocheck="false"
+                 command="Tools:RemoteWebConsole"/>
     <broadcaster id="devtoolsMenuBroadcaster_Inspect"
                  label="&inspectMenu.label;"
                  type="checkbox" autocheck="false"
                  command="Tools:Inspect"
                  key="key_inspect"/>
     <broadcaster id="devtoolsMenuBroadcaster_Debugger"
                  label="&debuggerMenu.label2;"
                  type="checkbox" autocheck="false"
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1441,16 +1441,20 @@ var gBrowserInit = {
     }
 
     // Enable Remote Debugger?
     let enabled = gPrefService.getBoolPref("devtools.debugger.remote-enabled");
     if (enabled) {
       let cmd = document.getElementById("Tools:RemoteDebugger");
       cmd.removeAttribute("disabled");
       cmd.removeAttribute("hidden");
+
+      cmd = document.getElementById("Tools:RemoteWebConsole");
+      cmd.removeAttribute("disabled");
+      cmd.removeAttribute("hidden");
     }
 
     // Enable Chrome Debugger?
     let enabled = gPrefService.getBoolPref("devtools.chrome.enabled") &&
                   gPrefService.getBoolPref("devtools.debugger.chrome-enabled") &&
                   gPrefService.getBoolPref("devtools.debugger.remote-enabled");
     if (enabled) {
       let cmd = document.getElementById("Tools:ChromeDebugger");
--- a/browser/devtools/scratchpad/scratchpad-manager.jsm
+++ b/browser/devtools/scratchpad/scratchpad-manager.jsm
@@ -18,16 +18,17 @@ Cu.import("resource://gre/modules/Servic
 
 /**
  * The ScratchpadManager object opens new Scratchpad windows and manages the state
  * of open scratchpads for session restore. There's only one ScratchpadManager in
  * the life of the browser.
  */
 var ScratchpadManager = {
 
+  _nextUid: 1,
   _scratchpads: [],
 
   /**
    * Get the saved states of open scratchpad windows. Called by
    * session restore.
    *
    * @return array
    *         The array of scratchpad states.
@@ -84,26 +85,30 @@ var ScratchpadManager = {
    *        Optional. The initial state of the scratchpad, an object
    *        with properties filename, text, and executionContext.
    *
    * @return nsIDomWindow
    *         The opened scratchpad window.
    */
   openScratchpad: function SPM_openScratchpad(aState)
   {
-    let params = null;
+    let params = Cc["@mozilla.org/embedcomp/dialogparam;1"]
+                 .createInstance(Ci.nsIDialogParamBlock);
+
+    params.SetNumberStrings(2);
+    params.SetString(0, JSON.stringify(this._nextUid++));
+
     if (aState) {
       if (typeof aState != 'object') {
         return;
       }
-      params = Cc["@mozilla.org/embedcomp/dialogparam;1"]
-               .createInstance(Ci.nsIDialogParamBlock);
-      params.SetNumberStrings(1);
-      params.SetString(0, JSON.stringify(aState));
+
+      params.SetString(1, JSON.stringify(aState));
     }
+
     let win = Services.ww.openWindow(null, SCRATCHPAD_WINDOW_URL, "_blank",
                                      SCRATCHPAD_WINDOW_FEATURES, params);
     // Only add the shutdown observer if we've opened a scratchpad window.
     ShutdownObserver.init();
 
     return win;
   }
 };
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -36,16 +36,17 @@ const BUTTON_POSITION_SAVE = 0;
 const BUTTON_POSITION_CANCEL = 1;
 const BUTTON_POSITION_DONT_SAVE = 2;
 const BUTTON_POSITION_REVERT=0;
 
 /**
  * The scratchpad object handles the Scratchpad window functionality.
  */
 var Scratchpad = {
+  _instanceId: null,
   _initialWindowTitle: document.title,
 
   /**
    * 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
@@ -200,16 +201,25 @@ var Scratchpad = {
   },
 
   /**
    * Cached Cu.Sandbox object for the active tab content window object.
    */
   _contentSandbox: null,
 
   /**
+   * Unique name for the current Scratchpad instance. Used to distinguish
+   * Scratchpad windows between each other. See bug 661762.
+   */
+  get uniqueName()
+  {
+    return "Scratchpad/" + this._instanceId;
+  },
+
+  /**
    * 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 location in the current tab of the current
    * browser window and it is reset for each context switch,
    * navigator:browser window switch, tab switch or navigation.
    */
   get contentSandbox()
   {
@@ -220,17 +230,17 @@ var Scratchpad = {
     }
 
     if (!this._contentSandbox ||
         this.browserWindow != this._previousBrowserWindow ||
         this._previousBrowser != this.gBrowser.selectedBrowser ||
         this._previousLocation != this.gBrowser.contentWindow.location.href) {
       let contentWindow = this.gBrowser.selectedBrowser.contentWindow;
       this._contentSandbox = new Cu.Sandbox(contentWindow,
-        { sandboxPrototype: contentWindow, wantXrays: false, 
+        { sandboxPrototype: contentWindow, wantXrays: false,
           sandboxName: 'scratchpad-content'});
       this._contentSandbox.__SCRATCHPAD__ = this;
 
       this._previousBrowserWindow = this.browserWindow;
       this._previousBrowser = this.gBrowser.selectedBrowser;
       this._previousLocation = contentWindow.location.href;
     }
 
@@ -255,17 +265,17 @@ var Scratchpad = {
       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, 
+        { sandboxPrototype: this.browserWindow, wantXrays: false,
           sandboxName: 'scratchpad-chrome'});
       this._chromeSandbox.__SCRATCHPAD__ = this;
       addDebuggerToGlobal(this._chromeSandbox);
 
       this._previousBrowserWindow = this.browserWindow;
     }
 
     return this._chromeSandbox;
@@ -312,17 +322,17 @@ var Scratchpad = {
    * @return mixed
    *         The script evaluation result.
    */
   evalInContentSandbox: function SP_evalInContentSandbox(aString)
   {
     let error, result;
     try {
       result = Cu.evalInSandbox(aString, this.contentSandbox, "1.8",
-                                "Scratchpad", 1);
+                                this.uniqueName, 1);
     }
     catch (ex) {
       error = ex;
     }
 
     return [error, result];
   },
 
@@ -334,17 +344,17 @@ var Scratchpad = {
    * @return mixed
    *         The script evaluation result.
    */
   evalInChromeSandbox: function SP_evalInChromeSandbox(aString)
   {
     let error, result;
     try {
       result = Cu.evalInSandbox(aString, this.chromeSandbox, "1.8",
-                                "Scratchpad", 1);
+                                this.uniqueName, 1);
     }
     catch (ex) {
       error = ex;
     }
 
     return [error, result];
   },
 
@@ -671,18 +681,16 @@ var Scratchpad = {
     let promptCallback = function(aFile) {
       this.promptSave(function(aCloseFile, aSaved, aStatus) {
         let shouldOpen = aCloseFile;
         if (aSaved && !Components.isSuccessCode(aStatus)) {
           shouldOpen = false;
         }
 
         if (shouldOpen) {
-          this._skipClosePrompt = true;
-
           let file;
           if (aFile) {
             file = aFile;
           } else {
             file = Components.classes["@mozilla.org/file/local;1"].
                    createInstance(Components.interfaces.nsILocalFile);
             let filePath = this.getRecentFiles()[aIndex];
             file.initWithPath(filePath);
@@ -1082,38 +1090,49 @@ var Scratchpad = {
    *
    * @param nsIDOMEvent aEvent
    */
   onLoad: function SP_onLoad(aEvent)
   {
     if (aEvent.target != document) {
       return;
     }
+
     let chrome = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED);
     if (chrome) {
       let environmentMenu = document.getElementById("sp-environment-menu");
       let errorConsoleCommand = document.getElementById("sp-cmd-errorConsole");
       let chromeContextCommand = document.getElementById("sp-cmd-browserContext");
       environmentMenu.removeAttribute("hidden");
       chromeContextCommand.removeAttribute("disabled");
       errorConsoleCommand.removeAttribute("disabled");
     }
 
-    let state = null;
-
     let initialText = this.strings.formatStringFromName(
       "scratchpadIntro1",
       [LayoutHelpers.prettyKey(document.getElementById("sp-key-run")),
        LayoutHelpers.prettyKey(document.getElementById("sp-key-inspect")),
        LayoutHelpers.prettyKey(document.getElementById("sp-key-display"))],
       3);
 
-    if ("arguments" in window &&
-         window.arguments[0] instanceof Ci.nsIDialogParamBlock) {
-      state = JSON.parse(window.arguments[0].GetString(0));
+    let args = window.arguments;
+
+    if (args && args[0] instanceof Ci.nsIDialogParamBlock) {
+      args = args[0];
+    } else {
+      // If this Scratchpad window doesn't have any arguments, horrible
+      // things might happen so we need to report an error.
+      Cu.reportError(this.strings. GetStringFromName("scratchpad.noargs"));
+    }
+
+    this._instanceId = args.GetString(0);
+
+    let state = args.GetString(1) || null;
+    if (state) {
+      state = JSON.parse(state);
       this.setState(state);
       initialText = state.text;
     }
 
     this.editor = new SourceEditor();
 
     let config = {
       mode: SourceEditor.MODES.JAVASCRIPT,
@@ -1212,18 +1231,23 @@ var Scratchpad = {
    */
   onUnload: function SP_onUnload(aEvent)
   {
     if (aEvent.target != document) {
       return;
     }
 
     this.resetContext();
-    this.gBrowser.selectedBrowser.removeEventListener("load",
-        this._reloadAndRunEvent, true);
+
+    // This event is created only after user uses 'reload and run' feature.
+    if (this._reloadAndRunEvent) {
+      this.gBrowser.selectedBrowser.removeEventListener("load",
+          this._reloadAndRunEvent, true);
+    }
+
     this.editor.removeEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
                                     this._onDirtyChanged);
     PreferenceObserver.uninit();
 
     this.editor.destroy();
     this.editor = null;
     this.initialized = false;
   },
@@ -1277,35 +1301,24 @@ var Scratchpad = {
     return true;
   },
 
   /**
    * Handler for window close event. Prompts to save scratchpad if
    * there are unsaved changes.
    *
    * @param nsIDOMEvent aEvent
+   * @param function aCallback
+   *        Optional function you want to call when file is saved/closed.
+   *        Used mainly for tests.
    */
-  onClose: function SP_onClose(aEvent)
+  onClose: function SP_onClose(aEvent, aCallback)
   {
-    if (this._skipClosePrompt) {
-      return;
-    }
-
-    this.promptSave(function(aShouldClose, aSaved, aStatus) {
-      let shouldClose = aShouldClose;
-      if (aSaved && !Components.isSuccessCode(aStatus)) {
-        shouldClose = false;
-      }
-
-      if (shouldClose) {
-        this._skipClosePrompt = true;
-        window.close();
-      }
-    }.bind(this));
     aEvent.preventDefault();
+    this.close(aCallback);
   },
 
   /**
    * Close the scratchpad window. Prompts before closing if the scratchpad
    * has unsaved changes.
    *
    * @param function aCallback
    *        Optional function you want to call when file is saved
@@ -1314,17 +1327,16 @@ var Scratchpad = {
   {
     this.promptSave(function(aShouldClose, aSaved, aStatus) {
       let shouldClose = aShouldClose;
       if (aSaved && !Components.isSuccessCode(aStatus)) {
         shouldClose = false;
       }
 
       if (shouldClose) {
-        this._skipClosePrompt = true;
         window.close();
       }
       if (aCallback) {
         aCallback();
       }
     }.bind(this));
   },
 
--- a/browser/devtools/scratchpad/test/Makefile.in
+++ b/browser/devtools/scratchpad/test/Makefile.in
@@ -30,11 +30,12 @@ MOCHITEST_BROWSER_FILES = \
 		browser_scratchpad_bug690552_display_outputs_errors.js \
 		browser_scratchpad_bug650345_find_ui.js \
 		browser_scratchpad_bug714942_goto_line_ui.js \
 		browser_scratchpad_bug_650760_help_key.js \
 		browser_scratchpad_bug_651942_recent_files.js \
 		browser_scratchpad_bug756681_display_non_error_exceptions.js \
 		browser_scratchpad_bug_751744_revert_to_saved.js \
 		browser_scratchpad_bug740948_reload_and_run.js \
+		browser_scratchpad_bug_661762_wrong_window_focus.js \
 		head.js \
 
 include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug690552_display_outputs_errors.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug690552_display_outputs_errors.js
@@ -31,23 +31,25 @@ function runTests()
   scratchpad.display();
   is(scratchpad.getText(),
       message + openComment + "Hello World!" + closeComment,
       "message display output");
 
   scratchpad.setText(error);
   scratchpad.display();
   is(scratchpad.getText(), 
-      error + openComment + "Exception: Ouch!\n@Scratchpad:1" + closeComment,
+      error + openComment +
+      "Exception: Ouch!\n@" + scratchpad.uniqueName + ":1" + closeComment,
       "error display output");
 
   scratchpad.setText(message);
   scratchpad.run();
   is(scratchpad.getText(), message, "message run output");
 
   scratchpad.setText(error);
   scratchpad.run();
   is(scratchpad.getText(), 
-      error + openComment + "Exception: Ouch!\n@Scratchpad:1" + closeComment,
+      error + openComment +
+      "Exception: Ouch!\n@" + scratchpad.uniqueName + ":1" + closeComment,
       "error display output");
 
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug756681_display_non_error_exceptions.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug756681_display_non_error_exceptions.js
@@ -36,17 +36,18 @@ function runTests()
   is(scratchpad.getText(),
       message + openComment + "Hello World!" + closeComment,
       "message display output");
 
   // Display error1, throw new Error("Ouch")
   scratchpad.setText(error1);
   scratchpad.display();
   is(scratchpad.getText(),
-      error1 + openComment + "Exception: Ouch!\n@Scratchpad:1" + closeComment,
+      error1 + openComment +
+      "Exception: Ouch!\n@" + scratchpad.uniqueName + ":1" + closeComment,
       "error display output");
 
   // Display error2, throw "A thrown string"
   scratchpad.setText(error2);
   scratchpad.display();
   is(scratchpad.getText(),
       error2 + openComment + "Exception: A thrown string" + closeComment,
       "thrown string display output");
@@ -70,17 +71,18 @@ function runTests()
   scratchpad.setText(message);
   scratchpad.run();
   is(scratchpad.getText(), message, "message run output");
 
   // Run error1, throw new Error("Ouch")
   scratchpad.setText(error1);
   scratchpad.run();
   is(scratchpad.getText(),
-      error1 + openComment + "Exception: Ouch!\n@Scratchpad:1" + closeComment,
+      error1 + openComment +
+      "Exception: Ouch!\n@" + scratchpad.uniqueName + ":1" + closeComment,
       "error run output");
 
   // Run error2, throw "A thrown string"
   scratchpad.setText(error2);
   scratchpad.run();
   is(scratchpad.getText(),
       error2 + openComment + "Exception: A thrown string" + closeComment,
       "thrown string run output");
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_653427_confirm_close.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_653427_confirm_close.js
@@ -4,17 +4,17 @@
 
 let tempScope = {};
 Cu.import("resource://gre/modules/NetUtil.jsm", tempScope);
 Cu.import("resource://gre/modules/FileUtils.jsm", tempScope);
 let NetUtil = tempScope.NetUtil;
 let FileUtils = tempScope.FileUtils;
 
 // only finish() when correct number of tests are done
-const expected = 5;
+const expected = 6;
 var count = 0;
 function done()
 {
   if (++count == expected) {
     cleanup();
     finish();
   }
 }
@@ -64,16 +64,17 @@ function testSavedFile()
       done();
     });
   }, {noFocus: true});
 }
 
 function testUnsaved()
 {
   testUnsavedFileCancel();
+  testCancelAfterLoad();
   testUnsavedFileSave();
   testUnsavedFileDontSave();
 }
 
 function testUnsavedFileCancel()
 {
   openScratchpad(function(win) {
     win.Scratchpad.setFilename("test.js");
@@ -84,16 +85,44 @@ function testUnsavedFileCancel()
     win.Scratchpad.close(function() {
       ok(!win.closed, "cancelling dialog shouldn't close scratchpad");
       win.close();
       done();
     });
   }, {noFocus: true});
 }
 
+// Test a regression where our confirmation dialog wasn't appearing
+// after openFile calls. See bug 801982.
+function testCancelAfterLoad()
+{
+  openScratchpad(function(win) {
+    win.Scratchpad.setRecentFile(gFile);
+    win.Scratchpad.openFile(0);
+    win.Scratchpad.editor.dirty = true;
+    promptButton = win.BUTTON_POSITION_CANCEL;
+
+    let EventStub = {
+      called: false,
+      preventDefault: function() {
+        EventStub.called = true;
+      }
+    };
+
+    win.Scratchpad.onClose(EventStub, function() {
+      ok(!win.closed, "cancelling dialog shouldn't close scratchpad");
+      ok(EventStub.called, "aEvent.preventDefault was called");
+
+      win.Scratchpad.editor.dirty = false;
+      win.close();
+      done();
+    });
+  }, {noFocus: true});
+}
+
 function testUnsavedFileSave()
 {
   openScratchpad(function(win) {
     win.Scratchpad.importFromFile(gFile, true, function(status, content) {
       win.Scratchpad.setFilename(gFile.path);
 
       let text = "new text";
       win.Scratchpad.setText(text);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_661762_wrong_window_focus.js
@@ -0,0 +1,93 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let tempScope = {};
+Cu.import("resource:///modules/HUDService.jsm", tempScope);
+let HUDService = tempScope.HUDService;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  // To test for this bug we open a Scratchpad window, save its
+  // reference and then open another one. This way the first window
+  // loses its focus.
+  //
+  // Then we open a web console and execute a console.log statement
+  // from the first Scratch window (that's why we needed to save its
+  // reference).
+  //
+  // Then we wait for our message to appear in the console and click
+  // on the location link. After that we check which Scratchpad window
+  // is currently active (it should be the older one).
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+
+    openScratchpad(function () {
+      let sw = gScratchpadWindow;
+
+      openScratchpad(function () {
+        function onWebConsoleOpen(subj) {
+          Services.obs.removeObserver(onWebConsoleOpen,
+            "web-console-created");
+          subj.QueryInterface(Ci.nsISupportsString);
+
+          let hud = HUDService.getHudReferenceById(subj.data);
+          hud.jsterm.clearOutput(true);
+          executeSoon(testFocus.bind(null, sw, hud));
+        }
+
+        Services.obs.
+          addObserver(onWebConsoleOpen, "web-console-created", false);
+
+        HUDService.consoleUI.toggleHUD();
+      });
+    });
+  }, true);
+
+  content.location = "data:text/html;charset=utf8,<p>test window focus for Scratchpad.";
+}
+
+function testFocus(sw, hud) {
+  let sp = sw.Scratchpad;
+
+  function onMessage(subj) {
+    Services.obs.removeObserver(onMessage, "web-console-message-created");
+
+    var loc = hud.jsterm.outputNode.querySelector(".webconsole-location");
+    ok(loc, "location element exists");
+    is(loc.value, sw.Scratchpad.uniqueName + ":1",
+        "location value is correct");
+
+    sw.addEventListener("focus", function onFocus() {
+      sw.removeEventListener("focus", onFocus, true);
+
+      let win = Services.wm.getMostRecentWindow("devtools:scratchpad");
+
+      ok(win, "there are active Scratchpad windows");
+      is(win.Scratchpad.uniqueName, sw.Scratchpad.uniqueName,
+          "correct window is in focus");
+
+      // gScratchpadWindow will be closed automatically but we need to
+      // close the second window ourselves.
+      sw.close();
+      finish();
+    }, true);
+
+    // Simulate a click on the "Scratchpad/N:1" link.
+    EventUtils.synthesizeMouse(loc, 2, 2, {}, hud.iframeWindow);
+  }
+
+  // Sending messages to web console is an asynchronous operation. That's
+  // why we have to setup an observer here.
+  Services.obs.addObserver(onMessage, "web-console-message-created", false);
+
+  sp.setText("console.log('foo');");
+  let [selection, error, result] = sp.run();
+  is(selection, "console.log('foo');", "selection is correct");
+  is(error, undefined, "error is correct");
+  is(result, undefined, "result is correct");
+}
--- a/browser/devtools/scratchpad/test/browser_scratchpad_open.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_open.js
@@ -1,38 +1,53 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // only finish() when correct number of tests are done
 const expected = 3;
 var count = 0;
+var lastUniqueName = null;
 
 function done()
 {
   if (++count == expected) {
     finish();
   }
 }
 
 function test()
 {
   waitForExplicitFinish();
   testOpen();
   testOpenWithState();
   testOpenInvalidState();
 }
 
+function testUniqueName(name)
+{
+  ok(name, "Scratchpad has a uniqueName");
+
+  if (lastUniqueName === null) {
+    lastUniqueName = name;
+    return;
+  }
+
+  ok(name !== lastUniqueName,
+      "Unique name for this instance differs from the last one.");
+}
+
 function testOpen()
 {
   openScratchpad(function(win) {
     is(win.Scratchpad.filename, undefined, "Default filename is undefined");
     isnot(win.Scratchpad.getText(), null, "Default text should not be null");
     is(win.Scratchpad.executionContext, win.SCRATCHPAD_CONTEXT_CONTENT,
       "Default execution context is content");
+    testUniqueName(win.Scratchpad.uniqueName);
 
     win.close();
     done();
   }, {noFocus: true});
 }
 
 function testOpenWithState()
 {
@@ -41,16 +56,17 @@ function testOpenWithState()
     executionContext: 2,
     text: "test text"
   };
 
   openScratchpad(function(win) {
     is(win.Scratchpad.filename, state.filename, "Filename loaded from state");
     is(win.Scratchpad.executionContext, state.executionContext, "Execution context loaded from state");
     is(win.Scratchpad.getText(), state.text, "Content loaded from state");
+    testUniqueName(win.Scratchpad.uniqueName);
 
     win.close();
     done();
   }, {state: state, noFocus: true});
 }
 
 function testOpenInvalidState()
 {
--- a/browser/devtools/sourceeditor/source-editor-orion.jsm
+++ b/browser/devtools/sourceeditor/source-editor-orion.jsm
@@ -1226,38 +1226,55 @@ SourceEditor.prototype = {
     }
 
     let caretOffset = this.getCaretOffset() - 1;
     let matchingIndex = this._getMatchingBracketIndex(caretOffset);
 
     // If the caret is not at the closing bracket "}", find the index of the
     // opening bracket "{" for the current code block.
     if (matchingIndex == -1 || matchingIndex > caretOffset) {
+      matchingIndex = -1;
       let text = this.getText();
       let closingOffset = text.indexOf("}", caretOffset);
       while (closingOffset > -1) {
         let closingMatchingIndex = this._getMatchingBracketIndex(closingOffset);
         if (closingMatchingIndex < caretOffset && closingMatchingIndex != -1) {
           matchingIndex = closingMatchingIndex;
           break;
         }
         closingOffset = text.indexOf("}", closingOffset + 1);
       }
+      // Moving to the previous code block starting bracket if caret not inside
+      // any code block.
+      if (matchingIndex == -1) {
+        let lastClosingOffset = text.lastIndexOf("}", caretOffset);
+        while (lastClosingOffset > -1) {
+          let closingMatchingIndex =
+            this._getMatchingBracketIndex(lastClosingOffset);
+          if (closingMatchingIndex < caretOffset &&
+              closingMatchingIndex != -1) {
+            matchingIndex = closingMatchingIndex;
+            break;
+          }
+          lastClosingOffset = text.lastIndexOf("}", lastClosingOffset - 1);
+        }
+      }
     }
 
     if (matchingIndex > -1) {
-      this.setCaretOffset(matchingIndex);
+      this.setCaretOffset(matchingIndex + 1);
     }
 
     return true;
   },
 
   /**
-   * Moves the cursor to the matching closing bracket if at corresponding opening
-   * bracket, otherwise move to the closing bracket for the current block of code.
+   * Moves the cursor to the matching closing bracket if at corresponding
+   * opening bracket, otherwise move to the closing bracket for the current
+   * block of code.
    *
    * @private
    */
   _moveToBracketClosing: function SE__moveToBracketClosing()
   {
     let mode = this.getMode();
     // Returning early if not in JavaScipt or CSS mode.
     if (mode != SourceEditor.MODES.JAVASCRIPT &&
@@ -1266,26 +1283,41 @@ SourceEditor.prototype = {
     }
 
     let caretOffset = this.getCaretOffset();
     let matchingIndex = this._getMatchingBracketIndex(caretOffset - 1);
 
     // If the caret is not at the opening bracket "{", find the index of the
     // closing bracket "}" for the current code block.
     if (matchingIndex == -1 || matchingIndex < caretOffset) {
+      matchingIndex = -1;
       let text = this.getText();
       let openingOffset = text.lastIndexOf("{", caretOffset);
       while (openingOffset > -1) {
         let openingMatchingIndex = this._getMatchingBracketIndex(openingOffset);
         if (openingMatchingIndex > caretOffset) {
           matchingIndex = openingMatchingIndex;
           break;
         }
         openingOffset = text.lastIndexOf("{", openingOffset - 1);
       }
+      // Moving to the next code block ending bracket if caret not inside
+      // any code block.
+      if (matchingIndex == -1) {
+        let nextOpeningIndex = text.indexOf("{", caretOffset + 1);
+        while (nextOpeningIndex > -1) {
+          let openingMatchingIndex =
+            this._getMatchingBracketIndex(nextOpeningIndex);
+          if (openingMatchingIndex > caretOffset) {
+            matchingIndex = openingMatchingIndex;
+            break;
+          }
+          nextOpeningIndex = text.indexOf("{", nextOpeningIndex + 1);
+        }
+      }
     }
 
     if (matchingIndex > -1) {
       this.setCaretOffset(matchingIndex);
     }
 
     return true;
   },
--- a/browser/devtools/sourceeditor/test/Makefile.in
+++ b/browser/devtools/sourceeditor/test/Makefile.in
@@ -25,11 +25,12 @@ MOCHITEST_BROWSER_FILES = \
 		browser_bug707987_debugger_breakpoints.js \
 		browser_bug712982_line_ruler_click.js \
 		browser_bug725618_moveLines_shortcut.js \
 		browser_bug700893_dirty_state.js \
 		browser_bug729480_line_vertical_align.js \
 		browser_bug725430_comment_uncomment.js \
 		browser_bug731721_debugger_stepping.js \
 		browser_bug729960_block_bracket_jump.js \
+		browser_bug744021_next_prev_bracket_jump.js \
 		head.js \
 
 include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/sourceeditor/test/browser_bug729960_block_bracket_jump.js
+++ b/browser/devtools/sourceeditor/test/browser_bug729960_block_bracket_jump.js
@@ -53,39 +53,39 @@ function test() {
 
     // Setting caret at Line 1 bracket start.
     editor.setCaretOffset(19);
     EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
     is(editor.getCaretOffset(), 220,
        "JS : Jump to closing bracket of the code block when caret at block start");
 
     EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
-    is(editor.getCaretOffset(), 19,
+    is(editor.getCaretOffset(), 20,
        "JS : Jump to opening bracket of the code block when caret at block end");
 
     // Setting caret at Line 10 start.
     editor.setCaretOffset(161);
     EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
-    is(editor.getCaretOffset(), 19,
+    is(editor.getCaretOffset(), 20,
        "JS : Jump to opening bracket of code block when inside the function");
 
     editor.setCaretOffset(161);
     EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
     is(editor.getCaretOffset(), 220,
        "JS : Jump to closing bracket of code block when inside the function");
 
     // Setting caret at Line 6 start.
     editor.setCaretOffset(67);
     EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
     is(editor.getCaretOffset(), 159,
        "JS : Jump to closing bracket in a nested function with caret inside");
 
     editor.setCaretOffset(67);
     EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
-    is(editor.getCaretOffset(), 61,
+    is(editor.getCaretOffset(), 62,
        "JS : Jump to opening bracket in a nested function with caret inside");
 
     let CSSText = "#object {\n" +
                   "  property: value;\n" +
                   "  /* comment */\n" +
                   "}";
 
     editor.setMode(SourceEditor.MODES.CSS);
@@ -93,23 +93,23 @@ function test() {
 
     // Setting caret at Line 1 bracket start.
     editor.setCaretOffset(8);
     EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
     is(editor.getCaretOffset(), 45,
        "CSS : Jump to closing bracket of the code block when caret at block start");
 
     EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
-    is(editor.getCaretOffset(), 8,
+    is(editor.getCaretOffset(), 9,
        "CSS : Jump to opening bracket of the code block when caret at block end");
 
     // Setting caret at Line 3 start.
     editor.setCaretOffset(28);
     EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
-    is(editor.getCaretOffset(), 8,
+    is(editor.getCaretOffset(), 9,
        "CSS : Jump to opening bracket of code block when inside the function");
 
     editor.setCaretOffset(28);
     EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
     is(editor.getCaretOffset(), 45,
        "CSS : Jump to closing bracket of code block when inside the function");
 
     let HTMLText = "<html>\n" +
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_bug744021_next_prev_bracket_jump.js
@@ -0,0 +1,104 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function test() {
+
+  let temp = {};
+  Cu.import("resource:///modules/source-editor.jsm", temp);
+  let SourceEditor = temp.SourceEditor;
+
+  waitForExplicitFinish();
+
+  let editor;
+
+  const windowUrl = "data:text/xml;charset=utf8,<?xml version='1.0'?><window " +
+    "xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' " +
+    "title='test for bug 744021' width='600' height='500'><hbox flex='1'/>" +
+    "</window>";
+  const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable," +
+    "dialog=no";
+
+  let testWin = Services.ww.openWindow(null, windowUrl, "_blank",
+                                       windowFeatures, null);
+  testWin.addEventListener("load", function onWindowLoad() {
+    testWin.removeEventListener("load", onWindowLoad, false);
+    waitForFocus(initEditor, testWin);
+  }, false);
+
+  function initEditor()
+  {
+    let hbox = testWin.document.querySelector("hbox");
+    editor = new SourceEditor();
+    editor.init(hbox, {showLineNumbers: true}, editorLoaded);
+  }
+
+  function editorLoaded()
+  {
+    editor.focus();
+    let JSText = "function foo() {\n" +
+                 "  \n" +
+                 "  function level2() {\n" +
+                 "    \n" +
+                 "    function level3() {\n" +
+                 "      \n" +
+                 "    }\n" +
+                 "  }\n" +
+                 "  function bar() { /* Block Level 2 */ }\n" +
+                 "}\n" +
+                 "function baz() {\n" +
+                 "  \n" +
+                 "}";
+
+    editor.setMode(SourceEditor.MODES.JAVASCRIPT);
+    editor.setText(JSText);
+
+    // Setting caret at end of line 11 (function baz() {).
+    editor.setCaretOffset(147);
+    EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
+    is(editor.getCaretOffset(), 16,
+       "JS : Jump to opening bracket of previous sibling block when no parent");
+
+    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    is(editor.getCaretOffset(), 129,
+       "JS : Jump to closing bracket of same code block");
+
+    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    is(editor.getCaretOffset(), 151,
+       "JS : Jump to closing bracket of next sibling code block");
+
+    let CSSText = "#object1 {\n" +
+                  "  property: value;\n" +
+                  "  /* comment */\n" +
+                  "}\n" +
+                  ".class1 {\n" +
+                  "  property: value;\n" +
+                  "}";
+
+    editor.setMode(SourceEditor.MODES.CSS);
+    editor.setText(CSSText);
+
+    // Setting caret at Line 5 end (.class1 {).
+    editor.setCaretOffset(57);
+    EventUtils.synthesizeKey("[", {accelKey: true}, testWin);
+    is(editor.getCaretOffset(), 10,
+       "CSS : Jump to opening bracket of previous sibling code block");
+
+    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    is(editor.getCaretOffset(), 46,
+       "CSS : Jump to closing bracket of same code block");
+
+    EventUtils.synthesizeKey("]", {accelKey: true}, testWin);
+    is(editor.getCaretOffset(), 77,
+       "CSS : Jump to closing bracket of next sibling code block");
+
+    editor.destroy();
+
+    testWin.close();
+    testWin = editor = null;
+
+    waitForFocus(finish, window);
+  }
+}
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -108,36 +108,43 @@ HUD_SERVICE.prototype =
 
   /**
    * Activate a HeadsUpDisplay for the given tab context.
    *
    * @param nsIDOMElement aTab
    *        The xul:tab element.
    * @param boolean aAnimated
    *        True if you want to animate the opening of the Web console.
+   * @param object aOptions
+   *        Options for the Web Console:
+   *        - host
+   *          Server to connect to.
+   *        - port
+   *          Port to connect to.
    * @return object
    *         The new HeadsUpDisplay instance.
    */
-  activateHUDForContext: function HS_activateHUDForContext(aTab, aAnimated)
+  activateHUDForContext:
+  function HS_activateHUDForContext(aTab, aAnimated, aOptions)
   {
     let hudId = "hud_" + aTab.linkedPanel;
     if (hudId in this.hudReferences) {
       return this.hudReferences[hudId];
     }
 
     this.wakeup();
 
     let window = aTab.ownerDocument.defaultView;
     let gBrowser = window.gBrowser;
 
     gBrowser.tabContainer.addEventListener("TabClose", this.onTabClose, false);
     gBrowser.tabContainer.addEventListener("TabSelect", this.onTabSelect, false);
     window.addEventListener("unload", this.onWindowUnload, false);
 
-    let hud = new WebConsole(aTab);
+    let hud = new WebConsole(aTab, aOptions);
     this.hudReferences[hudId] = hud;
 
     if (!aAnimated || hud.consolePanel) {
       this.disableAnimation(hudId);
     }
 
     HeadsUpDisplayUICommands.refreshCommand();
 
@@ -488,23 +495,29 @@ HUD_SERVICE.prototype =
  * A WebConsole instance is an interactive console initialized *per tab*
  * that displays console log data as well as provides an interactive terminal to
  * manipulate the current tab's document content.
  *
  * This object only wraps the iframe that holds the Web Console UI.
  *
  * @param nsIDOMElement aTab
  *        The xul:tab for which you want the WebConsole object.
+ * @param object aOptions
+ *        Web Console options: host and port, for the remote Web console.
  */
-function WebConsole(aTab)
+function WebConsole(aTab, aOptions = {})
 {
   this.tab = aTab;
   this.chromeDocument = this.tab.ownerDocument;
   this.chromeWindow = this.chromeDocument.defaultView;
   this.hudId = "hud_" + this.tab.linkedPanel;
+
+  this.remoteHost = aOptions.host;
+  this.remotePort = aOptions.port;
+
   this._onIframeLoad = this._onIframeLoad.bind(this);
   this._initUI();
 }
 
 WebConsole.prototype = {
   /**
    * The xul:tab for which the current Web Console instance was created.
    * @type nsIDOMElement
@@ -1012,17 +1025,18 @@ var HeadsUpDisplayUICommands = {
     let command = window.document.getElementById("Tools:WebConsole");
     if (this.getOpenHUD() != null) {
       command.setAttribute("checked", true);
     } else {
       command.setAttribute("checked", false);
     }
   },
 
-  toggleHUD: function UIC_toggleHUD() {
+  toggleHUD: function UIC_toggleHUD(aOptions)
+  {
     var window = HUDService.currentContext();
     var gBrowser = window.gBrowser;
     var linkedBrowser = gBrowser.selectedTab.linkedBrowser;
     var tabId = gBrowser.getNotificationBox(linkedBrowser).getAttribute("id");
     var hudId = "hud_" + tabId;
     var ownerDocument = gBrowser.selectedTab.ownerDocument;
     var hud = ownerDocument.getElementById(hudId);
     var hudRef = HUDService.hudReferences[hudId];
@@ -1041,21 +1055,62 @@ var HeadsUpDisplayUICommands = {
           // case gracefully.
           if (ownerDocument.getElementById(hudId)) {
             HUDService.deactivateHUDForContext(gBrowser.selectedTab, true);
           }
         });
       }
     }
     else {
-      HUDService.activateHUDForContext(gBrowser.selectedTab, true);
+      HUDService.activateHUDForContext(gBrowser.selectedTab, true, aOptions);
       HUDService.animate(hudId, ANIMATE_IN);
     }
   },
 
+  toggleRemoteHUD: function UIC_toggleRemoteHUD()
+  {
+    if (this.getOpenHUD()) {
+      this.toggleHUD();
+      return;
+    }
+
+    let host = Services.prefs.getCharPref("devtools.debugger.remote-host");
+    let port = Services.prefs.getIntPref("devtools.debugger.remote-port");
+
+    let check = { value: false };
+    let input = { value: host + ":" + port };
+
+    let result = Services.prompt.prompt(null,
+      l10n.getStr("remoteWebConsolePromptTitle"),
+      l10n.getStr("remoteWebConsolePromptMessage"),
+      input, null, check);
+
+    if (!result) {
+      return;
+    }
+
+    let parts = input.value.split(":");
+    if (parts.length != 2) {
+      return;
+    }
+
+    [host, port] = parts;
+    if (!host.length || !port.length) {
+      return;
+    }
+
+    Services.prefs.setCharPref("devtools.debugger.remote-host", host);
+    Services.prefs.setIntPref("devtools.debugger.remote-port", port);
+
+    this.toggleHUD({
+      host: host,
+      port: port,
+    });
+  },
+
   /**
    * 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();
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_595934_message_categories.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_595934_message_categories.js
@@ -147,16 +147,17 @@ function testNext() {
   foundCategory = false;
   foundText = false;
   pageLoaded = false;
   pageError = false;
 
   pos++;
   if (pos < TESTS.length) {
     waitForSuccess({
+      timeout: 10000,
       name: "test #" + pos + " successful finish",
       validatorFn: function()
       {
         return foundCategory && foundText && pageLoaded && pageError;
       },
       successFn: testNext,
       failureFn: function() {
         info("foundCategory " + foundCategory + " foundText " + foundText +
@@ -206,15 +207,17 @@ function onDOMNodeInserted(aEvent) {
   let textContent = output.textContent;
   foundText = textContent.indexOf(TESTS[pos].matchString) > -1;
   if (foundText) {
     ok(foundText, "test #" + pos + ": message found '" + TESTS[pos].matchString + "'");
   }
 }
 
 function test() {
+  requestLongerTimeout(2);
+
   addTab("data:text/html;charset=utf-8,Web Console test for bug 595934 - message categories coverage.");
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, consoleOpened);
   }, true);
 }
 
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -16,16 +16,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
                                   "resource://gre/modules/devtools/dbg-server.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
                                   "resource://gre/modules/devtools/dbg-client.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "debuggerSocketConnect",
+                                  "resource://gre/modules/devtools/dbg-client.jsm");
+
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
                                    "@mozilla.org/widget/clipboardhelper;1",
                                    "nsIClipboardHelper");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PropertyPanel",
                                   "resource:///modules/PropertyPanel.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PropertyTreeView",
@@ -355,18 +358,21 @@ WebConsoleFrame.prototype = {
   },
 
   /**
    * Connect to the server using the remote debugging protocol.
    * @private
    */
   _initConnection: function WCF__initConnection()
   {
-    this.proxy = new WebConsoleConnectionProxy(this);
-    this.proxy.initServer();
+    this.proxy = new WebConsoleConnectionProxy(this, {
+      host: this.owner.remoteHost,
+      port: this.owner.remotePort,
+    });
+
     this.proxy.connect(function() {
       this.saveRequestAndResponseBodies = this._saveRequestAndResponseBodies;
       this._onInitComplete();
     }.bind(this));
   },
 
   /**
    * Find the Web Console UI elements and setup event listeners as needed.
@@ -954,34 +960,54 @@ WebConsoleFrame.prototype = {
 
     this.mergeFilteredMessageNode(dupe, aNode);
 
     return true;
   },
 
   /**
    * Filter the console node from the output node if it is a repeat. Console
-   * messages are filtered from the output if and only if they match the
-   * immediately preceding message. The output node's last occurrence should
-   * have its timestamp updated.
+   * messages are filtered from the output if they match the immediately
+   * preceding message that came from the same source. The output node's
+   * last occurrence should have its timestamp updated.
    *
    * @param nsIDOMNode aNode
    *        The message node to be filtered or not.
    * @return boolean
    *         True if the message is filtered, false otherwise.
    */
   filterRepeatedConsole: function WCF_filterRepeatedConsole(aNode)
   {
     let lastMessage = this.outputNode.lastChild;
 
-    // childNodes[2] is the description element
-    if (lastMessage && lastMessage.childNodes[2] &&
-        !aNode.classList.contains("webconsole-msg-inspector") &&
-        aNode.childNodes[2].textContent ==
-        lastMessage.childNodes[2].textContent) {
+    if (!lastMessage) {
+      return false;
+    }
+
+    let body = aNode.querySelector(".webconsole-msg-body");
+    let lastBody = lastMessage.querySelector(".webconsole-msg-body");
+
+    if (aNode.classList.contains("webconsole-msg-inspector")) {
+      return false;
+    }
+
+    if (!body || !lastBody) {
+      return false;
+    }
+
+    if (body.textContent == lastBody.textContent) {
+      let loc = aNode.querySelector(".webconsole-location");
+      let lastLoc = lastMessage.querySelector(".webconsole-location");
+
+      if (loc && lastLoc) {
+        if (loc.getAttribute("value") !== lastLoc.getAttribute("value")) {
+          return false;
+        }
+      }
+
       this.mergeFilteredMessageNode(lastMessage, aNode);
       return true;
     }
 
     return false;
   },
 
   /**
@@ -2362,36 +2388,51 @@ WebConsoleFrame.prototype = {
    * @return nsIDOMNode
    *         The new XUL label node, ready to be added to the message node.
    */
   createLocationNode: function WCF_createLocationNode(aSourceURL, aSourceLine)
   {
     let locationNode = this.document.createElementNS(XUL_NS, "label");
 
     // Create the text, which consists of an abbreviated version of the URL
-    // plus an optional line number.
-    let text = WebConsoleUtils.abbreviateSourceURL(aSourceURL);
+    // plus an optional line number. Scratchpad URLs should not be abbreviated.
+    let text;
+
+    if (/^Scratchpad\/\d+$/.test(aSourceURL)) {
+      text = aSourceURL;
+    }
+    else {
+      text = WebConsoleUtils.abbreviateSourceURL(aSourceURL);
+    }
+
     if (aSourceLine) {
       text += ":" + aSourceLine;
     }
+
     locationNode.setAttribute("value", text);
 
     // Style appropriately.
     locationNode.setAttribute("crop", "center");
     locationNode.setAttribute("title", aSourceURL);
     locationNode.setAttribute("tooltiptext", aSourceURL);
     locationNode.classList.add("webconsole-location");
     locationNode.classList.add("text-link");
 
     // Make the location clickable.
     locationNode.addEventListener("click", function() {
-      if (aSourceURL == "Scratchpad") {
-        let win = Services.wm.getMostRecentWindow("devtools:scratchpad");
-        if (win) {
-          win.focus();
+      if (/^Scratchpad\/\d+$/.test(aSourceURL)) {
+        let wins = Services.wm.getEnumerator("devtools:scratchpad");
+
+        while (wins.hasMoreElements()) {
+          let win = wins.getNext();
+
+          if (win.Scratchpad.uniqueName === aSourceURL) {
+            win.focus();
+            return;
+          }
         }
       }
       else if (locationNode.parentNode.category == CATEGORY_CSS) {
         this.owner.viewSourceInStyleEditor(aSourceURL, aSourceLine);
       }
       else {
         this.owner.viewSource(aSourceURL, aSourceLine);
       }
@@ -3854,20 +3895,24 @@ CommandController.prototype = {
 
 /**
  * The WebConsoleConnectionProxy handles the connection between the Web Console
  * and the application we connect to through the remote debug protocol.
  *
  * @constructor
  * @param object aWebConsole
  *        The Web Console instance that owns this connection proxy.
+ * @param object aOptions
+ *        Connection options: host and port.
  */
-function WebConsoleConnectionProxy(aWebConsole)
+function WebConsoleConnectionProxy(aWebConsole, aOptions = {})
 {
   this.owner = aWebConsole;
+  this.remoteHost = aOptions.host;
+  this.remotePort = aOptions.port;
 
   this._onPageError = this._onPageError.bind(this);
   this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
   this._onNetworkEvent = this._onNetworkEvent.bind(this);
   this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
   this._onFileActivity = this._onFileActivity.bind(this);
   this._onLocationChange = this._onLocationChange.bind(this);
 }
@@ -3933,38 +3978,86 @@ WebConsoleConnectionProxy.prototype = {
   /**
    * Initialize a debugger client and connect it to the debugger server.
    *
    * @param function [aCallback]
    *        Optional function to invoke when connection is established.
    */
   connect: function WCCP_connect(aCallback)
   {
-    let transport = DebuggerServer.connectPipe();
+    let transport;
+    if (this.remoteHost) {
+      transport = debuggerSocketConnect(this.remoteHost, this.remotePort);
+    }
+    else {
+      this.initServer();
+      transport = DebuggerServer.connectPipe();
+    }
+
     let client = this.client = new DebuggerClient(transport);
 
     client.addListener("pageError", this._onPageError);
     client.addListener("consoleAPICall", this._onConsoleAPICall);
     client.addListener("networkEvent", this._onNetworkEvent);
     client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
     client.addListener("fileActivity", this._onFileActivity);
     client.addListener("locationChange", this._onLocationChange);
 
+    client.connect(function(aType, aTraits) {
+      client.listTabs(this._onListTabs.bind(this, aCallback));
+    }.bind(this));
+  },
+
+  /**
+   * The "listTabs" response handler.
+   *
+   * @private
+   * @param function [aCallback]
+   *        Optional function to invoke once the connection is established.
+   * @param object aResponse
+   *        The JSON response object received from the server.
+   */
+  _onListTabs: function WCCP__onListTabs(aCallback, aResponse)
+  {
+    let selectedTab;
+
+    if (this.remoteHost) {
+      let tabs = [];
+      for (let tab of aResponse.tabs) {
+        tabs.push(tab.title);
+      }
+
+      tabs.push(l10n.getStr("listTabs.globalConsoleActor"));
+
+      let selected = {};
+      let result = Services.prompt.select(null,
+        l10n.getStr("remoteWebConsoleSelectTabTitle"),
+        l10n.getStr("remoteWebConsoleSelectTabMessage"),
+        tabs.length, tabs, selected);
+
+      if (result && selected.value < aResponse.tabs.length) {
+        selectedTab = aResponse.tabs[selected.value];
+      }
+    }
+    else {
+      selectedTab = aResponse.tabs[aResponse.selected];
+    }
+
+    if (selectedTab) {
+      this._consoleActor = selectedTab.consoleActor;
+      this.owner.onLocationChange(selectedTab.url, selectedTab.title);
+    }
+    else {
+      this._consoleActor = aResponse.consoleActor;
+    }
+
     let listeners = ["PageError", "ConsoleAPI", "NetworkActivity",
                      "FileActivity", "LocationChange"];
-
-    client.connect(function(aType, aTraits) {
-      client.listTabs(function(aResponse) {
-        let tab = aResponse.tabs[aResponse.selected];
-        this._consoleActor = tab.consoleActor;
-        this.owner.onLocationChange(tab.url, tab.title);
-        client.attachConsole(tab.consoleActor, listeners,
-                             this._onAttachConsole.bind(this, aCallback));
-      }.bind(this));
-    }.bind(this));
+    this.client.attachConsole(this._consoleActor, listeners,
+                              this._onAttachConsole.bind(this, aCallback));
   },
 
   /**
    * The "attachConsole" response handler.
    *
    * @private
    * @param function [aCallback]
    *        Optional function to invoke once the connection is established.
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -202,16 +202,17 @@ These should match what Safari and other
 
 <!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 remoteWebConsoleCmd.label    "Remote Web Console">
 
 <!ENTITY inspectMenu.label            "Inspect">
 <!ENTITY inspectMenu.accesskey        "I">
 <!ENTITY inspectMenu.commandkey       "I">
 
 <!ENTITY inspectContextMenu.label     "Inspect Element">
 <!ENTITY inspectContextMenu.accesskey "Q">
 
--- a/browser/locales/en-US/chrome/browser/devtools/scratchpad.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/scratchpad.properties
@@ -66,15 +66,20 @@ confirmRevert=Do you want to revert the 
 # you try to revert unsaved content of scratchpad.
 confirmRevert.title=Revert Changes
 
 # LOCALIZATION NOTE  (scratchpadIntro): This is a multi-line comment explaining
 # how to use the Scratchpad. Note that this should be a valid JavaScript
 # comment inside /* and */.
 scratchpadIntro1=/*\n * This is a JavaScript Scratchpad.\n *\n * Enter some JavaScript, then Right Click or choose from the Execute Menu:\n * 1. Run to evaluate the selected text (%1$S),\n * 2. Inspect to bring up an Object Inspector on the result (%2$S), or,\n * 3. Display to insert the result in a comment after the selection. (%3$S)\n */\n\n
 
+# LOCALIZATION NOTE  (scratchpad.noargs): This error message is shown when
+# Scratchpad instance is created without any arguments. Scratchpad window
+# expects to receive its unique identifier as the first window argument.
+scratchpad.noargs=Scratchpad was created without any arguments.
+
 # LOCALIZATION NOTE  (notification.browserContext): This is the message displayed
 # over the top of the editor when the user has switched to browser context.
 browserContext.notification=This scratchpad executes in the Browser context.
 
 # LOCALIZATION NOTE (help.openDocumentationPage): This returns a localized link with
 # documentation for Scratchpad on MDN.
 help.openDocumentationPage=https://developer.mozilla.org/en/Tools/Scratchpad
--- a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
@@ -140,8 +140,28 @@ Autocomplete.blank=  <- no result
 
 maxTimersExceeded=The maximum allowed number of timers in this page was exceeded.
 
 # LOCALIZATION NOTE (JSTerm.updateNotInspectable):
 # This string is used when the user inspects an evaluation result in the Web
 # Console and tries the Update button, but the new result no longer returns an
 # object that can be inspected.
 JSTerm.updateNotInspectable=After your input has been re-evaluated the result is no longer inspectable.
+
+# LOCALIZATION NOTE (remoteWebConsolePromptTitle): The title displayed on the
+# Web Console prompt asking for the remote host and port to connect to.
+remoteWebConsolePromptTitle=Remote Connection
+
+# LOCALIZATION NOTE (remoteWebConsolePromptMessage): The message displayed on the
+# Web Console prompt asking for the remote host and port to connect to.
+remoteWebConsolePromptMessage=Enter hostname and port number (host:port)
+
+# LOCALIZATION NOTE (remoteWebConsoleSelectTabTitle): The title displayed on the
+# Web Console prompt asking the user to pick a tab to attach to.
+remoteWebConsoleSelectTabTitle=Tab list - Remote Connection
+
+# LOCALIZATION NOTE (remoteWebConsoleSelectTabMessage): The message displayed on the
+# Web Console prompt asking the user to pick a tab to attach to.
+remoteWebConsoleSelectTabMessage=Select one of the tabs you want to attach to, or select the global console.
+
+# LOCALIZATION NOTE (listTabs.globalConsoleActor): The string displayed for the
+# global console in the tabs selection.
+listTabs.globalConsoleActor=*Global Console*
--- a/toolkit/devtools/webconsole/NetworkHelper.jsm
+++ b/toolkit/devtools/webconsole/NetworkHelper.jsm
@@ -207,43 +207,37 @@ var NetworkHelper =
   /**
    * Gets the nsIDOMWindow that is associated with aRequest.
    *
    * @param nsIHttpChannel aRequest
    * @returns nsIDOMWindow or null
    */
   getWindowForRequest: function NH_getWindowForRequest(aRequest)
   {
-    let loadContext = this.getRequestLoadContext(aRequest);
-    if (loadContext) {
-      return loadContext.associatedWindow;
-    }
+    try {
+      return this.getRequestLoadContext(aRequest).associatedWindow;
+    } catch (ex) { }
     return null;
   },
 
   /**
    * Gets the nsILoadContext that is associated with aRequest.
    *
    * @param nsIHttpChannel aRequest
    * @returns nsILoadContext or null
    */
   getRequestLoadContext: function NH_getRequestLoadContext(aRequest)
   {
-    if (aRequest && aRequest.notificationCallbacks) {
-      try {
-        return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext);
-      } catch (ex) { }
-    }
+    try {
+      return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext);
+    } catch (ex) { }
 
-    if (aRequest && aRequest.loadGroup
-                 && aRequest.loadGroup.notificationCallbacks) {
-      try {
-        return aRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
-      } catch (ex) { }
-    }
+    try {
+      return aRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
+    } catch (ex) { }
 
     return null;
   },
 
   /**
    * Loads the content of aUrl from the cache.
    *
    * @param string aUrl
--- a/toolkit/devtools/webconsole/WebConsoleUtils.jsm
+++ b/toolkit/devtools/webconsole/WebConsoleUtils.jsm
@@ -2585,26 +2585,26 @@ NetworkMonitor.prototype = {
 
 /**
  * A WebProgressListener that listens for location changes.
  *
  * This progress listener is used to track file loads and other kinds of
  * location changes.
  *
  * @constructor
- * @param object aBrowser
- *        The xul:browser for which we need to track location changes.
+ * @param object aWindow
+ *        The window for which we need to track location changes.
  * @param object aOwner
  *        The listener owner which needs to implement two methods:
  *        - onFileActivity(aFileURI)
  *        - onLocationChange(aState, aTabURI, aPageTitle)
  */
-function ConsoleProgressListener(aBrowser, aOwner)
+function ConsoleProgressListener(aWindow, aOwner)
 {
-  this.browser = aBrowser;
+  this.window = aWindow;
   this.owner = aOwner;
 }
 
 ConsoleProgressListener.prototype = {
   /**
    * Constant used for startMonitor()/stopMonitor() that tells you want to
    * monitor file loads.
    */
@@ -2632,31 +2632,38 @@ ConsoleProgressListener.prototype = {
 
   /**
    * Tells if the console progress listener is initialized or not.
    * @private
    * @type boolean
    */
   _initialized: false,
 
+  _webProgress: null,
+
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                          Ci.nsISupportsWeakReference]),
 
   /**
    * Initialize the ConsoleProgressListener.
    * @private
    */
   _init: function CPL__init()
   {
     if (this._initialized) {
       return;
     }
 
+    this._webProgress = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
+                        .getInterface(Ci.nsIWebNavigation)
+                        .QueryInterface(Ci.nsIWebProgress);
+    this._webProgress.addProgressListener(this,
+                                          Ci.nsIWebProgress.NOTIFY_STATE_ALL);
+
     this._initialized = true;
-    this.browser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_ALL);
   },
 
   /**
    * Start a monitor/tracker related to the current nsIWebProgressListener
    * instance.
    *
    * @param number aMonitor
    *        Tells what you want to track. Available constants:
@@ -2763,28 +2770,26 @@ ConsoleProgressListener.prototype = {
   function CPL__checkLocationChange(aProgress, aRequest, aState, aStatus)
   {
     let isStart = aState & Ci.nsIWebProgressListener.STATE_START;
     let isStop = aState & Ci.nsIWebProgressListener.STATE_STOP;
     let isNetwork = aState & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
     let isWindow = aState & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
 
     // Skip non-interesting states.
-    if (!isNetwork || !isWindow ||
-        aProgress.DOMWindow != this.browser.contentWindow) {
+    if (!isNetwork || !isWindow || aProgress.DOMWindow != this.window) {
       return;
     }
 
     if (isStart && aRequest instanceof Ci.nsIChannel) {
       this.owner.onLocationChange("start", aRequest.URI.spec, "");
     }
     else if (isStop) {
-      let window = this.browser.contentWindow;
-      this.owner.onLocationChange("stop", window.location.href,
-                                  window.document.title);
+      this.owner.onLocationChange("stop", this.window.location.href,
+                                  this.window.document.title);
     }
   },
 
   onLocationChange: function() {},
   onStatusChange: function() {},
   onProgressChange: function() {},
   onSecurityChange: function() {},
 
@@ -2796,21 +2801,25 @@ ConsoleProgressListener.prototype = {
     if (!this._initialized) {
       return;
     }
 
     this._initialized = false;
     this._fileActivity = false;
     this._locationChange = false;
 
-    if (this.browser.removeProgressListener) {
-      this.browser.removeProgressListener(this);
+    try {
+      this._webProgress.removeProgressListener(this);
+    }
+    catch (ex) {
+      // This can throw during browser shutdown.
     }
 
-    this.browser = null;
+    this._webProgress = null;
+    this.window = null;
     this.owner = null;
   },
 };
 
 function gSequenceId()
 {
   return gSequenceId.n++;
 }
--- a/toolkit/devtools/webconsole/dbg-webconsole-actors.js
+++ b/toolkit/devtools/webconsole/dbg-webconsole-actors.js
@@ -42,25 +42,30 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 /**
  * The WebConsoleActor implements capabilities needed for the Web Console
  * feature.
  *
  * @constructor
  * @param object aConnection
  *        The connection to the client, DebuggerServerConnection.
- * @param object [aTabActor]
- *        Optional, the parent tab actor. This must be an instance of
- *        BrowserTabActor.
+ * @param object [aParentActor]
+ *        Optional, the parent actor.
  */
-function WebConsoleActor(aConnection, aTabActor)
+function WebConsoleActor(aConnection, aParentActor)
 {
   this.conn = aConnection;
-  if (aTabActor instanceof BrowserTabActor) {
-    this._browser = aTabActor.browser;
+
+  if (aParentActor instanceof BrowserTabActor &&
+      aParentActor.browser instanceof Ci.nsIDOMWindow) {
+    this._window = aParentActor.browser;
+  }
+  else if (aParentActor instanceof BrowserTabActor &&
+           aParentActor.browser instanceof Ci.nsIDOMElement) {
+    this._window = aParentActor.browser.contentWindow;
   }
   else {
     this._window = Services.wm.getMostRecentWindow("navigator:browser");
     this._isGlobalActor = true;
   }
 
   this._objectActorsPool = new ActorPool(this.conn);
   this.conn.addActorPool(this._objectActorsPool);
@@ -69,24 +74,16 @@ function WebConsoleActor(aConnection, aT
   this.conn.addActorPool(this._networkEventActorsPool);
 
   this._prefs = {};
 }
 
 WebConsoleActor.prototype =
 {
   /**
-   * The xul:browser we work with. This is only available when the Web Console
-   * actor is a tab actor.
-   * @private
-   * @type nsIDOMElement
-   */
-  _browser: null,
-
-  /**
    * Tells if this Web Console actor is a global actor or not.
    * @private
    * @type boolean
    */
   _isGlobalActor: false,
 
   /**
    * Actor pool for all of the object actors for objects we send to the client.
@@ -132,17 +129,17 @@ WebConsoleActor.prototype =
    * @type object
    */
   conn: null,
 
   /**
    * The content window we work with.
    * @type nsIDOMWindow
    */
-  get window() this._browser ? this._browser.contentWindow : this._window,
+  get window() this._window,
 
   _window: null,
 
   /**
    * The PageErrorListener instance.
    * @type object
    */
   pageErrorListener: null,
@@ -215,17 +212,17 @@ WebConsoleActor.prototype =
       this.consoleProgressListener.destroy();
       this.consoleProgressListener = null;
     }
     this.conn.removeActorPool(this._objectActorsPool);
     this.conn.removeActorPool(this._networkEventActorsPool);
     this._objectActorsPool = null;
     this._networkEventActorsPool = null;
     this._sandboxLocation = this.sandbox = null;
-    this.conn = this._browser = this._window = null;
+    this.conn = this._window = null;
   },
 
   /**
    * Create a grip for the given value. If the value is an object,
    * a WebConsoleObjectActor will be created.
    *
    * @param mixed aValue
    * @return object
@@ -327,39 +324,33 @@ WebConsoleActor.prototype =
           if (!this.networkMonitor) {
             this.networkMonitor =
               new NetworkMonitor(window, this);
             this.networkMonitor.init();
           }
           startedListeners.push(listener);
           break;
         case "FileActivity":
-          if (this._isGlobalActor) {
-            // The ConsoleProgressListener cannot listen for global events.
-            // See bug 798764.
-            break;
-          }
           if (!this.consoleProgressListener) {
             this.consoleProgressListener =
-              new ConsoleProgressListener(this._browser, this);
+              new ConsoleProgressListener(this.window, this);
           }
           this.consoleProgressListener.startMonitor(this.consoleProgressListener.
                                                     MONITOR_FILE_ACTIVITY);
           startedListeners.push(listener);
           break;
         case "LocationChange":
-          if (this._isGlobalActor) {
-            break;
-          }
           if (!this.consoleProgressListener) {
             this.consoleProgressListener =
-              new ConsoleProgressListener(this._browser, this);
+              new ConsoleProgressListener(this.window, this);
           }
           this.consoleProgressListener.startMonitor(this.consoleProgressListener.
                                                     MONITOR_LOCATION_CHANGE);
+          startedListeners.push(listener);
+          break;
       }
     }
     return {
       startedListeners: startedListeners,
       nativeConsoleAPI: this.hasNativeConsoleAPI(),
     };
   },
 
--- a/toolkit/devtools/webconsole/test/Makefile.in
+++ b/toolkit/devtools/webconsole/test/Makefile.in
@@ -14,14 +14,15 @@ MOCHITEST_CHROME_FILES = \
     test_basics.html \
     test_cached_messages.html \
     test_page_errors.html \
     test_consoleapi.html \
     test_jsterm.html \
     test_object_actor.html \
     test_network_get.html \
     test_network_post.html \
+    test_file_uri.html \
     network_requests_iframe.html \
     data.json \
     common.js \
     $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_file_uri.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for file activity tracking</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript;version=1.8" src="common.js"></script>
+  <!-- Any copyright is dedicated to the Public Domain.
+     - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for file activity tracking</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+let gState;
+let gTmpFile;
+
+function doFileActivity()
+{
+  info("doFileActivity");
+  let fileContent = "<p>hello world from bug 798764";
+
+  gTmpFile = FileUtils.getFile("TmpD", ["bug798764.html"]);
+  gTmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+
+  let fout = FileUtils.openSafeFileOutputStream(gTmpFile,
+    FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE);
+
+  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+                  createInstance(Ci.nsIScriptableUnicodeConverter);
+  converter.charset = "UTF-8";
+  let fileContentStream = converter.convertToInputStream(fileContent);
+
+  NetUtil.asyncCopy(fileContentStream, fout, addIframe);
+}
+
+function addIframe(aStatus)
+{
+  ok(Components.isSuccessCode(aStatus),
+     "the temporary file was saved successfully");
+
+  let iframe = document.createElement("iframe");
+  iframe.src = NetUtil.newURI(gTmpFile).spec;
+  document.body.appendChild(iframe);
+}
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+
+  attachConsole(["FileActivity"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+  gState = aState;
+  gState.dbgClient.addListener("fileActivity", onFileActivity);
+  doFileActivity();
+}
+
+function onFileActivity(aType, aPacket)
+{
+  is(aPacket.from, gState.actor, "fileActivity actor");
+
+  gState.dbgClient.removeListener("fileActivity", onFileActivity);
+
+  ok(/bug798764\.html$/.test(aPacket.uri), "file URI match");
+
+  testEnd();
+}
+
+function testEnd()
+{
+  if (gTmpFile) {
+    gTmpFile.remove(false);
+    gTmpFile = null;
+  }
+
+  if (gState) {
+    closeDebugger(gState, function() {
+      gState = null;
+      SimpleTest.finish();
+    });
+  } else {
+    SimpleTest.finish();
+  }
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>