Bug 599610 - making sure that PurgeScriptFragments collects all fragments
authorIgor Bukanov <igor@mir2.org>
Wed, 06 Oct 2010 17:21:23 +0200
changeset 58041 f9785814bdbc41a57115ee867ef0d3244fca57dc
parent 58040 75d629d5bae7dbd3223940648d785599ac097d7a
child 58042 043609d1ec8b2835ffd6119612cd80211a8ba1cd
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
bugs599610
milestone2.0b8pre
Bug 599610 - making sure that PurgeScriptFragments collects all fragments
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsfun.cpp
js/src/jsgc.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jstracer.cpp
js/src/jstracer.h
uriloader/exthandler/tests/unit_ipc/test_encoding.js
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -489,17 +489,20 @@ JSThreadData::init()
 #ifdef DEBUG
     /* The data must be already zeroed. */
     for (size_t i = 0; i != sizeof(*this); ++i)
         JS_ASSERT(reinterpret_cast<uint8*>(this)[i] == 0);
 #endif
     if (!stackSpace.init())
         return false;
 #ifdef JS_TRACER
-    InitJIT(&traceMonitor);
+    if (!InitJIT(&traceMonitor)) {
+        finish();
+        return false;
+    }
 #endif
     dtoaState = js_NewDtoaState();
     if (!dtoaState) {
         finish();
         return false;
     }
     nativeStackBase = GetNativeStackBase();
     return true;
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -893,16 +893,20 @@ typedef HashMap<jsbytecode*,
 /* Holds the profile data for loops. */
 typedef HashMap<jsbytecode*,
                 LoopProfile*,
                 DefaultHasher<jsbytecode*>,
                 SystemAllocPolicy> LoopProfileMap;
 
 class Oracle;
 
