Backout changeset b0200dab0ccc to revert the incorrect backout of an fx-team range (no bug)

This is the range I mistakenly backed out before:
http://hg.mozilla.org/mozilla-central/pushloghtml?changeset=bc5fee76550b
This commit is contained in:
Ehsan Akhgari 2012-05-02 16:11:19 -04:00
Родитель cdc1d98f64
Коммит 6c81de89ff
16 изменённых файлов: 322 добавлений и 131 удалений

Просмотреть файл

@ -68,6 +68,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
XPCOMUtils.defineLazyModuleGetter(this, "webappsUI",
"resource:///modules/webappsUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
"resource:///modules/PageThumbs.jsm");
const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
const PREF_PLUGINS_UPDATEURL = "plugins.update.url";
@ -358,6 +361,8 @@ BrowserGlue.prototype = {
// Initialize webapps UI
webappsUI.init();
PageThumbs.init();
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
},
@ -379,6 +384,7 @@ BrowserGlue.prototype = {
_onProfileShutdown: function BG__onProfileShutdown() {
this._shutdownPlaces();
this._sanitizer.onShutdown();
PageThumbs.uninit();
},
// All initial windows have opened.

Просмотреть файл

@ -4,7 +4,7 @@
"use strict";
let EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsCache"];
let EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsStorage", "PageThumbsCache"];
const Cu = Components.utils;
const Cc = Components.classes;
@ -12,6 +12,11 @@ const Ci = Components.interfaces;
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
/**
* Name of the directory in the profile that contains the thumbnails.
*/
const THUMBNAIL_DIRECTORY = "thumbnails";
/**
* The default background color for page thumbnails.
*/
@ -25,11 +30,29 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "gCryptoHash", function () {
return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
});
XPCOMUtils.defineLazyGetter(this, "gUnicodeConverter", function () {
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = 'utf8';
return converter;
});
/**
* Singleton providing functionality for capturing web page thumbnails and for
* accessing them if already cached.
*/
let PageThumbs = {
_initialized: false,
/**
* The calculated width and height of the thumbnails.
@ -52,6 +75,20 @@ let PageThumbs = {
*/
get contentType() "image/png",
init: function PageThumbs_init() {
if (!this._initialized) {
this._initialized = true;
PlacesUtils.history.addObserver(PageThumbsHistoryObserver, false);
}
},
uninit: function PageThumbs_uninit() {
if (this._initialized) {
this._initialized = false;
PlacesUtils.history.removeObserver(PageThumbsHistoryObserver);
}
},
/**
* Gets the thumbnail image's url for a given web page's url.
* @param aUrl The web page's url that is depicted in the thumbnail.
@ -124,32 +161,14 @@ let PageThumbs = {
// Sync and therefore also redirect sources appear on the newtab
// page. We also want thumbnails for those.
if (url != originalURL)
PageThumbsCache._copy(url, originalURL);
PageThumbsStorage.copy(url, originalURL);
}
if (aCallback)
aCallback(aSuccessful);
}
// Get a writeable cache entry.
PageThumbsCache.getWriteEntry(url, function (aEntry) {
if (!aEntry) {
finish(false);
return;
}
let outputStream = aEntry.openOutputStream(0);
// Write the image data to the cache entry.
NetUtil.asyncCopy(aInputStream, outputStream, function (aResult) {
let success = Components.isSuccessCode(aResult);
if (success)
aEntry.markValid();
aEntry.close();
finish(success);
});
});
PageThumbsStorage.write(url, aInputStream, finish);
});
},
@ -197,7 +216,7 @@ let PageThumbs = {
*/
_getThumbnailSize: function PageThumbs_getThumbnailSize() {
if (!this._thumbnailWidth || !this._thumbnailHeight) {
let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
.getService(Ci.nsIScreenManager);
let left = {}, top = {}, width = {}, height = {};
screenManager.primaryScreen.GetRect(left, top, width, height);
@ -208,6 +227,88 @@ let PageThumbs = {
}
};
let PageThumbsStorage = {
getFileForURL: function Storage_getFileForURL(aURL) {
let hash = this._calculateMD5Hash(aURL);
let parts = [THUMBNAIL_DIRECTORY, hash[0], hash[1], hash.slice(2) + ".png"];
return FileUtils.getFile("ProfD", parts);
},
write: function Storage_write(aURL, aDataStream, aCallback) {
let file = this.getFileForURL(aURL);
let fos = FileUtils.openSafeFileOutputStream(file);
NetUtil.asyncCopy(aDataStream, fos, function (aResult) {
FileUtils.closeSafeFileOutputStream(fos);
aCallback(Components.isSuccessCode(aResult));
});
},
copy: function Storage_copy(aSourceURL, aTargetURL) {
let sourceFile = this.getFileForURL(aSourceURL);
let targetFile = this.getFileForURL(aTargetURL);
try {
sourceFile.copyTo(targetFile.parent, targetFile.leafName);
} catch (e) {
/* We might not be permitted to write to the file. */
}
},
remove: function Storage_remove(aURL) {
try {
this.getFileForURL(aURL).remove(false);
} catch (e) {
/* The file might not exist or we're not permitted to remove it. */
}
},
wipe: function Storage_wipe() {
try {
FileUtils.getDir("ProfD", [THUMBNAIL_DIRECTORY]).remove(true);
} catch (e) {
/* The file might not exist or we're not permitted to remove it. */
}
},
_calculateMD5Hash: function Storage_calculateMD5Hash(aValue) {
let hash = gCryptoHash;
let value = gUnicodeConverter.convertToByteArray(aValue);
hash.init(hash.MD5);
hash.update(value, value.length);
return this._convertToHexString(hash.finish(false));
},
_convertToHexString: function Storage_convertToHexString(aData) {
let hex = "";
for (let i = 0; i < aData.length; i++)
hex += ("0" + aData.charCodeAt(i).toString(16)).slice(-2);
return hex;
},
};
let PageThumbsHistoryObserver = {
onDeleteURI: function Thumbnails_onDeleteURI(aURI, aGUID) {
PageThumbsStorage.remove(aURI.spec);
},
onClearHistory: function Thumbnails_onClearHistory() {
PageThumbsStorage.wipe();
},
onTitleChanged: function () {},
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onVisit: function () {},
onBeforeDeleteURI: function () {},
onPageChanged: function () {},
onDeleteVisits: function () {},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
};
/**
* A singleton handling the storage of page thumbnails.
*/
@ -222,64 +323,6 @@ let PageThumbsCache = {
this._openCacheEntry(aKey, Ci.nsICache.ACCESS_READ, aCallback);
},
/**
* Calls the given callback with a cache entry opened for writing.
* @param aKey The key identifying the desired cache entry.
* @param aCallback The callback that is called when the cache entry is ready.
*/
getWriteEntry: function Cache_getWriteEntry(aKey, aCallback) {
// Try to open the desired cache entry.
this._openCacheEntry(aKey, Ci.nsICache.ACCESS_WRITE, aCallback);
},
/**
* Copies an existing cache entry's data to a new cache entry.
* @param aSourceKey The key that contains the data to copy.
* @param aTargetKey The key that will be the copy of aSourceKey's data.
*/
_copy: function Cache_copy(aSourceKey, aTargetKey) {
let sourceEntry, targetEntry, waitingCount = 2;
function finish() {
if (sourceEntry)
sourceEntry.close();
if (targetEntry)
targetEntry.close();
}
function copyDataWhenReady() {
if (--waitingCount > 0)
return;
if (!sourceEntry || !targetEntry) {
finish();
return;
}
let inputStream = sourceEntry.openInputStream(0);
let outputStream = targetEntry.openOutputStream(0);
// Copy the image data to a new entry.
NetUtil.asyncCopy(inputStream, outputStream, function (aResult) {
if (Components.isSuccessCode(aResult))
targetEntry.markValid();
finish();
});
}
this.getReadEntry(aSourceKey, function (aSourceEntry) {
sourceEntry = aSourceEntry;
copyDataWhenReady();
});
this.getWriteEntry(aTargetKey, function (aTargetEntry) {
targetEntry = aTargetEntry;
copyDataWhenReady();
});
},
/**
* Opens the cache entry identified by the given key.
* @param aKey The key identifying the desired cache entry.

Просмотреть файл

@ -72,6 +72,14 @@ Protocol.prototype = {
* @return The newly created channel.
*/
newChannel: function Proto_newChannel(aURI) {
let {url} = parseURI(aURI);
let file = PageThumbsStorage.getFileForURL(url);
if (file.exists()) {
let fileuri = Services.io.newFileURI(file);
return Services.io.newChannelFromURI(fileuri);
}
return new Channel(aURI);
},

Просмотреть файл

@ -14,6 +14,7 @@ include $(topsrcdir)/config/rules.mk
_BROWSER_FILES = \
browser_thumbnails_capture.js \
browser_thumbnails_redirect.js \
browser_thumbnails_storage.js \
browser_thumbnails_bug726727.js \
head.js \
background_red.html \

Просмотреть файл

@ -4,9 +4,6 @@
const URL = "http://mochi.test:8888/browser/browser/components/thumbnails/" +
"test/background_red_redirect.sjs";
let cacheService = Cc["@mozilla.org/network/cache-service;1"]
.getService(Ci.nsICacheService);
/**
* These tests ensure that we save and provide thumbnails for redirecting sites.
*/
@ -19,33 +16,17 @@ function runTests() {
yield addTab(URL);
yield captureAndCheckColor(255, 0, 0, "we have a red thumbnail");
// Wait until the referrer's thumbnail's cache entry has been written.
yield whenCacheEntryExists(URL);
// Wait until the referrer's thumbnail's file has been written.
yield whenFileExists(URL);
yield checkThumbnailColor(URL, 255, 0, 0, "referrer has a red thumbnail");
}
function whenCacheEntryExists(aKey) {
function whenFileExists(aURL) {
let callback = next;
checkCacheEntryExists(aKey, function (aExists) {
if (!aExists)
callback = function () whenCacheEntryExists(aKey);
let file = PageThumbsStorage.getFileForURL(aURL);
if (!file.exists())
callback = function () whenFileExists(aURL);
executeSoon(callback);
});
}
function checkCacheEntryExists(aKey, aCallback) {
PageThumbsCache.getReadEntry(aKey, function (aEntry) {
let inputStream = aEntry && aEntry.openInputStream(0);
let exists = inputStream && inputStream.available();
if (inputStream)
inputStream.close();
if (aEntry)
aEntry.close();
aCallback(exists);
});
executeSoon(callback);
}

Просмотреть файл

@ -0,0 +1,89 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const URL = "http://mochi.test:8888/";
const URL_COPY = URL + "#copy";
XPCOMUtils.defineLazyGetter(this, "Sanitizer", function () {
let tmp = {};
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://browser/content/sanitize.js", tmp);
return tmp.Sanitizer;
});
/**
* These tests ensure that the thumbnail storage is working as intended.
* Newly captured thumbnails should be saved as files and they should as well
* be removed when the user sanitizes their history.
*/
function runTests() {
clearHistory();
// create a thumbnail
yield addTab(URL);
yield whenFileExists();
gBrowser.removeTab(gBrowser.selectedTab);
// clear all browser history
yield clearHistory();
// create a thumbnail
yield addTab(URL);
yield whenFileExists();
gBrowser.removeTab(gBrowser.selectedTab);
// make sure copy() updates an existing file
PageThumbsStorage.copy(URL, URL_COPY);
let copy = PageThumbsStorage.getFileForURL(URL_COPY);
let mtime = copy.lastModifiedTime -= 60;
PageThumbsStorage.copy(URL, URL_COPY);
isnot(PageThumbsStorage.getFileForURL(URL_COPY).lastModifiedTime, mtime,
"thumbnail file was updated");
// clear last 10 mins of history
yield clearHistory(true);
ok(!copy.exists(), "copy of thumbnail has been removed");
}
function clearHistory(aUseRange) {
let s = new Sanitizer();
s.prefDomain = "privacy.cpd.";
let prefs = gPrefService.getBranch(s.prefDomain);
prefs.setBoolPref("history", true);
prefs.setBoolPref("downloads", false);
prefs.setBoolPref("cache", false);
prefs.setBoolPref("cookies", false);
prefs.setBoolPref("formdata", false);
prefs.setBoolPref("offlineApps", false);
prefs.setBoolPref("passwords", false);
prefs.setBoolPref("sessions", false);
prefs.setBoolPref("siteSettings", false);
if (aUseRange) {
let usec = Date.now() * 1000;
s.range = [usec - 10 * 60 * 1000 * 1000, usec];
}
s.sanitize();
s.range = null;
executeSoon(function () {
if (PageThumbsStorage.getFileForURL(URL).exists())
clearHistory(aFile, aUseRange);
else
next();
});
}
function whenFileExists() {
let callback = whenFileExists;
let file = PageThumbsStorage.getFileForURL(URL);
if (file.exists() && file.fileSize)
callback = next;
executeSoon(callback);
}

Просмотреть файл

@ -4,7 +4,7 @@
let tmp = {};
Cu.import("resource:///modules/PageThumbs.jsm", tmp);
let PageThumbs = tmp.PageThumbs;
let PageThumbsCache = tmp.PageThumbsCache;
let PageThumbsStorage = tmp.PageThumbsStorage;
registerCleanupFunction(function () {
while (gBrowser.tabs.length > 1)

Просмотреть файл

@ -112,7 +112,7 @@ testxpcsrcdir = $(topsrcdir)/testing/xpcshell
# See also testsuite-targets.mk 'xpcshell-tests' target for global execution.
xpcshell-tests:
$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
-I$(topsrcdir)/build \
-I$(topsrcdir)/build -I$(DEPTH)/_tests/mozbase/mozinfo \
$(testxpcsrcdir)/runxpcshelltests.py \
--symbols-path=$(DIST)/crashreporter-symbols \
--build-info-json=$(DEPTH)/mozinfo.json \
@ -142,7 +142,7 @@ xpcshell-tests-remote:
# attach a debugger and then start the test.
check-interactive:
$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
-I$(topsrcdir)/build \
-I$(topsrcdir)/build -I$(DEPTH)/_tests/mozbase/mozinfo \
$(testxpcsrcdir)/runxpcshelltests.py \
--symbols-path=$(DIST)/crashreporter-symbols \
--build-info-json=$(DEPTH)/mozinfo.json \
@ -155,7 +155,7 @@ check-interactive:
# Execute a single test, specified in $(SOLO_FILE)
check-one:
$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
-I$(topsrcdir)/build \
-I$(topsrcdir)/build -I$(DEPTH)/_tests/mozbase/mozinfo \
$(testxpcsrcdir)/runxpcshelltests.py \
--symbols-path=$(DIST)/crashreporter-symbols \
--build-info-json=$(DEPTH)/mozinfo.json \

Просмотреть файл

@ -112,7 +112,7 @@ testxpcsrcdir = $(topsrcdir)/testing/xpcshell
# See also testsuite-targets.mk 'xpcshell-tests' target for global execution.
xpcshell-tests:
$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
-I$(topsrcdir)/build \
-I$(topsrcdir)/build -I$(DEPTH)/_tests/mozbase/mozinfo \
$(testxpcsrcdir)/runxpcshelltests.py \
--symbols-path=$(DIST)/crashreporter-symbols \
--build-info-json=$(DEPTH)/mozinfo.json \
@ -142,7 +142,7 @@ xpcshell-tests-remote:
# attach a debugger and then start the test.
check-interactive:
$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
-I$(topsrcdir)/build \
-I$(topsrcdir)/build -I$(DEPTH)/_tests/mozbase/mozinfo \
$(testxpcsrcdir)/runxpcshelltests.py \
--symbols-path=$(DIST)/crashreporter-symbols \
--build-info-json=$(DEPTH)/mozinfo.json \
@ -155,7 +155,7 @@ check-interactive:
# Execute a single test, specified in $(SOLO_FILE)
check-one:
$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
-I$(topsrcdir)/build \
-I$(topsrcdir)/build -I$(DEPTH)/_tests/mozbase/mozinfo \
$(testxpcsrcdir)/runxpcshelltests.py \
--symbols-path=$(DIST)/crashreporter-symbols \
--build-info-json=$(DEPTH)/mozinfo.json \

Просмотреть файл

@ -239,7 +239,7 @@ GARBAGE += $(addsuffix .log,$(MOCHITESTS) reftest crashtest jstestbrowser)
# Usage: |make [TEST_PATH=...] [EXTRA_TEST_ARGS=...] xpcshell-tests|.
xpcshell-tests:
$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
-I$(topsrcdir)/build \
-I$(topsrcdir)/build -I$(DEPTH)/_tests/mozbase/mozinfo \
$(topsrcdir)/testing/xpcshell/runxpcshelltests.py \
--manifest=$(DEPTH)/_tests/xpcshell/xpcshell.ini \
--build-info-json=$(DEPTH)/mozinfo.json \

Просмотреть файл

@ -72,5 +72,5 @@ endif
include $(topsrcdir)/config/rules.mk
libs::
$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
$(NSINSTALL) $(srcdir)/server/*.jsm $(FINAL_TARGET)/modules/devtools
$(INSTALL) $(IFLAGS1) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
$(INSTALL) $(IFLAGS1) $(srcdir)/server/*.jsm $(FINAL_TARGET)/modules/devtools

Просмотреть файл

@ -375,10 +375,10 @@ DebuggerClient.prototype = {
*/
request: function DC_request(aRequest, aOnResponse) {
if (!this._connected) {
throw "Have not yet received a hello packet from the server.";
throw Error("Have not yet received a hello packet from the server.");
}
if (!aRequest.to) {
throw "Request packet has no destination.";
throw Error("Request packet has no destination.");
}
this._pendingRequests.push({ to: aRequest.to,
@ -449,7 +449,7 @@ DebuggerClient.prototype = {
onResponse(aPacket);
}
} catch(ex) {
dumpn("Error handling response: " + ex + " - " + ex.stack);
dumpn("Error handling response: " + ex + " - stack:\n" + ex.stack);
Cu.reportError(ex);
}
@ -536,7 +536,7 @@ ThreadClient.prototype = {
_assertPaused: function TC_assertPaused(aCommand) {
if (!this.paused) {
throw aCommand + " command sent while not paused.";
throw Error(aCommand + " command sent while not paused.");
}
},

Просмотреть файл

@ -63,6 +63,17 @@ function dbg_assert(cond, e) {
}
}
/* Turn the error e into a string, without fail. */
function safeErrorString(aError) {
try {
var s = aError.toString();
if (typeof s === "string")
return s;
} catch (ee) { }
return "<failed trying to find error description>";
}
loadSubScript.call(this, "chrome://global/content/devtools/dbg-transport.js");
// XPCOM constructors
@ -445,11 +456,14 @@ DebuggerServerConnection.prototype = {
} catch(e) {
Cu.reportError(e);
ret = { error: "unknownError",
message: "An unknown error has occurred while processing request." };
message: ("error occurred while processing '" + aPacket.type +
"' request: " + safeErrorString(e)) };
}
} else {
ret = { error: "unrecognizedPacketType",
message: 'Actor "' + actor.actorID + '" does not recognize the packet type "' + aPacket.type + '"' };
message: ('Actor "' + actor.actorID +
'" does not recognize the packet type "' +
aPacket.type + '"') };
}
if (!ret) {

Просмотреть файл

@ -7,10 +7,61 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource:///modules/devtools/dbg-server.jsm");
Cu.import("resource:///modules/devtools/dbg-client.jsm");
Cu.import("resource://gre/modules/Services.jsm");
// Always log packets when running tests. runxpcshelltests.py will throw
// the output away anyway, unless you give it the --verbose flag.
Services.prefs.setBoolPref("devtools.debugger.log", true);
Cu.import("resource:///modules/devtools/dbg-server.jsm");
Cu.import("resource:///modules/devtools/dbg-client.jsm");
// Convert an nsIScriptError 'aFlags' value into an appropriate string.
function scriptErrorFlagsToKind(aFlags) {
var kind;
if (aFlags & Ci.nsIScriptError.warningFlag)
kind = "warning";
if (aFlags & Ci.nsIScriptError.exceptionFlag)
kind = "exception";
else
kind = "error";
if (aFlags & Ci.nsIScriptError.strictFlag)
kind = "strict " + kind;
return kind;
}
// Register a console listener, so console messages don't just disappear
// into the ether.
let errorCount = 0;
let listener = {
observe: function (aMessage) {
errorCount++;
try {
// If we've been given an nsIScriptError, then we can print out
// something nicely formatted, for tools like Emacs to pick up.
var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
scriptErrorFlagsToKind(aMessage.flags) + ": " +
aMessage.errorMessage + "\n");
var string = aMessage.errorMessage;
} catch (x) {
// Be a little paranoid with message, as the whole goal here is to lose
// no information.
try {
var string = "" + aMessage.message;
} catch (x) {
var string = "<error converting error message to string>";
}
}
do_throw("head_dbg.js got console message: " + string + "\n");
}
};
let consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
consoleService.registerListener(listener);
function check_except(func)
{

Просмотреть файл

@ -27,8 +27,8 @@ function run_test()
function test_breakpoint_running()
{
let path = getFilePath('test_breakpoint-01.js');
let location = { url: path, line: gDebuggee.line0 + 3};
let path = getFilePath('test_breakpoint-02.js');
let location = { url: path, line: gDebuggee.line0 + 2};
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"var a = 1;\n" + // line0 + 1
@ -37,8 +37,6 @@ function test_breakpoint_running()
// Setting the breakpoint later should interrupt the debuggee.
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
do_check_eq(aPacket.type, "paused");
do_check_eq(aPacket.frame.where.url, path);
do_check_eq(aPacket.frame.where.line, location);
do_check_eq(aPacket.why.type, "interrupted");
});

Просмотреть файл

@ -70,10 +70,10 @@ function release_grips(aFrameArgs, aThreadGrips)
arg_grips(aFrameArgs, function (aNewGrips) {
for (let i = 0; i < aNewGrips.length; i++) {
do_check_neq(aThreadGrips[i].actor, aNewGrips[i].actor);
gThreadClient.resume(function () {
finishClient(gClient);
});
}
gThreadClient.resume(function () {
finishClient(gClient);
});
});
});
});