Bug 583041 - Style Editor integration; part 1; r=rcampbell
authorCedric Vivier <cedricv@neonux.com>
Tue, 15 Nov 2011 16:41:37 +0800
changeset 80386 00e06ace79460572b3ce4eb4327c3b55d185eeec
parent 80385 a43d24581da011cd7cdfeb28cf45a74caaa763ad
child 80387 69cd4124a49a44d338dd10938d57448c88cd3405
push id21489
push userbmcbride@mozilla.com
push dateFri, 18 Nov 2011 01:56:06 +0000
treeherdermozilla-central@169516414349 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrcampbell
bugs583041
milestone11.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
Bug 583041 - Style Editor integration; part 1; r=rcampbell
browser/devtools/scratchpad/scratchpad.js
browser/devtools/scratchpad/test/Makefile.in
browser/devtools/scratchpad/test/browser_scratchpad_bug_646070_chrome_context_pref.js
browser/devtools/scratchpad/test/browser_scratchpad_bug_660560_tab.js
browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js
browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js
browser/devtools/scratchpad/test/browser_scratchpad_bug_699130_edit_ui_updates.js
browser/devtools/scratchpad/test/browser_scratchpad_contexts.js
browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
browser/devtools/scratchpad/test/browser_scratchpad_files.js
browser/devtools/scratchpad/test/browser_scratchpad_initialization.js
browser/devtools/scratchpad/test/browser_scratchpad_inspect.js
browser/devtools/scratchpad/test/browser_scratchpad_open.js
browser/devtools/scratchpad/test/browser_scratchpad_restore.js
browser/devtools/scratchpad/test/browser_scratchpad_tab_switch.js
browser/devtools/scratchpad/test/browser_scratchpad_ui.js
browser/devtools/scratchpad/test/head.js
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -486,20 +486,22 @@ var Scratchpad = {
 
     return propPanel;
   },
 
   // Menu Operations
 
   /**
    * Open a new Scratchpad window.
+   *
+   * @return nsIWindow
    */
   openScratchpad: function SP_openScratchpad()
   {
-    ScratchpadManager.openScratchpad();
+    return ScratchpadManager.openScratchpad();
   },
 
   /**
    * Export the textbox content to a file.
    *
    * @param nsILocalFile aFile
    *        The file where you want to save the textbox content.
    * @param boolean aNoConfirmation
@@ -763,16 +765,18 @@ var Scratchpad = {
     this.editor.setCaretOffset(this.editor.getCharCount());
     
     if (this.filename && !this.saved) {
       this.onTextChanged();
     }
     else if (this.filename && this.saved) {
       this.onTextSaved();
     }
+
+    this._triggerObservers("Ready");
   },
 
   /**
    * Insert text at the current caret location.
    *
    * @param string aText
    *        The text you want to insert.
    */
@@ -872,16 +876,78 @@ var Scratchpad = {
     }
 
     this.resetContext();
     this.editor.removeEventListener(SourceEditor.EVENTS.CONTEXT_MENU,
                                     this.onContextMenu);
     this.editor.destroy();
     this.editor = null;
   },