+typedef HashSet<JSScript *,
+                DefaultHasher<JSScript *>,
+                SystemAllocPolicy> TracedScriptSet;
+
 /*
  * Trace monitor. Every JSThread (if JS_THREADSAFE) or JSRuntime (if not
  * JS_THREADSAFE) has an associated trace monitor that keeps track of loop
  * frequencies for all JavaScript code loaded into that runtime.
  */
 struct TraceMonitor {
     /*
      * The context currently executing JIT-compiled code on this thread, or
@@ -993,16 +997,19 @@ struct TraceMonitor {
      */
     REHashMap*              reFragments;
 
     // Cached temporary typemap to avoid realloc'ing every time we create one.
     // This must be used in only one place at a given time. It must be cleared
     // before use.
     TypeMap*                cachedTempTypeMap;
 
+    /* Scripts with recorded fragments. */
+    TracedScriptSet         tracedScripts;
+
 #ifdef DEBUG
     /* Fields needed for fragment/guard profiling. */
     nanojit::Seq<nanojit::Fragment*>* branches;
     uint32                  lastFragID;
     /*
      * profAlloc has a lifetime which spans exactly from js_InitJIT to
      * js_FinishJIT.
      */
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -2057,17 +2057,17 @@ fun_finalize(JSContext *cx, JSObject *ob
         return;
     }
 
     /*
      * Null-check of u.i.script is required since the parser sets interpreted
      * very early.
      */
     if (FUN_INTERPRETED(fun) && fun->u.i.script)
-        js_DestroyScript(cx, fun->u.i.script);
+        js_DestroyScriptFromGC(cx, fun->u.i.script, NULL);
 }
 
 int
 JSFunction::sharpSlotBase(JSContext *cx)
 {
 #if JS_HAS_SHARP_VARS
     JSAtom *name = js_Atomize(cx, "#array", 6, 0);
     if (name) {
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1746,17 +1746,17 @@ js_DestroyScriptsToGC(JSContext *cx, JST
 {
     JSScript **listp, *script;
 
     for (size_t i = 0; i != JS_ARRAY_LENGTH(data->scriptsToGC); ++i) {
         listp = &data->scriptsToGC[i];
         while ((script = *listp) != NULL) {
             *listp = script->u.nextToGC;
             script->u.nextToGC = NULL;
-            js_DestroyScript(cx, script);
+            js_DestroyScriptFromGC(cx, script, data);
         }
     }
 }
 
 /*
  * This function is called from js_FinishAtomState to force the finalization
  * of the permanently interned strings when cx is not available.
  */
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -81,17 +81,17 @@ static const jsbytecode emptyScriptCode[
     1, JSVERSION_DEFAULT, 0, 0, 0, 0, 0, 0, 0, true, false, false, false, false,
     false,      /* usesEval */
     false,      /* usesArguments */
     true,       /* warnedAboutTwoArgumentEval */
 #ifdef JS_METHODJIT
     false,      /* debugMode */
 #endif
     const_cast<jsbytecode*>(emptyScriptCode),
-    {0, NULL}, NULL, NULL, 0, 0, 0,
+    {0, jsatomid(0)}, NULL, NULL, 0, 0, 0,
     0,          /* nClosedArgs */
     0,          /* nClosedVars */
     NULL, {NULL},
 #ifdef CHECK_SCRIPT_OWNER
     reinterpret_cast<JSThread*>(1)
 #endif
 };
 
@@ -475,17 +475,17 @@ js_XDRScript(JSXDRState *xdr, JSScript *
 
 #endif /* JS_HAS_XDR */
 
 static void
 script_finalize(JSContext *cx, JSObject *obj)
 {
     JSScript *script = (JSScript *) obj->getPrivate();
     if (script)
-        js_DestroyScript(cx, script);
+        js_DestroyScriptFromGC(cx, script, NULL);
 }
 
 static void
 script_trace(JSTracer *trc, JSObject *obj)
 {
     JSScript *script = (JSScript *) obj->getPrivate();
     if (script)
         js_TraceScript(trc, script);
@@ -1296,18 +1296,18 @@ js_CallDestroyScriptHook(JSContext *cx, 
 
     JSDestroyScriptHook hook;
 
     hook = cx->debugHooks->destroyScriptHook;
     if (hook)
         hook(cx, script, cx->debugHooks->destroyScriptHookData);
 }
 
-void
-js_DestroyScript(JSContext *cx, JSScript *script)
+static void
+DestroyScript(JSContext *cx, JSScript *script, JSThreadData *data)
 {
     if (script == JSScript::emptyScript()) {
         JS_RUNTIME_UNMETER(cx->runtime, liveEmptyScripts);
         return;
     }
 
     js_CallDestroyScriptHook(cx, script);
     JS_ClearScriptTraps(cx, script);
@@ -1354,30 +1354,53 @@ js_DestroyScript(JSContext *cx, JSScript
 
 #ifdef CHECK_SCRIPT_OWNER
             JS_ASSERT(script->owner == cx->thread);
 #endif
         }
     }
 
 #ifdef JS_TRACER
-    PurgeScriptFragments(cx, script);
+# ifdef JS_THREADSAFE
+    if (data) {
+        PurgeScriptFragments(&data->traceMonitor, script);
+    } else {
+        for (ThreadDataIter i(cx->runtime); !i.empty(); i.popFront())
+            PurgeScriptFragments(&i.threadData()->traceMonitor, script);
+    }
+# else
+    PurgeScriptFragments(JS_TRACE_MONITOR(cx), script);
+# endif
 #endif
 
 #if defined(JS_METHODJIT)
     mjit::ReleaseScriptCode(cx, script);
 #endif
     JS_REMOVE_LINK(&script->links);
 
     cx->free(script);
 
     JS_RUNTIME_UNMETER(cx->runtime, liveScripts);
 }
 
 void
+js_DestroyScript(JSContext *cx, JSScript *script)
+{
+    JS_ASSERT(!cx->runtime->gcRunning);
+    DestroyScript(cx, script, JS_THREAD_DATA(cx));
+}
+
+void
+js_DestroyScriptFromGC(JSContext *cx, JSScript *script, JSThreadData *data)
+{
+    JS_ASSERT(cx->runtime->gcRunning);
+    DestroyScript(cx, script, data);
+}
+
+void
 js_TraceScript(JSTracer *trc, JSScript *script)
 {
     JSAtomMap *map = &script->atomMap;
     MarkAtomRange(trc, map->length, map->vector, "atomMap");
 
     if (script->objectsOffset != 0) {
         JSObjectArray *objarray = script->objects();
         uintN i = objarray->length;
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -505,19 +505,30 @@ js_SweepScriptFilenames(JSRuntime *rt);
  * of any owning function (the fun parameter) or script object (null fun).
  */
 extern JS_FRIEND_API(void)
 js_CallNewScriptHook(JSContext *cx, JSScript *script, JSFunction *fun);
 
 extern JS_FRIEND_API(void)
 js_CallDestroyScriptHook(JSContext *cx, JSScript *script);
 
+/*
+ * The function must be used only outside the GC for a script that was run
+ * only on the current thread.
+ */
 extern void
 js_DestroyScript(JSContext *cx, JSScript *script);
 
+/*
+ * If data is not null, it indicates that the script could been accessed only
+ * from that thread.
+ */
+extern void
+js_DestroyScriptFromGC(JSContext *cx, JSScript *script, JSThreadData *data);
+
 extern void
 js_TraceScript(JSTracer *trc, JSScript *script);
 
 extern JSBool
 js_NewScriptObject(JSContext *cx, JSScript *script);
 
 /*
  * To perturb as little code as possible, we introduce a js_GetSrcNote lookup
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -2746,16 +2746,17 @@ TraceMonitor::flush()
         globalStates[i].globalSlots = new (alloc) SlotList(&alloc);
     }
 
     assembler = new (alloc) Assembler(*codeAlloc, alloc, alloc, core, &LogController, avmplus::AvmCore::config);
     verbose_only( branches = NULL; )
 
     PodArrayZero(vmfragments);
     reFragments = new (alloc) REHashMap(alloc);
+    tracedScripts.clear();
 
     needFlush = JS_FALSE;
 }
 
 inline bool
 HasUnreachableGCThings(TreeFragment *f)
 {
     /*
@@ -5632,17 +5633,21 @@ RecordTree(JSContext* cx, TreeFragment* 
     /* Make sure the global type map didn't change on us. */
     if (!CheckGlobalObjectShape(cx, tm, f->globalObj)) {
         Backoff(cx, (jsbytecode*) localRootIP);
         return false;
     }
 
     AUDIT(recorderStarted);
 
-    if (tm->outOfMemory() || OverfullJITCache(tm)) {
+    if (tm->outOfMemory() ||
+        OverfullJITCache(tm) ||
+        !tm->tracedScripts.put(cx->fp()->script())) {
+        if (!OverfullJITCache(tm))
+            js_ReportOutOfMemory(cx);
         Backoff(cx, (jsbytecode*) f->root->ip);
         ResetJIT(cx, FR_OOM);
         debug_only_print0(LC_TMTracer,
                           "Out of memory recording new tree, flushing cache.\n");
         return false;
     }
 
     JS_ASSERT(!f->code());
@@ -7527,17 +7532,17 @@ SetMaxCodeCacheBytes(JSContext* cx, uint
     JS_ASSERT(tm->codeAlloc && tm->dataAlloc && tm->traceAlloc);
     if (bytes > 1 G)
         bytes = 1 G;
     if (bytes < 128 K)
         bytes = 128 K;
     tm->maxCodeCacheBytes = bytes;
 }
 
-void
+bool
 InitJIT(TraceMonitor *tm)
 {
 #if defined JS_JIT_SPEW
     tm->profAlloc = NULL;
     /* Set up debug logging. */
     if (!did_we_set_up_debug_logging) {
         InitJITLogController();
         did_we_set_up_debug_logging = true;
@@ -7632,16 +7637,20 @@ InitJIT(TraceMonitor *tm)
 #endif
 #if defined NANOJIT_PPC
     jitstats.archIsPPC = 1;
 #endif
 #if defined NANOJIT_X64
     jitstats.archIsAMD64 = 1;
 #endif
 #endif
+
+    if (!tm->tracedScripts.init())
+        return false;
+    return true;
 }
 
 void
 FinishJIT(TraceMonitor *tm)
 {
     JS_ASSERT(!tm->recorder);
     JS_ASSERT(!tm->profile);
 
@@ -7743,27 +7752,30 @@ FinishJIT(TraceMonitor *tm)
         tm->storage = NULL;
     }
 
     delete tm->cachedTempTypeMap;
     tm->cachedTempTypeMap = NULL;
 }
 
 JS_REQUIRES_STACK void
-PurgeScriptFragments(JSContext* cx, JSScript* script)
+PurgeScriptFragments(TraceMonitor* tm, JSScript* script)
 {
     debug_only_printf(LC_TMTracer,
                       "Purging fragments for JSScript %p.\n", (void*)script);
 
-    TraceMonitor* tm = &JS_TRACE_MONITOR(cx);
-
     /* A recorder script is being evaluated and can not be destroyed or GC-ed. */
     JS_ASSERT_IF(tm->recorder, 
                  JS_UPTRDIFF(tm->recorder->getTree()->ip, script->code) >= script->length);
 
+    TracedScriptSet::Ptr found = tm->tracedScripts.lookup(script);
+    if (!found)
+        return;
+    tm->tracedScripts.remove(found);
+
     for (size_t i = 0; i < FRAGMENT_TABLE_SIZE; ++i) {
         TreeFragment** fragp = &tm->vmfragments[i];
         while (TreeFragment* frag = *fragp) {
             if (JS_UPTRDIFF(frag->ip, script->code) < script->length) {
                 /* This fragment is associated with the script. */
                 debug_only_printf(LC_TMTracer,
                                   "Disconnecting TreeFragment %p "
                                   "with ip %p, in range [%p,%p).\n",
--- a/js/src/jstracer.h
+++ b/js/src/jstracer.h
@@ -1671,24 +1671,24 @@ RecordTracePoint(JSContext*, uintN& inli
 
 extern JS_REQUIRES_STACK TracePointAction
 MonitorTracePoint(JSContext*, uintN& inlineCallCount, bool* blacklist,
                   void** traceData, uintN *traceEpoch, uint32 *loopCounter, uint32 hits);
 
 extern JS_REQUIRES_STACK TraceRecorder::AbortResult
 AbortRecording(JSContext* cx, const char* reason);
 
-extern void
+extern bool
 InitJIT(TraceMonitor *tm);
 
 extern void
 FinishJIT(TraceMonitor *tm);
 
 extern void
-PurgeScriptFragments(JSContext* cx, JSScript* script);
+PurgeScriptFragments(TraceMonitor* tm, JSScript* script);
 
 extern bool
 OverfullJITCache(TraceMonitor* tm);
 
 extern void
 FlushJITCache(JSContext* cx);
 
 extern JSObject *
deleted file mode 100644
--- a/uriloader/exthandler/tests/unit_ipc/test_encoding.js
+++ /dev/null
@@ -1,281 +0,0 @@
-
-do_get_profile();
-do_load_httpd_js();
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-// Dynamically generates a classID for our component, registers it to mask
-// the existing component, and stored the masked components classID to be
-// restored later, when we unregister.
-function registerTemporaryComponent(comp)
-{
-  let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
-  if (!comp.prototype.classID) {
-    let uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
-    comp.prototype.classID = uuidgen.generateUUID();
-  }
-  comp.prototype.maskedClassID = Components.ID(Cc[comp.prototype.contractID].number);
-  if (!comp.prototype.factory)
-    comp.prototype.factory = getFactory(comp);
-  registrar.registerFactory(comp.prototype.classID, "", comp.prototype.contractID, comp.prototype.factory);
-}
-
-function unregisterTemporaryComponent(comp)
-{
-  let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
-  registrar.unregisterFactory(comp.prototype.classID, comp.prototype.factory);
-  registrar.registerFactory(comp.prototype.maskedClassID, "", comp.prototype.contractID, null);
-}
-
-let DownloadListener = {
-  init: function () {
-    let obs = Services.obs;
-    obs.addObserver(this, "dl-done", true);
-  },
-
-  observe: function (subject, topic, data) {
-    this.onFinished(subject, topic, data);
-  },
-
-  QueryInterface: function (iid) {
-    if (iid.equals(Ci.nsIObserver) ||
-        iid.equals(Ci.nsISupportsWeakReference) ||
-        iid.equals(Ci.nsISupports))
-      return this;
-
-    throw Cr.NS_ERROR_NO_INTERFACE;
-  }
-}
-DownloadListener.init();
-
-function HelperAppDlg() { }
-HelperAppDlg.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
-  contractID: "@mozilla.org/helperapplauncherdialog;1",
-  show: function (launcher, ctx, reason) {
-    launcher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.saveToFile;
-    launcher.launchWithApplication(null, false);
-  },
-
-  promptForSaveToFile: function (launcher, ctx, defaultFile, suggestedExtension, forcePrompt) { }
-}
-
-// Stolen from XPCOMUtils, since this handy function is not public there
-function getFactory(comp)
-{
-  return {
-    createInstance: function (outer, iid) {
-      if (outer)
-        throw Cr.NS_ERROR_NO_AGGREGATION;
-      return (new comp()).QueryInterface(iid);
-    }
-  }
-}
-
-// Override the download-manager-ui to prevent anyone from trying to open
-// a window.
-function DownloadMgrUI() { }
-DownloadMgrUI.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
-  contractID: "@mozilla.org/download-manager-ui;1",
-  show: function (ir, aID, reason) { },
-
-  visible: false,
-
-  getAttention: function () { }
-}
-
-function AlertsSVC() { }
-AlertsSVC.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAlertsService]),
-  contractID: "@mozilla.org/alerts-service;1",
-  showAlertNotification: function (url, title, text, clickable, cookie, listener, name) { },
-}
-
-registerTemporaryComponent(HelperAppDlg);
-registerTemporaryComponent(DownloadMgrUI);
-registerTemporaryComponent(AlertsSVC);
-
-function initChildTestEnv()
-{
-  sendCommand('                                                                \
-    const Cc = Components.classes;                                             \
-    const Ci = Components.interfaces;                                          \
-    const Cr = Components.results;                                             \
-    function WindowContext() { }                                               \
-                                                                               \
-    WindowContext.prototype = {                                                \
-      getInterface: function (iid) {                                           \
-        if (iid.equals(Ci.nsIInterfaceRequestor) ||                            \
-            iid.equals(Ci.nsIURIContentListener) ||                            \
-            iid.equals(Ci.nsILoadGroup) ||                                     \
-            iid.equals(Ci.nsIDocumentLoader) ||                                \
-            iid.equals(Ci.nsIDOMWindow))                                       \
-          return this;                                                         \
-                                                                               \
-        throw Cr.NS_ERROR_NO_INTERFACE;                                        \
-      },                                                                       \
-                                                                               \
-      /* nsIURIContentListener */                                              \
-      onStartURIOpen: function (uri) { },                                      \
-      isPreferred: function (type, desiredtype) { return false; },             \
-                                                                               \
-      /* nsILoadGroup */                                                       \
-      addRequest: function (request, context) { },                             \
-      removeRequest: function (request, context, status) { }                   \
-    };                                                                         \
-                                                                               \
-    var ioservice = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);\
-    var uriloader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);\
-  ');
-}
-
-function testFinisher(endFunc) {
-  let ef = endFunc;
-  return function (file) {
-    ef(file);
-    runNextTest();
-  }
-}
-
-function runChildTestSet(set)
-{
-  DownloadListener.onFinished = testFinisher(set[2]);
-  sendCommand('\
-  let uri = ioservice.newURI("http://localhost:4444' + set[0] + '", null, null);\
-  let channel = ioservice.newChannelFromURI(uri);                              \
-  uriloader.openURI(channel, true, new WindowContext());                       \
-  ');
-}
-
-var httpserver = null;
-let currentTest = 0;
-function runNextTest()
-{
-  if (currentTest == tests.length) {
-    httpserver.stop(do_test_finished);
-    return;
-  }
-
-  let set = tests[currentTest++];
-  runChildTestSet(set);
-}
-
-const responseBody = [0x1f, 0x8b, 0x08, 0x00, 0x16, 0x5a, 0x8a, 0x48, 0x02,
-		      0x03, 0x2b, 0x49, 0x2d, 0x2e, 0xe1, 0x02, 0x00, 0xc6,
-		      0x35, 0xb9, 0x3b, 0x05, 0x00, 0x00, 0x00];
-
-/*
- * First test:  a file with Content-Type application/x-gzip and Content-Encoding gzip
- * should not be decoded in a round-trip
- */
-function testResponse1(metadata, response) {
-  response.setHeader("Content-Type", "application/x-gzip", false);
-  response.setHeader("Content-Encoding", "gzip", false);
-  response.setHeader("Content-Disposition", "attachment", false);
-
-  var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
-  bos.setOutputStream(response.bodyOutputStream);
-  bos.writeByteArray(responseBody, responseBody.length);
-}
-
-function finishTest1(subject, topic, data) {
-  let file = subject.QueryInterface(Ci.nsIDownload).targetFile;
-  do_check_true(file.path.search("test1.gz") != 0);
-  let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
-  fis.init(file, -1, -1, 0);
-  let bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
-  bis.setInputStream(fis);
-  let str = bis.readByteArray(bis.available());
-  do_check_true(str.length == responseBody.length);
-
-  let cmp = 0;
-  for (i = 0; i < str.length; i++) {
-    cmp += str[i] - responseBody[i];
-    if (cmp != 0) break;
-  }
-  do_check_true(cmp == 0);
-}
-
-/*
- * Second test:  a file with Content-Type text/html and Content-Encoding gzip
- * should not be decoded in a round-trip, if its filename ends in ".gz".
- * We specify a Content-disposition header to force it to be saved as a file.
- */
-function testResponse2(metadata, response) {
-  response.setHeader("Content-Type", "text/html", false);
-  response.setHeader("Content-Encoding", "gzip", false);
-  response.setHeader("Content-Disposition", "attachment", false);
-
-  var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
-  bos.setOutputStream(response.bodyOutputStream);
-  bos.writeByteArray(responseBody, responseBody.length);
-}
-
-function finishTest2(subject, topic, data) {
-  let file = subject.QueryInterface(Ci.nsIDownload).targetFile;
-  do_check_true(file.path.search("test2.gz") != 0);
-  let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
-  fis.init(file, -1, -1, 0);
-  let bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
-  bis.setInputStream(fis);
-  let str = bis.readByteArray(bis.available());
-  do_check_true(str.length == responseBody.length);
-
-  let cmp = 0;
-  for (i = 0; i < str.length; i++) {
-    cmp += str[i] - responseBody[i];
-    if (cmp != 0) break;
-  }
-  do_check_true(cmp == 0);
-}
-
-function testResponse3(metadata, response) {
-  response.setHeader("Content-Type", "text/html", false);
-  response.setHeader("Content-Encoding", "gzip", false);
-  response.setHeader("Content-Disposition", "attachment", false);
-
-  var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
-  bos.setOutputStream(response.bodyOutputStream);
-  bos.writeByteArray(responseBody, responseBody.length);
-}
-
-function finishTest3(subject, topic, data) {
-  let file = subject.QueryInterface(Ci.nsIDownload).targetFile;
-  do_check_true(file.path.search("test3.txt") != 0);
-  let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
-  fis.init(file, -1, -1, 0);
-  let bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
-  bis.setInputStream(fis);
-  let str = bis.readByteArray(bis.available());
-  let decodedBody = [ 116, 101, 115, 116, 10 ]; // 't','e','s','t','\n'
-  do_check_true(str.length == decodedBody.length);
-
-  let cmp = 0;
-  for (i = 0; i < str.length; i++) {
-    cmp += str[i] - decodedBody[i];
-    if (cmp != 0) break;
-  }
-  do_check_true(cmp == 0);
-}
-
-let tests = [
-  [ "/test1.gz", testResponse1, finishTest1 ],
-  [ "/test2.gz", testResponse2, finishTest2 ],
-  [ "/test3.txt", testResponse3, finishTest3 ],
-];
-
-function run_test() {
-//  do_load_child_test_harness();
-  httpserver = new nsHttpServer();
-  httpserver.start(4444);
-  do_test_pending();
-
-  initChildTestEnv();
-
-  for each (set in tests)
-    httpserver.registerPathHandler(set[0], set[1]);
-
-  runNextTest();
-}