From 218ffaae77d110160ff99c09b2325ab600d6c146 Mon Sep 17 00:00:00 2001 From: Brad Lassey Date: Tue, 30 Jun 2015 18:08:19 -0400 Subject: [PATCH] bug 1158561 - [e10s] Browser hang in PluginModuleParent::NPP_ClearSiteData() r=jimm,mak --- browser/base/content/sanitize.js | 82 ++++++-- .../test/plugins/browser_clearplugindata.js | 6 +- dom/plugins/base/PluginPRLibrary.cpp | 23 ++- dom/plugins/base/PluginPRLibrary.h | 4 +- dom/plugins/base/nsIPluginHost.idl | 14 +- dom/plugins/base/nsPluginHost.cpp | 178 +++++++++++++----- dom/plugins/base/nsPluginHost.h | 9 +- dom/plugins/ipc/PPluginModule.ipdl | 12 +- dom/plugins/ipc/PluginLibrary.h | 13 +- dom/plugins/ipc/PluginModuleChild.cpp | 19 +- dom/plugins/ipc/PluginModuleChild.h | 6 +- dom/plugins/ipc/PluginModuleParent.cpp | 72 +++++-- dom/plugins/ipc/PluginModuleParent.h | 18 +- .../test/mochitest/test_clear_site_data.html | 134 ++++++++----- toolkit/forgetaboutsite/ForgetAboutSite.jsm | 19 +- .../test/browser/browser_clearplugindata.js | 19 +- 16 files changed, 452 insertions(+), 176 deletions(-) diff --git a/browser/base/content/sanitize.js b/browser/base/content/sanitize.js index 2e01cbd2f21..bc602d687b7 100644 --- a/browser/base/content/sanitize.js +++ b/browser/base/content/sanitize.js @@ -94,6 +94,34 @@ Sanitizer.prototype = { return deferred.promise; } + let cookiesIndex = itemsToClear.indexOf("cookies"); + if (cookiesIndex != -1) { + itemsToClear.splice(cookiesIndex, 1); + let item = this.items.cookies; + item.range = this.range; + let ok = item.clear(() => { + try { + if (!itemsToClear.length) { + // we're done + deferred.resolve(); + return; + } + let clearedPromise = this.sanitize(itemsToClear); + clearedPromise.then(deferred.resolve, deferred.reject); + } catch(e) { + let error = "Sanitizer threw after clearing cookies: " + e; + Cu.reportError(error); + deferred.reject(error); + } + }); + // When cancelled, reject immediately + if (!ok) { + deferred.reject("Sanitizer canceled clearing cookies"); + } + + return deferred.promise; + } + TelemetryStopwatch.start("FX_SANITIZE_TOTAL"); // Cache the range of times to clear @@ -177,7 +205,7 @@ Sanitizer.prototype = { }, cookies: { - clear: function () + clear: function (aCallback) { TelemetryStopwatch.start("FX_SANITIZE_COOKIES"); TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2"); @@ -209,6 +237,16 @@ Sanitizer.prototype = { // Clear plugin data. TelemetryStopwatch.start("FX_SANITIZE_PLUGINS"); + this.clearPluginCookies().then( + function() { + TelemetryStopwatch.finish("FX_SANITIZE_PLUGINS"); + TelemetryStopwatch.finish("FX_SANITIZE_COOKIES"); + aCallback(); + }); + return true; + }, + + clearPluginCookies: function() { const phInterface = Ci.nsIPluginHost; const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL; let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface); @@ -217,29 +255,35 @@ Sanitizer.prototype = { // that this.range[1] is actually now, so we compute age range based // on the lower bound. If this.range results in a negative age, do // nothing. - let age = this.range ? (Date.now() / 1000 - this.range[0] / 1000000) - : -1; + let age = this.range ? (Date.now() / 1000 - this.range[0] / 1000000) : -1; if (!this.range || age >= 0) { let tags = ph.getPluginTags(); - for (let i = 0; i < tags.length; i++) { - try { - ph.clearSiteData(tags[i], null, FLAG_CLEAR_ALL, age); - } catch (e) { - // If the plugin doesn't support clearing by age, clear everything. - if (e.result == Components.results. - NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) { - try { - ph.clearSiteData(tags[i], null, FLAG_CLEAR_ALL, -1); - } catch (e) { - // Ignore errors from the plugin - } + function iterate(tag) { + let promise = new Promise(resolve => { + try { + let onClear = function(rv) { + // If the plugin doesn't support clearing by age, clear everything. + if (rv == Components.results. NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) { + ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, -1, function() { + resolve(); + }); + } else { + resolve(); + } + }; + ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, age, onClear); + } catch (ex) { + resolve(); } - } + }); + return promise; } + let promises = []; + for (let tag of tags) { + promises.push(iterate(tag)); + } + return Promise.all(promises); } - - TelemetryStopwatch.finish("FX_SANITIZE_PLUGINS"); - TelemetryStopwatch.finish("FX_SANITIZE_COOKIES"); }, get canClear() diff --git a/browser/base/content/test/plugins/browser_clearplugindata.js b/browser/base/content/test/plugins/browser_clearplugindata.js index 6dea3504e85..555c0a3e994 100644 --- a/browser/base/content/test/plugins/browser_clearplugindata.js +++ b/browser/base/content/test/plugins/browser_clearplugindata.js @@ -84,7 +84,7 @@ add_task(function* () { // Clear 20 seconds ago let now_uSec = Date.now() * 1000; sanitizer.range = [now_uSec - 20*1000000, now_uSec]; - sanitizer.sanitize(); + yield sanitizer.sanitize(); ok(stored(["bar.com","qux.com"]), "Data stored for sites"); ok(!stored(["foo.com"]), "Data cleared for foo.com"); @@ -92,7 +92,7 @@ add_task(function* () { // Clear everything sanitizer.range = null; - sanitizer.sanitize(); + yield sanitizer.sanitize(); ok(!stored(null), "All data cleared"); @@ -117,7 +117,7 @@ add_task(function* () { // clearing all data regardless of age. let now_uSec = Date.now() * 1000; sanitizer.range = [now_uSec - 20*1000000, now_uSec]; - sanitizer.sanitize(); + yield sanitizer.sanitize(); ok(!stored(null), "All data cleared"); diff --git a/dom/plugins/base/PluginPRLibrary.cpp b/dom/plugins/base/PluginPRLibrary.cpp index 313276161fa..35f17f4a072 100644 --- a/dom/plugins/base/PluginPRLibrary.cpp +++ b/dom/plugins/base/PluginPRLibrary.cpp @@ -204,7 +204,7 @@ PluginPRLibrary::NPP_New(NPMIMEType pluginType, NPP instance, nsresult PluginPRLibrary::NPP_ClearSiteData(const char* site, uint64_t flags, - uint64_t maxAge) + uint64_t maxAge, nsCOMPtr callback) { if (!mNPP_ClearSiteData) { return NS_ERROR_NOT_AVAILABLE; @@ -213,39 +213,44 @@ PluginPRLibrary::NPP_ClearSiteData(const char* site, uint64_t flags, MAIN_THREAD_JNI_REF_GUARD; NPError result = mNPP_ClearSiteData(site, flags, maxAge); + nsresult rv; switch (result) { case NPERR_NO_ERROR: - return NS_OK; + rv = NS_OK; + break; case NPERR_TIME_RANGE_NOT_SUPPORTED: - return NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED; + rv = NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED; + break; case NPERR_MALFORMED_SITE: - return NS_ERROR_INVALID_ARG; + rv = NS_ERROR_INVALID_ARG; + break; default: - return NS_ERROR_FAILURE; + rv = NS_ERROR_FAILURE; } + callback->Callback(rv); + return NS_OK; } nsresult -PluginPRLibrary::NPP_GetSitesWithData(InfallibleTArray& result) +PluginPRLibrary::NPP_GetSitesWithData(nsCOMPtr callback) { if (!mNPP_GetSitesWithData) { return NS_ERROR_NOT_AVAILABLE; } - result.Clear(); - MAIN_THREAD_JNI_REF_GUARD; char** sites = mNPP_GetSitesWithData(); if (!sites) { return NS_OK; } - + InfallibleTArray result; char** iterator = sites; while (*iterator) { result.AppendElement(*iterator); free(*iterator); ++iterator; } + callback->SitesWithData(result); free(sites); return NS_OK; diff --git a/dom/plugins/base/PluginPRLibrary.h b/dom/plugins/base/PluginPRLibrary.h index c92ef831062..8bbcd977362 100644 --- a/dom/plugins/base/PluginPRLibrary.h +++ b/dom/plugins/base/PluginPRLibrary.h @@ -105,8 +105,8 @@ public: NPError* aError) override; virtual nsresult NPP_ClearSiteData(const char* aSite, uint64_t aFlags, - uint64_t aMaxAge) override; - virtual nsresult NPP_GetSitesWithData(InfallibleTArray& aResult) override; + uint64_t aMaxAge, nsCOMPtr callback) override; + virtual nsresult NPP_GetSitesWithData(nsCOMPtr callback) override; virtual nsresult AsyncSetWindow(NPP aInstance, NPWindow* aWindow) override; virtual nsresult GetImageContainer(NPP aInstance, mozilla::layers::ImageContainer** aContainer) override; diff --git a/dom/plugins/base/nsIPluginHost.idl b/dom/plugins/base/nsIPluginHost.idl index f1ac372834a..f62a763e203 100644 --- a/dom/plugins/base/nsIPluginHost.idl +++ b/dom/plugins/base/nsIPluginHost.idl @@ -27,7 +27,16 @@ interface nsIPluginPlayPreviewInfo : nsISupports boolean checkWhitelist(in AUTF8String pageURI, in AUTF8String objectURI); }; -[scriptable, uuid(d7d5b2e0-105b-4c9d-8558-b6b31f28b7df)] +[scriptable, function, uuid(9c311778-7c2c-4ad8-b439-b8a2786a20dd)] +interface nsIClearSiteDataCallback : nsISupports +{ + /** + * callback with the result from a call to clearSiteData + */ + void callback(in nsresult rv); +}; + +[scriptable, uuid(a884e736-5396-4400-bc82-9a23d871d12c)] interface nsIPluginHost : nsISupports { /** @@ -77,7 +86,8 @@ interface nsIPluginHost : nsISupports * general or for that particular site and/or flag combination. */ void clearSiteData(in nsIPluginTag plugin, in AUTF8String domain, - in uint64_t flags, in int64_t maxAge); + in uint64_t flags, in int64_t maxAge, + in nsIClearSiteDataCallback callback); /* * Determine if a plugin has stored data for a given site. diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp index 94d6f3f2e5f..8b416d06df4 100644 --- a/dom/plugins/base/nsPluginHost.cpp +++ b/dom/plugins/base/nsPluginHost.cpp @@ -1534,10 +1534,77 @@ nsPluginHost::GetPlayPreviewInfo(const nsACString& mimeType, return NS_ERROR_NOT_AVAILABLE; } +#define ClearDataFromSitesClosure_CID {0x9fb21761, 0x2403, 0x41ad, {0x9e, 0xfd, 0x36, 0x7e, 0xc4, 0x4f, 0xa4, 0x5e}} + + +// Class to hold all the data we need need for IterateMatchesAndClear and ClearDataFromSites +class ClearDataFromSitesClosure : public nsIClearSiteDataCallback, public nsIGetSitesWithDataCallback { +public: + ClearDataFromSitesClosure(nsIPluginTag* plugin, const nsACString& domain, uint64_t flags, + int64_t maxAge, nsCOMPtr callback, + nsPluginHost* host) : + domain(domain), callback(callback), tag(plugin), flags(flags), maxAge(maxAge), host(host) {} + NS_DECL_ISUPPORTS + + // Callback from NPP_ClearSiteData, continue to iterate the matches and clear + NS_IMETHOD Callback(nsresult rv) { + if (NS_FAILED(rv)) { + callback->Callback(rv); + return NS_OK; + } + if (!matches.Length()) { + callback->Callback(NS_OK); + return NS_OK; + } + + const nsCString match(matches[0]); + matches.RemoveElement(match); + PluginLibrary* library = static_cast(tag)->mPlugin->GetLibrary(); + rv = library->NPP_ClearSiteData(match.get(), flags, maxAge, this); + if (NS_FAILED(rv)) { + callback->Callback(rv); + return NS_OK; + } + return NS_OK; + } + + // Callback from NPP_GetSitesWithData, kick the iteration off to clear the data + NS_IMETHOD SitesWithData(InfallibleTArray& sites) + { + // Enumerate the sites and build a list of matches. + nsresult rv = host->EnumerateSiteData(domain, sites, matches, false); + Callback(rv); + return NS_OK; + } + + nsCString domain; + nsCOMPtr callback; + InfallibleTArray matches; + nsIPluginTag* tag; + uint64_t flags; + int64_t maxAge; + nsPluginHost* host; + NS_DECLARE_STATIC_IID_ACCESSOR(ClearDataFromSitesClosure_CID) + private: + virtual ~ClearDataFromSitesClosure() {} +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(ClearDataFromSitesClosure, ClearDataFromSitesClosure_CID) + +NS_IMPL_ADDREF(ClearDataFromSitesClosure) +NS_IMPL_RELEASE(ClearDataFromSitesClosure) + +NS_INTERFACE_MAP_BEGIN(ClearDataFromSitesClosure) + NS_INTERFACE_MAP_ENTRY(nsIClearSiteDataCallback) + NS_INTERFACE_MAP_ENTRY(nsIGetSitesWithDataCallback) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIClearSiteDataCallback) +NS_INTERFACE_MAP_END + NS_IMETHODIMP nsPluginHost::ClearSiteData(nsIPluginTag* plugin, const nsACString& domain, - uint64_t flags, int64_t maxAge) + uint64_t flags, int64_t maxAge, nsIClearSiteDataCallback* callbackFunc) { + nsCOMPtr callback(callbackFunc); // maxAge must be either a nonnegative integer or -1. NS_ENSURE_ARG(maxAge >= 0 || maxAge == -1); @@ -1569,29 +1636,71 @@ nsPluginHost::ClearSiteData(nsIPluginTag* plugin, const nsACString& domain, // If 'domain' is the null string, clear everything. if (domain.IsVoid()) { - return library->NPP_ClearSiteData(nullptr, flags, maxAge); + return library->NPP_ClearSiteData(nullptr, flags, maxAge, callback); } - - // Get the list of sites from the plugin. - InfallibleTArray sites; - rv = library->NPP_GetSitesWithData(sites); + nsCOMPtr closure(new ClearDataFromSitesClosure(plugin, domain, flags, + maxAge, callback, this)); + rv = library->NPP_GetSitesWithData(closure); NS_ENSURE_SUCCESS(rv, rv); - - // Enumerate the sites and build a list of matches. - InfallibleTArray matches; - rv = EnumerateSiteData(domain, sites, matches, false); - NS_ENSURE_SUCCESS(rv, rv); - - // Clear the matches. - for (uint32_t i = 0; i < matches.Length(); ++i) { - const nsCString& match = matches[i]; - rv = library->NPP_ClearSiteData(match.get(), flags, maxAge); - NS_ENSURE_SUCCESS(rv, rv); - } - return NS_OK; } +#define GetSitesClosure_CID {0x4c9268ac, 0x2fd1, 0x4f2a, {0x9a, 0x10, 0x7a, 0x09, 0xf1, 0xb7, 0x60, 0x3a}} + +// Closure to contain the data needed to handle the callback from NPP_GetSitesWithData +class GetSitesClosure : public nsIGetSitesWithDataCallback { +public: + NS_DECL_ISUPPORTS + GetSitesClosure(const nsACString& domain, nsPluginHost* host) + : domain(domain), host(host), keepWaiting(true) + { + } + NS_IMETHOD SitesWithData(InfallibleTArray& sites) { + retVal = HandleGetSites(sites); + keepWaiting = false; + return NS_OK; + } + + nsresult HandleGetSites(InfallibleTArray& sites) { + // If there's no data, we're done. + if (sites.IsEmpty()) { + result = false; + return NS_OK; + } + + // If 'domain' is the null string, and there's data for at least one site, + // we're done. + if (domain.IsVoid()) { + result = true; + return NS_OK; + } + + // Enumerate the sites and determine if there's a match. + InfallibleTArray matches; + nsresult rv = host->EnumerateSiteData(domain, sites, matches, true); + NS_ENSURE_SUCCESS(rv, rv); + + result = !matches.IsEmpty(); + return NS_OK; + } + + nsCString domain; + nsRefPtr host; + bool result; + bool keepWaiting; + nsresult retVal; + NS_DECLARE_STATIC_IID_ACCESSOR(GetSitesClosure_CID) + private: + virtual ~GetSitesClosure() { + } +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(GetSitesClosure, GetSitesClosure_CID) + +NS_IMPL_ISUPPORTS(GetSitesClosure, GetSitesClosure, nsIGetSitesWithDataCallback) + +// This will spin the event loop while waiting on an async +// call to GetSitesWithData NS_IMETHODIMP nsPluginHost::SiteHasData(nsIPluginTag* plugin, const nsACString& domain, bool* result) @@ -1619,31 +1728,16 @@ nsPluginHost::SiteHasData(nsIPluginTag* plugin, const nsACString& domain, PluginLibrary* library = tag->mPlugin->GetLibrary(); - // Get the list of sites from the plugin. - InfallibleTArray sites; - rv = library->NPP_GetSitesWithData(sites); + // Get the list of sites from the plugin + nsCOMPtr closure(new GetSitesClosure(domain, this)); + rv = library->NPP_GetSitesWithData(nsCOMPtr(do_QueryInterface(closure))); NS_ENSURE_SUCCESS(rv, rv); - - // If there's no data, we're done. - if (sites.IsEmpty()) { - *result = false; - return NS_OK; + // Spin the event loop while we wait for the async call to GetSitesWithData + while (closure->keepWaiting) { + NS_ProcessNextEvent(nullptr, true); } - - // If 'domain' is the null string, and there's data for at least one site, - // we're done. - if (domain.IsVoid()) { - *result = true; - return NS_OK; - } - - // Enumerate the sites and determine if there's a match. - InfallibleTArray matches; - rv = EnumerateSiteData(domain, sites, matches, true); - NS_ENSURE_SUCCESS(rv, rv); - - *result = !matches.IsEmpty(); - return NS_OK; + *result = closure->result; + return closure->retVal; } nsPluginHost::SpecialType diff --git a/dom/plugins/base/nsPluginHost.h b/dom/plugins/base/nsPluginHost.h index f725b5cd0ce..b406428ce22 100644 --- a/dom/plugins/base/nsPluginHost.h +++ b/dom/plugins/base/nsPluginHost.h @@ -243,6 +243,11 @@ public: void CreateWidget(nsPluginInstanceOwner* aOwner); + nsresult EnumerateSiteData(const nsACString& domain, + const InfallibleTArray& sites, + InfallibleTArray& result, + bool firstMatchOnly); + private: friend class nsPluginUnloadRunnable; @@ -366,10 +371,6 @@ private: // Helpers for ClearSiteData and SiteHasData. nsresult NormalizeHostname(nsCString& host); - nsresult EnumerateSiteData(const nsACString& domain, - const InfallibleTArray& sites, - InfallibleTArray& result, - bool firstMatchOnly); nsWeakPtr mCurrentDocument; // weak reference, we use it to id document only diff --git a/dom/plugins/ipc/PPluginModule.ipdl b/dom/plugins/ipc/PPluginModule.ipdl index 616b2495b14..dcfc616b6ca 100644 --- a/dom/plugins/ipc/PPluginModule.ipdl +++ b/dom/plugins/ipc/PPluginModule.ipdl @@ -76,11 +76,9 @@ child: returns (bool aURLRedirectNotify, bool aClearSiteData, bool aGetSitesWithData); - intr NPP_ClearSiteData(nsCString site, uint64_t flags, uint64_t maxAge) - returns (NPError rv); + async NPP_ClearSiteData(nsCString site, uint64_t flags, uint64_t maxAge, uint64_t aCallbackId); - intr NPP_GetSitesWithData() - returns (nsCString[] sites); + async NPP_GetSitesWithData(uint64_t aCallbackId); // Windows specific message to set up an audio session in the plugin process async SetAudioSessionData(nsID aID, @@ -148,6 +146,12 @@ parent: async NotifyContentModuleDestroyed(); async Profile(nsCString aProfile); + + // Answers to request about site data + async ReturnClearSiteData(NPError aRv, uint64_t aCallbackId); + + async ReturnSitesWithData(nsCString[] aSites, uint64_t aCallbackId); + }; } // namespace plugins diff --git a/dom/plugins/ipc/PluginLibrary.h b/dom/plugins/ipc/PluginLibrary.h index 8422edccb70..63ea2f80064 100644 --- a/dom/plugins/ipc/PluginLibrary.h +++ b/dom/plugins/ipc/PluginLibrary.h @@ -28,6 +28,15 @@ class ImageContainer; } } +class nsIClearSiteDataCallback; + +#define nsIGetSitesWithDataCallback_CID {0xd0028b83, 0xfdf9, 0x4c53, {0xb7, 0xbb, 0x47, 0x46, 0x0f, 0x6b, 0x83, 0x6c}} +class nsIGetSitesWithDataCallback : public nsISupports { +public: + NS_IMETHOD SitesWithData(InfallibleTArray& result) = 0; + NS_DECLARE_STATIC_IID_ACCESSOR(nsIGetSitesWithDataCallback_CID) +}; +NS_DEFINE_STATIC_IID_ACCESSOR(nsIGetSitesWithDataCallback, nsIGetSitesWithDataCallback_CID) namespace mozilla { @@ -62,8 +71,8 @@ public: NPError* error) = 0; virtual nsresult NPP_ClearSiteData(const char* site, uint64_t flags, - uint64_t maxAge) = 0; - virtual nsresult NPP_GetSitesWithData(InfallibleTArray& aResult) = 0; + uint64_t maxAge, nsCOMPtr callback) = 0; + virtual nsresult NPP_GetSitesWithData(nsCOMPtr callback) = 0; virtual nsresult AsyncSetWindow(NPP instance, NPWindow* window) = 0; virtual nsresult GetImageContainer(NPP instance, mozilla::layers::ImageContainer** aContainer) = 0; diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp index e43a0e55808..0d3dc6025ac 100644 --- a/dom/plugins/ipc/PluginModuleChild.cpp +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -734,31 +734,34 @@ PluginModuleChild::AnswerOptionalFunctionsSupported(bool *aURLRedirectNotify, } bool -PluginModuleChild::AnswerNPP_ClearSiteData(const nsCString& aSite, +PluginModuleChild::RecvNPP_ClearSiteData(const nsCString& aSite, const uint64_t& aFlags, const uint64_t& aMaxAge, - NPError* aResult) + const uint64_t& aCallbackId) { - *aResult = + NPError result = mFunctions.clearsitedata(NullableStringGet(aSite), aFlags, aMaxAge); + SendReturnClearSiteData(result, aCallbackId); return true; } bool -PluginModuleChild::AnswerNPP_GetSitesWithData(InfallibleTArray* aResult) +PluginModuleChild::RecvNPP_GetSitesWithData(const uint64_t& aCallbackId) { char** result = mFunctions.getsiteswithdata(); - if (!result) + InfallibleTArray array; + if (!result) { + SendReturnSitesWithData(array, aCallbackId); return true; - + } char** iterator = result; while (*iterator) { - aResult->AppendElement(*iterator); + array.AppendElement(*iterator); free(*iterator); ++iterator; } + SendReturnSitesWithData(array, aCallbackId); free(result); - return true; } diff --git a/dom/plugins/ipc/PluginModuleChild.h b/dom/plugins/ipc/PluginModuleChild.h index 9d58f9ac9ab..322c967f993 100644 --- a/dom/plugins/ipc/PluginModuleChild.h +++ b/dom/plugins/ipc/PluginModuleChild.h @@ -112,13 +112,13 @@ protected: bool *aGetSitesWithData) override; virtual bool - AnswerNPP_ClearSiteData(const nsCString& aSite, + RecvNPP_ClearSiteData(const nsCString& aSite, const uint64_t& aFlags, const uint64_t& aMaxAge, - NPError* aResult) override; + const uint64_t& aCallbackId) override; virtual bool - AnswerNPP_GetSitesWithData(InfallibleTArray* aResult) override; + RecvNPP_GetSitesWithData(const uint64_t& aCallbackId) override; virtual bool RecvSetAudioSessionData(const nsID& aId, diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index 10ff6713394..05a61c608f0 100755 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -2654,35 +2654,34 @@ PluginModuleChromeParent::UpdatePluginTimeout() } nsresult -PluginModuleParent::NPP_ClearSiteData(const char* site, uint64_t flags, - uint64_t maxAge) +PluginModuleParent::NPP_ClearSiteData(const char* site, uint64_t flags, uint64_t maxAge, + nsCOMPtr callback) { if (!mClearSiteDataSupported) return NS_ERROR_NOT_AVAILABLE; - NPError result; - if (!CallNPP_ClearSiteData(NullableString(site), flags, maxAge, &result)) - return NS_ERROR_FAILURE; + static uint64_t callbackId = 0; + callbackId++; + mClearSiteDataCallbacks[callbackId] = callback; - switch (result) { - case NPERR_NO_ERROR: - return NS_OK; - case NPERR_TIME_RANGE_NOT_SUPPORTED: - return NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED; - case NPERR_MALFORMED_SITE: - return NS_ERROR_INVALID_ARG; - default: + if (!SendNPP_ClearSiteData(NullableString(site), flags, maxAge, callbackId)) { return NS_ERROR_FAILURE; } + return NS_OK; } + nsresult -PluginModuleParent::NPP_GetSitesWithData(InfallibleTArray& result) +PluginModuleParent::NPP_GetSitesWithData(nsCOMPtr callback) { if (!mGetSitesWithDataSupported) return NS_ERROR_NOT_AVAILABLE; - if (!CallNPP_GetSitesWithData(&result)) + static uint64_t callbackId = 0; + callbackId++; + mSitesWithDataCallbacks[callbackId] = callback; + + if (!SendNPP_GetSitesWithData(callbackId)) return NS_ERROR_FAILURE; return NS_OK; @@ -2936,6 +2935,49 @@ PluginModuleChromeParent::RecvNotifyContentModuleDestroyed() return true; } +bool +PluginModuleParent::RecvReturnClearSiteData(const NPError& aRv, + const uint64_t& aCallbackId) +{ + if (mClearSiteDataCallbacks.find(aCallbackId) == mClearSiteDataCallbacks.end()) { + return true; + } + if (!!mClearSiteDataCallbacks[aCallbackId]) { + nsresult rv; + switch (aRv) { + case NPERR_NO_ERROR: + rv = NS_OK; + break; + case NPERR_TIME_RANGE_NOT_SUPPORTED: + rv = NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED; + break; + case NPERR_MALFORMED_SITE: + rv = NS_ERROR_INVALID_ARG; + break; + default: + rv = NS_ERROR_FAILURE; + } + mClearSiteDataCallbacks[aCallbackId]->Callback(rv); + } + mClearSiteDataCallbacks.erase(aCallbackId); + return true; +} + +bool +PluginModuleParent::RecvReturnSitesWithData(nsTArray&& aSites, + const uint64_t& aCallbackId) +{ + if (mSitesWithDataCallbacks.find(aCallbackId) == mSitesWithDataCallbacks.end()) { + return true; + } + + if (!!mSitesWithDataCallbacks[aCallbackId]) { + mSitesWithDataCallbacks[aCallbackId]->SitesWithData(aSites); + } + mSitesWithDataCallbacks.erase(aCallbackId); + return true; +} + #ifdef MOZ_CRASHREPORTER_INJECTOR // We only add the crash reporter to subprocess which have the filename diff --git a/dom/plugins/ipc/PluginModuleParent.h b/dom/plugins/ipc/PluginModuleParent.h index 30ab7d51490..45206c2a05c 100644 --- a/dom/plugins/ipc/PluginModuleParent.h +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -200,6 +200,12 @@ protected: virtual bool RecvProfile(const nsCString& aProfile) override { return true; } + virtual bool RecvReturnClearSiteData(const NPError& aRv, + const uint64_t& aCallbackId); + + virtual bool RecvReturnSitesWithData(nsTArray&& aSites, + const uint64_t& aCallbackId); + void SetPluginFuncs(NPPluginFuncs* aFuncs); nsresult NPP_NewInternal(NPMIMEType pluginType, NPP instance, uint16_t mode, @@ -264,9 +270,15 @@ protected: uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved, NPError* error) override; - virtual nsresult NPP_ClearSiteData(const char* site, uint64_t flags, - uint64_t maxAge) override; - virtual nsresult NPP_GetSitesWithData(InfallibleTArray& result) override; + virtual nsresult NPP_ClearSiteData(const char* site, uint64_t flags, uint64_t maxAge, + nsCOMPtr callback) override; + virtual nsresult NPP_GetSitesWithData(nsCOMPtr callback) override; + +private: + std::map> mClearSiteDataCallbacks; + std::map> mSitesWithDataCallbacks; + +public: #if defined(XP_MACOSX) virtual nsresult IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing) override; diff --git a/dom/plugins/test/mochitest/test_clear_site_data.html b/dom/plugins/test/mochitest/test_clear_site_data.html index 4836fca1409..770254f2a9f 100644 --- a/dom/plugins/test/mochitest/test_clear_site_data.html +++ b/dom/plugins/test/mochitest/test_clear_site_data.html @@ -28,9 +28,7 @@ SimpleTest.executeSoon(function() { // Make sure clearing by timerange is supported. p.setSitesWithDataCapabilities(true); - ok(PluginUtils.withTestPlugin(runTest), "Test plugin found"); - SimpleTest.finish(); }); function stored(needles) { @@ -59,7 +57,6 @@ function runTest(pluginTag) { this.pluginTag = pluginTag; - p.setSitesWithData( "foo.com:0:5," + "foo.com:0:7," + @@ -69,75 +66,99 @@ "qux.com:1:5," + "quz.com:1:8" ); - - ok(stored(["foo.com","bar.com","baz.com","qux.com","quz.com"]), - "Data stored for sites"); - - // Clear nothing. - pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_ALL, 4); ok(stored(["foo.com","bar.com","baz.com","qux.com","quz.com"]), "Data stored for sites"); - pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, 4); + // Clear nothing. + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_ALL, 4, {callback: function() { test1(); }}); + } + function test1() { + ok(stored(["foo.com","bar.com","baz.com","qux.com","quz.com"]), + "Data stored for sites"); + + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, 4, {callback: function() { test2(); }}); + } + function test2() { ok(stored(["foo.com","bar.com","baz.com","qux.com","quz.com"]), "Data stored for sites"); // Clear cache data 5 seconds or older. - pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, 5); + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, 5, {callback: function() { test3(); }}); + } + function test3() { ok(stored(["foo.com","bar.com","baz.com","quz.com"]), "Data stored for sites"); ok(!stored(["qux.com"]), "Data cleared for qux.com"); - // Clear cache data for foo.com, but leave non-cache data. - pluginHost.clearSiteData(pluginTag, "foo.com", FLAG_CLEAR_CACHE, 20); + pluginHost.clearSiteData(pluginTag, "foo.com", FLAG_CLEAR_CACHE, 20, {callback: function() { test4(); }}); + } + function test4() { ok(stored(["foo.com","bar.com","baz.com","quz.com"]), "Data stored for sites"); // Clear all data 7 seconds or older. - pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_ALL, 7); + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_ALL, 7, {callback: function() { test5(); }}); + } + function test5() { ok(stored(["bar.com","baz.com","quz.com"]), "Data stored for sites"); ok(!stored(["foo.com"]), "Data cleared for foo.com"); ok(!stored(["qux.com"]), "Data cleared for qux.com"); // Clear all cache data. - pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, 20); + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, 20, {callback: function() { test6(); }}); + } + function test6() { ok(stored(["bar.com","baz.com"]), "Data stored for sites"); ok(!stored(["quz.com"]), "Data cleared for quz.com"); // Clear all data for bar.com. - pluginHost.clearSiteData(pluginTag, "bar.com", FLAG_CLEAR_ALL, 20); + pluginHost.clearSiteData(pluginTag, "bar.com", FLAG_CLEAR_ALL, 20, {callback: function(rv) { test7(rv); }}); + } + function test7(rv) { ok(stored(["baz.com"]), "Data stored for baz.com"); ok(!stored(["bar.com"]), "Data cleared for bar.com"); // Disable clearing by age. p.setSitesWithDataCapabilities(false); - // Attempt to clear data by age. - checkThrows(function() { - pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_ALL, 20); - }, Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED); - - checkThrows(function() { - pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, 20); - }, Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED); - - checkThrows(function() { - pluginHost.clearSiteData(pluginTag, "baz.com", FLAG_CLEAR_ALL, 20); - }, Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED); - - checkThrows(function() { - pluginHost.clearSiteData(pluginTag, "baz.com", FLAG_CLEAR_CACHE, 20); - }, Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED); - + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_ALL, 20, {callback: function(rv) { + is(rv, Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED); + test8(rv); + }}); + } + function test8(rv) { + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, 20, {callback: function(rv) { + is(rv, Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED); + test9(rv); + }}); + } + function test9(rv) { + pluginHost.clearSiteData(pluginTag, "baz.com", FLAG_CLEAR_ALL, 20, {callback: function(rv) { + is(rv, Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED); + test10(rv); + }}); + } + function test10(rv) { + pluginHost.clearSiteData(pluginTag, "baz.com", FLAG_CLEAR_CACHE, 20, {callback: function(rv) { + is(rv, Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED); + test11(); + }}); + } + function test11() { // Clear cache for baz.com and globally for all ages. - pluginHost.clearSiteData(pluginTag, "baz.com", FLAG_CLEAR_CACHE, -1); - pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, -1); - + pluginHost.clearSiteData(pluginTag, "baz.com", FLAG_CLEAR_CACHE, -1, {callback: function(rv) { test12()}}); + } + function test12() { + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, -1, {callback: function(rv) { test13()}}); + } + function test13() { // Check that all of the above were no-ops. ok(stored(["baz.com"]), "Data stored for baz.com"); // Clear everything for baz.com. - pluginHost.clearSiteData(pluginTag, "baz.com", FLAG_CLEAR_ALL, -1); + pluginHost.clearSiteData(pluginTag, "baz.com", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test14()}}); + } + function test14() { ok(!stored(["baz.com"]), "Data cleared for baz.com"); ok(!stored(null), "All data cleared"); @@ -150,26 +171,33 @@ "[192.168.1.1]:0:0," + "localhost:0:0" ); - ok(stored(["foo.com","nonexistent.foo.com","bar.com","192.168.1.1","localhost"]), - "Data stored for sites"); + "Data stored for sites"); // Clear data for "foo.com" and its subdomains. - pluginHost.clearSiteData(pluginTag, "foo.com", FLAG_CLEAR_ALL, -1); + pluginHost.clearSiteData(pluginTag, "foo.com", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test15()}}); + } + function test15() { ok(stored(["bar.com","192.168.1.1","localhost"]), "Data stored for sites"); ok(!stored(["foo.com"]), "Data cleared for foo.com"); ok(!stored(["bar.foo.com"]), "Data cleared for subdomains of foo.com"); // Clear data for "bar.com" using a subdomain. - pluginHost.clearSiteData(pluginTag, "foo.bar.com", FLAG_CLEAR_ALL, -1); + pluginHost.clearSiteData(pluginTag, "foo.bar.com", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test16()}}); + } + function test16() { ok(!stored(["bar.com"]), "Data cleared for bar.com"); // Clear data for "192.168.1.1". - pluginHost.clearSiteData(pluginTag, "192.168.1.1", FLAG_CLEAR_ALL, -1); + pluginHost.clearSiteData(pluginTag, "192.168.1.1", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test17()}}); + } + function test17() { ok(!stored(["192.168.1.1"]), "Data cleared for 192.168.1.1"); // Clear data for "localhost". - pluginHost.clearSiteData(pluginTag, "localhost", FLAG_CLEAR_ALL, -1); + pluginHost.clearSiteData(pluginTag, "localhost", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test18()}}); + } + function test18() { ok(!stored(null), "All data cleared"); // Set data to test international domains. @@ -178,18 +206,21 @@ "b\u00FCcher.uk:0:0," + "xn--bcher-kva.NZ:0:0" ); - - // Check that both the ACE and UTF-8 representations register. + // Check that both the ACE and UTF-8 representations register. ok(stored(["b\u00FCcher.es","xn--bcher-kva.es","b\u00FCcher.uk","xn--bcher-kva.uk"]), - "Data stored for sites"); + "Data stored for sites"); // Clear data for the UTF-8 version. - pluginHost.clearSiteData(pluginTag, "b\u00FCcher.es", FLAG_CLEAR_ALL, -1); + pluginHost.clearSiteData(pluginTag, "b\u00FCcher.es", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test19()}}); + } + function test19() { ok(!stored(["b\u00FCcher.es"]), "Data cleared for UTF-8 representation"); ok(!stored(["xn--bcher-kva.es"]), "Data cleared for ACE representation"); // Clear data for the ACE version. - pluginHost.clearSiteData(pluginTag, "xn--bcher-kva.uk", FLAG_CLEAR_ALL, -1); + pluginHost.clearSiteData(pluginTag, "xn--bcher-kva.uk", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test20()}}); + } + function test20() { ok(!stored(["b\u00FCcher.uk"]), "Data cleared for UTF-8 representation"); ok(!stored(["xn--bcher-kva.uk"]), "Data cleared for ACE representation"); @@ -197,11 +228,14 @@ // UTF-8. We do happen to normalize the result anyway, so while that's not // strictly required, we test it here. ok(stored(["b\u00FCcher.nz","xn--bcher-kva.nz"]), - "Data stored for sites"); - pluginHost.clearSiteData(pluginTag, "b\u00FCcher.nz", FLAG_CLEAR_ALL, -1); + "Data stored for sites"); + pluginHost.clearSiteData(pluginTag, "b\u00FCcher.nz", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test21()}}); + } + function test21() { ok(!stored(["b\u00FCcher.nz"]), "Data cleared for UTF-8 representation"); ok(!stored(["xn--bcher-kva.nz"]), "Data cleared for ACE representation"); ok(!stored(null), "All data cleared"); + SimpleTest.finish(); } diff --git a/toolkit/forgetaboutsite/ForgetAboutSite.jsm b/toolkit/forgetaboutsite/ForgetAboutSite.jsm index c4dde69e281..9e9e65298d9 100644 --- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm +++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm @@ -90,12 +90,20 @@ this.ForgetAboutSite = { const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL; let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface); let tags = ph.getPluginTags(); + let promises = []; for (let i = 0; i < tags.length; i++) { - try { - ph.clearSiteData(tags[i], aDomain, FLAG_CLEAR_ALL, -1); - } catch (e) { - // Ignore errors from the plugin - } + let promise = new Promise(resolve => { + let tag = tags[i]; + try { + ph.clearSiteData(tags[i], aDomain, FLAG_CLEAR_ALL, -1, function(rv) { + resolve(); + }); + } catch (e) { + // Ignore errors from the plugin, but resolve the promise + resolve(); + } + }); + promises.push(promise); } // Downloads @@ -168,5 +176,6 @@ this.ForgetAboutSite = { let np = Cc["@mozilla.org/network/predictor;1"]. getService(Ci.nsINetworkPredictor); np.reset(); + return Promise.all(promises); } }; diff --git a/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js b/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js index 14dd189a695..6ccb8a208eb 100644 --- a/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js +++ b/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js @@ -82,27 +82,36 @@ function do_test() "Data stored for sites"); // Clear data for "foo.com" and its subdomains. - ForgetAboutSite.removeDataFromDomain("foo.com"); + ForgetAboutSite.removeDataFromDomain("foo.com").then(test1); + }); + function test1() { + dump("test1\n"); ok(stored(["bar.com","192.168.1.1","localhost"]), "Data stored for sites"); ok(!stored(["foo.com"]), "Data cleared for foo.com"); ok(!stored(["bar.foo.com"]), "Data cleared for subdomains of foo.com"); // Clear data for "bar.com" using a subdomain. - ForgetAboutSite.removeDataFromDomain("foo.bar.com"); + ForgetAboutSite.removeDataFromDomain("foo.bar.com").then(test2); + } + function test2() { ok(!stored(["bar.com"]), "Data cleared for bar.com"); // Clear data for "192.168.1.1". - ForgetAboutSite.removeDataFromDomain("192.168.1.1"); + ForgetAboutSite.removeDataFromDomain("192.168.1.1").then(test3); + } + function test3() { ok(!stored(["192.168.1.1"]), "Data cleared for 192.168.1.1"); // Clear data for "localhost". - ForgetAboutSite.removeDataFromDomain("localhost"); + ForgetAboutSite.removeDataFromDomain("localhost").then(test4); + } + function test4() { ok(!stored(null), "All data cleared"); gBrowser.removeCurrentTab(); executeSoon(finish); - }); + } }, true); content.location = testURL; }