+
+  _observers: [],
+
+  /**
+   * Add an observer for Scratchpad events.
+   *
+   * The observer implements IScratchpadObserver := {
+   *   onReady:      Called when the Scratchpad and its SourceEditor are ready.
+   *                 Arguments: (Scratchpad aScratchpad)
+   * }
+   *
+   * All observer handlers are optional.
+   *
+   * @param IScratchpadObserver aObserver
+   * @see removeObserver
+   */
+  addObserver: function SP_addObserver(aObserver)
+  {
+    this._observers.push(aObserver);
+  },
+
+  /**
+   * Remove an observer for Scratchpad events.
+   *
+   * @param IScratchpadObserver aObserver
+   * @see addObserver
+   */
+  removeObserver: function SP_removeObserver(aObserver)
+  {
+    let index = this._observers.indexOf(aObserver);
+    if (index != -1) {
+      this._observers.splice(index, 1);
+    }
+  },
+
+  /**
+   * Trigger named handlers in Scratchpad observers.
+   *
+   * @param string aName
+   *        Name of the handler to trigger.
+   * @param Array aArgs
+   *        Optional array of arguments to pass to the observer(s).
+   * @see addObserver
+   */
+  _triggerObservers: function SP_triggerObservers(aName, aArgs)
+  {
+    // insert this Scratchpad instance as the first argument
+    if (!aArgs) {
+      aArgs = [this];
+    } else {
+      aArgs.unshift(this);
+    }
+
+    // trigger all observers that implement this named handler
+    for (let i = 0; i < this._observers.length; ++i) {
+      let observer = this._observers[i];
+      let handler = observer["on" + aName];
+      if (handler) {
+        handler.apply(observer, aArgs);
+      }
+    }
+  }
 };
 
 XPCOMUtils.defineLazyGetter(Scratchpad, "strings", function () {
   return Services.strings.createBundle(SCRATCHPAD_L10N);
 });
 
 addEventListener("DOMContentLoaded", Scratchpad.onLoad.bind(Scratchpad), false);
 addEventListener("unload", Scratchpad.onUnload.bind(Scratchpad), false);
--- a/browser/devtools/scratchpad/test/Makefile.in
+++ b/browser/devtools/scratchpad/test/Makefile.in
@@ -53,11 +53,12 @@ include $(topsrcdir)/config/rules.mk
 		browser_scratchpad_ui.js \
 		browser_scratchpad_bug_646070_chrome_context_pref.js \
 		browser_scratchpad_bug_660560_tab.js \
 		browser_scratchpad_open.js \
 		browser_scratchpad_restore.js \
 		browser_scratchpad_bug_679467_falsy.js \
 		browser_scratchpad_bug_699130_edit_ui_updates.js \
 		browser_scratchpad_bug_669612_unsaved.js \
+		head.js \
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_646070_chrome_context_pref.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_646070_chrome_context_pref.js
@@ -1,15 +1,12 @@
 /* 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);
@@ -50,13 +47,10 @@ function runTests()
   let chromeContextCommand = gScratchpadWindow.document.
                             getElementById("sp-cmd-browserContext");
   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();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_660560_tab.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_660560_tab.js
@@ -1,41 +1,47 @@
 /* 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;
+var ScratchpadManager = Scratchpad.ScratchpadManager;
 
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function() {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+  gBrowser.selectedBrowser.addEventListener("load", function onTabLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", onTabLoad, true);
 
     ok(window.Scratchpad, "Scratchpad variable exists");
 
     Services.prefs.setIntPref("devtools.editor.tabsize", 5);
 
     gScratchpadWindow = Scratchpad.openScratchpad();
-    gScratchpadWindow.addEventListener("load", runTests, false);
+    gScratchpadWindow.addEventListener("load", function onScratchpadLoad() {
+      gScratchpadWindow.removeEventListener("load", onScratchpadLoad, false);
+
+      gScratchpadWindow.Scratchpad.addObserver({
+        onReady: runTests
+      });
+    }, false);
   }, true);
 
   content.location = "data:text/html,Scratchpad test for the Tab key, bug 660560";
 }
 
 function runTests()
 {
-  gScratchpadWindow.removeEventListener("load", arguments.callee, false);
-
   let sp = gScratchpadWindow.Scratchpad;
   ok(sp, "Scratchpad object exists in new window");
 
+  is(this.onReady, runTests, "the handler runs in the context of the observer");
+  sp.removeObserver(this);
+
   ok(sp.editor.hasFocus(), "the editor has focus");
 
   sp.setText("window.foo;");
   sp.editor.setCaretOffset(0);
 
   EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow);
 
   is(sp.getText(), "     window.foo;", "Tab key added 5 spaces");
@@ -58,36 +64,37 @@ function runTests()
   is(sp.getText(), "     w    omgindow.foo;", "insertTextAtCaret() works");
 
   is(sp.editor.getCaretOffset(), 13, "caret location is correct after update");
 
   gScratchpadWindow.close();
 
   Services.prefs.setIntPref("devtools.editor.tabsize", 6);
   Services.prefs.setBoolPref("devtools.editor.expandtab", false);
+
   gScratchpadWindow = Scratchpad.openScratchpad();
-  gScratchpadWindow.addEventListener("load", runTests2, false);
+  gScratchpadWindow.addEventListener("load", function onScratchpadLoad() {
+    gScratchpadWindow.removeEventListener("load", onScratchpadLoad, false);
+    gScratchpadWindow.Scratchpad.addObserver({
+      onReady: runTests2
+    });
+  }, false);
 }
 
 function runTests2()
 {
-  gScratchpadWindow.removeEventListener("load", arguments.callee, false);
-
   let sp = gScratchpadWindow.Scratchpad;
+  sp.removeObserver(this);
 
   sp.setText("window.foo;");
   sp.editor.setCaretOffset(0);
 
   EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow);
 
   is(sp.getText(), "\twindow.foo;", "Tab key added the tab character");
 
   is(sp.editor.getCaretOffset(), 1, "caret location is correct");
 
   Services.prefs.clearUserPref("devtools.editor.tabsize");
   Services.prefs.clearUserPref("devtools.editor.expandtab");
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-
-  gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js
@@ -27,114 +27,144 @@ function test()
 
   content.location = "data:text/html,<p>test star* UI for unsaved file changes";
 }
 
 function testListeners()
 {
   let win = ScratchpadManager.openScratchpad();
 
-  win.addEventListener("load", function() {
-    let scratchpad = win.Scratchpad;
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
 
-    scratchpad.setText("new text");
-    ok(!isStar(win), "no star if scratchpad isn't from a file");
+    win.Scratchpad.addObserver({
+      onReady: function (aScratchpad) {
+        aScratchpad.removeObserver(this);
 
-    scratchpad.onTextSaved(); 
-    ok(!isStar(win), "no star before changing text");
+        aScratchpad.setText("new text");
+        ok(!isStar(win), "no star if scratchpad isn't from a file");
+
+        aScratchpad.onTextSaved();
+        ok(!isStar(win), "no star before changing text");
 
-    scratchpad.setText("new text2");
-    ok(isStar(win), "shows star if scratchpad text changes");
+        aScratchpad.setText("new text2");
+        ok(isStar(win), "shows star if scratchpad text changes");
+
+        aScratchpad.onTextSaved();
+        ok(!isStar(win), "no star if scratchpad was just saved");
 
-    scratchpad.onTextSaved();
-    ok(!isStar(win), "no star if scratchpad was just saved");
-    
-    scratchpad.undo();
-    ok(isStar(win), "star if scratchpad undo");
+        aScratchpad.undo();
+        ok(isStar(win), "star if scratchpad undo");
 
-    win.close();
-    done();
-  });
+        win.close();
+        done();
+      }
+    });
+  }, false);
 }
 
 function testErrorStatus()
 {
   let win = ScratchpadManager.openScratchpad();
 
-  win.addEventListener("load", function() {
-    let scratchpad = win.Scratchpad;
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
+
+    win.Scratchpad.addObserver({
+      onReady: function (aScratchpad) {
+        aScratchpad.removeObserver(this);
 
-    scratchpad.onTextSaved(Components.results.NS_ERROR_FAILURE);
-    scratchpad.setText("new text");
-    ok(!isStar(win), "no star if file save failed");
+        aScratchpad.onTextSaved(Components.results.NS_ERROR_FAILURE);
+        aScratchpad.setText("new text");
+        ok(!isStar(win), "no star if file save failed");
 
-    win.close();
-    done();
-  });
+        win.close();
+        done();
+      }
+    });
+  }, false);
 }
 
 
 function testRestoreNotFromFile()
 {
   let session = [{
     text: "test1",
     executionContext: 1
   }];
 
   let [win] = ScratchpadManager.restoreSession(session);
-  win.addEventListener("load", function() {
-    let scratchpad = win.Scratchpad;
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
+
+    win.Scratchpad.addObserver({
+      onReady: function (aScratchpad) {
+        aScratchpad.removeObserver(this);
 
-    scratchpad.setText("new text");
-    ok(!isStar(win), "no star if restored scratchpad isn't from a file");
-    
-    win.close();
-    done();
-  });
+        aScratchpad.setText("new text");
+        ok(!isStar(win), "no star if restored scratchpad isn't from a file");
+
+        win.close();
+        done();
+      }
+    });
+  }, false);
 }
 
 function testRestoreFromFileSaved()
 {
   let session = [{
     filename: "test.js",
     text: "test1",
     executionContext: 1,
     saved: true
   }];
 
   let [win] = ScratchpadManager.restoreSession(session);
-  win.addEventListener("load", function() {
-    let scratchpad = win.Scratchpad;
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
 
-    ok(!isStar(win), "no star before changing text in scratchpad restored from file");
+    win.Scratchpad.addObserver({
+      onReady: function (aScratchpad) {
+        aScratchpad.removeObserver(this);
+
+        ok(!isStar(win), "no star before changing text in scratchpad restored from file");
 
-    scratchpad.setText("new text");
-    ok(isStar(win), "star when text changed from scratchpad restored from file");
+        aScratchpad.setText("new text");
+        ok(isStar(win), "star when text changed from scratchpad restored from file");
 
-    win.close();
-    done();
-  });
+        win.close();
+        done();
+      }
+    });
+  }, false);
 }
 
 function testRestoreFromFileUnsaved()
 {
   let session = [{
     filename: "test.js",
     text: "test1",
     executionContext: 1,
     saved: false
   }];
 
   let [win] = ScratchpadManager.restoreSession(session);
-  win.addEventListener("load", function() {
-    let scratchpad = win.Scratchpad;
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
+
+    win.Scratchpad.addObserver({
+      onReady: function (aScratchpad) {
+        aScratchpad.removeObserver(this);
 
-    ok(isStar(win), "star with scratchpad restored with unsaved text");
+        ok(isStar(win), "star with scratchpad restored with unsaved text");
 
-    win.close();
-    done();
-  });
+        win.close();
+        done();
+      }
+    });
+  }, false);
 }
 
 function isStar(win)
 {
   return win.document.title.match(/^\*[^\*]/);
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js
@@ -25,19 +25,16 @@ function testFalsy(sp)
   gScratchpadWindow.removeEventListener("load", testFalsy, false);
 
   let sp = gScratchpadWindow.Scratchpad;
   verifyFalsies(sp);
   
   sp.setBrowserContext();
   verifyFalsies(sp);
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-  gBrowser.removeCurrentTab();
   finish();
 }
 
 function verifyFalsies(sp)
 {
   sp.setText("undefined");
   sp.display();
   is(sp.selectedText, "/*\nundefined\n*/", "'undefined' is displayed");
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_699130_edit_ui_updates.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_699130_edit_ui_updates.js
@@ -1,39 +1,35 @@
 /* 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";
 
 Cu.import("resource:///modules/source-editor.jsm");
 
-// Reference to the Scratchpad chrome window object.
-let gScratchpadWindow;
-
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
 
     gScratchpadWindow = Scratchpad.openScratchpad();
-    gScratchpadWindow.addEventListener("load", function onScratchpadLoad() {
-      gScratchpadWindow.removeEventListener("load", onScratchpadLoad, false);
-      waitForFocus(runTests, gScratchpadWindow);
-    }, false);
+    gScratchpadWindow.addEventListener("load", runTests, false);
   }, true);
 
   content.location = "data:text/html,test Edit menu updates Scratchpad - bug 699130";
 }
 
 function runTests()
 {
+  gScratchpadWindow.removeEventListener("load", runTests, false);
+
   let sp = gScratchpadWindow.Scratchpad;
   let doc = gScratchpadWindow.document;
   let winUtils = gScratchpadWindow.QueryInterface(Ci.nsIInterfaceRequestor).
                  getInterface(Ci.nsIDOMWindowUtils);
   let OS = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
 
   info("will test the Edit menu");
 
@@ -121,33 +117,40 @@ function runTests()
 
   let showAfterSelect = function() {
     ok(!cutItem.hasAttribute("disabled"), "cut menuitem is enabled after select");
     closeMenu(hideAfterSelect);
   };
 
   let hideAfterSelect = function() {
     sp.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onCut);
-    EventUtils.synthesizeKey("x", {accelKey: true}, gScratchpadWindow);
+    waitForFocus(function () {
+      let selectedText = sp.editor.getSelectedText();
+      ok(selectedText.length > 0, "non-empty selected text will be cut");
+
+      EventUtils.synthesizeKey("x", {accelKey: true}, gScratchpadWindow);
+    }, gScratchpadWindow);
   };
 
   let onCut = function() {
     sp.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onCut);
     openMenu(12, 12, showAfterCut);
   };
 
   let showAfterCut = function() {
     ok(cutItem.hasAttribute("disabled"), "cut menuitem is disabled after cut");
     ok(!pasteItem.hasAttribute("disabled"), "paste menuitem is enabled after cut");
     closeMenu(hideAfterCut);
   };
 
   let hideAfterCut = function() {
     sp.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onPaste);
-    EventUtils.synthesizeKey("v", {accelKey: true}, gScratchpadWindow);
+    waitForFocus(function () {
+      EventUtils.synthesizeKey("v", {accelKey: true}, gScratchpadWindow);
+    }, gScratchpadWindow);
   };
 
   let onPaste = function() {
     sp.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onPaste);
     openMenu(13, 13, showAfterPaste);
   };
 
   let showAfterPaste = function() {
@@ -156,17 +159,17 @@ function runTests()
     closeMenu(hideAfterPaste);
   };
 
   let hideAfterPaste = function() {
     if (pass == 0) {
       pass++;
       testContextMenu();
     } else {
-      finishTest();
+      finish();
     }
   };
 
   let testContextMenu = function() {
     info("will test the context menu");
 
     editMenu = null;
     isContextMenu = true;
@@ -177,17 +180,10 @@ function runTests()
     ok(cutItem, "the Cut menuitem");
     pasteItem = doc.getElementById("menu_paste");
     ok(pasteItem, "the Paste menuitem");
 
     sp.setText("bug 699130: hello world! (context menu)");
     openMenu(10, 10, firstShow);
   };
 
-  let finishTest = function() {
-    gScratchpadWindow.close();
-    gScratchpadWindow = null;
-    gBrowser.removeCurrentTab();
-    finish();
-  };
-
   openMenu(10, 10, firstShow);
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_contexts.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_contexts.js
@@ -118,13 +118,10 @@ function runTests()
   sp.setContentContext();
 
   is(sp.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_CONTENT,
      "executionContext is content");
 
   is(sp.run()[2], "undefined",
      "global variable no longer exists after changing the context");
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-  gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
@@ -1,15 +1,12 @@
 /* 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);
 
@@ -124,13 +121,10 @@ function runTests()
   sp.setText("foo1");
   sp.setText("foo2");
   is(sp.getText(), "foo2", "editor content updated");
   sp.undo();
   is(sp.getText(), "foo1", "undo() works");
   sp.redo();
   is(sp.getText(), "foo2", "redo() works");
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-  gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_files.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_files.js
@@ -133,13 +133,10 @@ function fileRead(aInputStream, aStatus)
     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();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_initialization.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_initialization.js
@@ -45,13 +45,10 @@ function runTests()
      "Error console command is disabled");
 
   let chromeContextCommand = gScratchpadWindow.document.
                             getElementById("sp-cmd-browserContext");
   ok(chromeContextCommand, "Chrome context command element exists");
   is(chromeContextCommand.getAttribute("disabled"), "true",
      "Chrome context command is disabled");
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-  gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_inspect.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_inspect.js
@@ -50,15 +50,12 @@ function runTests()
         break;
       }
     }
     ok(found, "found the document.title property");
 
     executeSoon(function() {
       propPanel.hidePopup();
 
-      gScratchpadWindow.close();
-      gScratchpadWindow = null;
-      gBrowser.removeCurrentTab();
       finish();
     });
   }, false);
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_open.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_open.js
@@ -23,47 +23,51 @@ function test()
   testOpenWithState();
   testOpenInvalidState();
 }
 
 function testOpen()
 {
   let win = ScratchpadManager.openScratchpad();
 
-  win.addEventListener("load", function() {
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
+
     is(win.Scratchpad.filename, undefined, "Default filename is undefined");
     is(win.Scratchpad.getText(),
        win.Scratchpad.strings.GetStringFromName("scratchpadIntro"),
        "Default text is loaded")
     is(win.Scratchpad.executionContext, win.SCRATCHPAD_CONTEXT_CONTENT,
       "Default execution context is content");
 
     win.close();
     done();
-  });
+  }, false);
 }
 
 function testOpenWithState()
 {
   let state = {
     filename: "testfile",
     executionContext: 2,
     text: "test text"
   };
 
   let win = ScratchpadManager.openScratchpad(state);
 
-  win.addEventListener("load", function() {
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
+
     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");
 
     win.close();
     done();
-  });
+  }, false);
 }
 
 function testOpenInvalidState()
 {
   let state = 7;
 
   let win = ScratchpadManager.openScratchpad(state);
   ok(!win, "no scratchpad opened if state is not an object");
--- a/browser/devtools/scratchpad/test/browser_scratchpad_restore.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_restore.js
@@ -44,19 +44,20 @@ function testRestore()
       text: "text3",
       executionContext: 1
     }
   ];
 
   asyncMap(states, function(state, done) {
     // Open some scratchpad windows
     let win = ScratchpadManager.openScratchpad(state);
-    win.addEventListener("load", function() {
+    win.addEventListener("load", function onScratchpadLoad() {
+      removeEventListener("load", onScratchpadLoad, false);
       done(win);
-    })
+    }, false)
   }, function(wins) {
     // Then save the windows to session store
     ScratchpadManager.saveOpenWindows();
 
     // Then get their states
     let session = ScratchpadManager.getSessionState();
 
     // Then close them
@@ -68,21 +69,22 @@ function testRestore()
     ScratchpadManager.saveOpenWindows();
 
     // Then restore them
     let restoredWins = ScratchpadManager.restoreSession(session);
 
     is(restoredWins.length, 3, "Three scratchad windows restored");
 
     asyncMap(restoredWins, function(restoredWin, done) {
-      restoredWin.addEventListener("load", function() {
+      restoredWin.addEventListener("load", function onScratchpadLoad() {
+        restoredWin.removeEventListener("load", onScratchpadLoad, false);
         let state = restoredWin.Scratchpad.getState();
         restoredWin.close();
         done(state);
-      });
+      }, false);
     }, function(restoredStates) {
       // Then make sure they were restored with the right states
       ok(statesMatch(restoredStates, states),
         "All scratchpad window states restored correctly");
 
       // Yay, we're done!
       finish();
     });
--- a/browser/devtools/scratchpad/test/browser_scratchpad_tab_switch.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_tab_switch.js
@@ -94,17 +94,13 @@ function runTests2() {
 
 function runTests3() {
   gBrowser.selectedBrowser.removeEventListener("load", runTests3, true);
   // Check that the sandbox is not cached.
 
   sp.setText("typeof foosbug653108;");
   is(sp.run()[2], "undefined", "global variable does not exist");
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
   tab1 = null;
   tab2 = null;
   sp = null;
-  gBrowser.removeCurrentTab();
-  gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_ui.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_ui.js
@@ -70,13 +70,10 @@ function runTests()
     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/devtools/scratchpad/test/head.js
@@ -0,0 +1,20 @@
+/* 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";
+
+let gScratchpadWindow; // Reference to the Scratchpad chrome window object
+
+function cleanup()
+{
+  if (gScratchpadWindow) {
+    gScratchpadWindow.close();
+    gScratchpadWindow = null;
+  }
+  while (gBrowser.tabs.length > 1) {
+    gBrowser.removeCurrentTab();
+  }
+}
+
+registerCleanupFunction(cleanup);