Bug 508167 - NPAPI additions for clearing recent history (e.g. for "flash cookies"). r=josh, a=beltzner
authorDan Witte <dwitte@mozilla.com>
Tue, 08 Feb 2011 14:16:07 -0800
changeset 62174 d0ea866fef6e2b9b61e7dede3d5dd7d0e4a797e3
parent 62173 05752611dd994e978cf2ed81e08ce269a4a83e73
child 62175 2ff5b862446372b8eb2511427c148414ceef1d12
push id1
push userroot
push dateTue, 10 Dec 2013 15:46:25 +0000
reviewersjosh, beltzner
bugs508167
milestone2.0b12pre
Bug 508167 - NPAPI additions for clearing recent history (e.g. for "flash cookies"). r=josh, a=beltzner
dom/plugins/PPluginModule.ipdl
dom/plugins/PluginLibrary.h
dom/plugins/PluginModuleChild.cpp
dom/plugins/PluginModuleChild.h
dom/plugins/PluginModuleParent.cpp
dom/plugins/PluginModuleParent.h
js/src/xpconnect/src/xpc.msg
js/src/xpconnect/src/xpcexception.cpp
modules/plugin/base/public/nsIPluginHost.idl
modules/plugin/base/public/nsPluginError.h
modules/plugin/base/src/PluginPRLibrary.cpp
modules/plugin/base/src/PluginPRLibrary.h
modules/plugin/base/src/nsNPAPIPlugin.cpp
modules/plugin/base/src/nsNPAPIPlugin.h
modules/plugin/base/src/nsPluginHost.cpp
modules/plugin/base/src/nsPluginHost.h
modules/plugin/base/src/nsPluginTags.cpp
modules/plugin/base/src/nsPluginTags.h
modules/plugin/test/mochitest/Makefile.in
modules/plugin/test/mochitest/test_clear_site_data.html
modules/plugin/test/testplugin/nptest.cpp
--- a/dom/plugins/PPluginModule.ipdl
+++ b/dom/plugins/PPluginModule.ipdl
@@ -81,18 +81,25 @@ child:
                       uint16_t aMode,
                       nsCString[] aNames,
                       nsCString[] aValues)
     returns (NPError rv);
 
   rpc NP_Shutdown()
     returns (NPError rv);
 
-  rpc URLRedirectNotifySupported()
-    returns (bool aBoolVal);
+  rpc OptionalFunctionsSupported()
+    returns (bool aURLRedirectNotify, bool aClearSiteData,
+             bool aGetSitesWithData);
+
+  rpc NPP_ClearSiteData(nsCString site, uint64_t flags, uint64_t maxAge)
+    returns (NPError rv);
+
+  rpc NPP_GetSitesWithData()
+    returns (nsCString[] sites);
 
 parent:
   /**
    * This message is only used on X11 platforms.
    *
    * Send a dup of the plugin process's X socket to the parent
    * process.  In theory, this scheme keeps the plugin's X resources
    * around until after both the plugin process shuts down *and* the
--- a/dom/plugins/PluginLibrary.h
+++ b/dom/plugins/PluginLibrary.h
@@ -38,19 +38,22 @@
 
 #ifndef mozilla_PluginLibrary_h
 #define mozilla_PluginLibrary_h 1
 
 #include "prlink.h"
 #include "npapi.h"
 #include "npfunctions.h"
 #include "nscore.h"
+#include "nsTArray.h"
+#include "nsPluginError.h"
 
 class nsNPAPIPlugin;
 class gfxASurface;
+class nsCString;
 
 namespace mozilla {
 
 class PluginLibrary
 {
 public:
   virtual ~PluginLibrary() { }
 
@@ -74,16 +77,20 @@ public:
 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_OS2)
   virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) = 0;
 #endif
   virtual nsresult NPP_New(NPMIMEType pluginType, NPP instance,
                            uint16_t mode, int16_t argc, char* argn[],
                            char* argv[], NPSavedData* saved,
                            NPError* error) = 0;
 
+  virtual nsresult NPP_ClearSiteData(const char* site, uint64_t flags,
+                                     uint64_t maxAge) = 0;
+  virtual nsresult NPP_GetSitesWithData(InfallibleTArray<nsCString>& aResult) = 0;
+
   virtual nsresult AsyncSetWindow(NPP instance, NPWindow* window) = 0;
   virtual nsresult GetSurface(NPP instance, gfxASurface** aSurface) = 0;
   virtual bool UseAsyncPainting() = 0;
 };
 
 
 } // namespace mozilla
 
--- a/dom/plugins/PluginModuleChild.cpp
+++ b/dom/plugins/PluginModuleChild.cpp
@@ -575,19 +575,52 @@ PluginModuleChild::AnswerNP_Shutdown(NPE
 #ifdef OS_WIN
     ResetEventHooks();
 #endif
 
     return true;
 }
 
 bool
-PluginModuleChild::AnswerURLRedirectNotifySupported(bool *aBoolVal)
+PluginModuleChild::AnswerOptionalFunctionsSupported(bool *aURLRedirectNotify,
+                                                    bool *aClearSiteData,
+                                                    bool *aGetSitesWithData)
+{
+    *aURLRedirectNotify = !!mFunctions.urlredirectnotify;
+    *aClearSiteData = !!mFunctions.clearsitedata;
+    *aGetSitesWithData = !!mFunctions.getsiteswithdata;
+    return true;
+}
+
+bool
+PluginModuleChild::AnswerNPP_ClearSiteData(const nsCString& aSite,
+                                           const uint64_t& aFlags,
+                                           const uint64_t& aMaxAge,
+                                           NPError* aResult)
 {
-    *aBoolVal = !!mFunctions.urlredirectnotify;
+    *aResult =
+        mFunctions.clearsitedata(NullableStringGet(aSite), aFlags, aMaxAge);
+    return true;
+}
+
+bool
+PluginModuleChild::AnswerNPP_GetSitesWithData(InfallibleTArray<nsCString>* aResult)
+{
+    char** result = mFunctions.getsiteswithdata();
+    if (!result)
+        return true;
+
+    char** iterator = result;
+    while (*iterator) {
+        aResult->AppendElement(*iterator);
+        NS_Free(*iterator);
+        ++iterator;
+    }
+    NS_Free(result);
+
     return true;
 }
 
 void
 PluginModuleChild::QuickExit()
 {
     NS_WARNING("plugin process _exit()ing");
     _exit(0);
--- a/dom/plugins/PluginModuleChild.h
+++ b/dom/plugins/PluginModuleChild.h
@@ -134,17 +134,28 @@ protected:
                                      const uint16_t& aMode,
                                      const InfallibleTArray<nsCString>& aNames,
                                      const InfallibleTArray<nsCString>& aValues,
                                      NPError* rv);
     virtual bool
     AnswerNP_Shutdown(NPError *rv);
 
     virtual bool
-    AnswerURLRedirectNotifySupported(bool *aBoolVal);
+    AnswerOptionalFunctionsSupported(bool *aURLRedirectNotify,
+                                     bool *aClearSiteData,
+                                     bool *aGetSitesWithData);
+
+    virtual bool
+    AnswerNPP_ClearSiteData(const nsCString& aSite,
+                            const uint64_t& aFlags,
+                            const uint64_t& aMaxAge,
+                            NPError* aResult);
+
+    virtual bool
+    AnswerNPP_GetSitesWithData(InfallibleTArray<nsCString>* aResult);
 
     virtual void
     ActorDestroy(ActorDestroyReason why);
 
     NS_NORETURN void QuickExit();
 
 public:
     PluginModuleChild();
--- a/dom/plugins/PluginModuleParent.cpp
+++ b/dom/plugins/PluginModuleParent.cpp
@@ -104,16 +104,18 @@ PluginModuleParent::LoadModule(const cha
     return parent.forget();
 }
 
 
 PluginModuleParent::PluginModuleParent(const char* aFilePath)
     : mSubprocess(new PluginProcessParent(aFilePath))
     , mPluginThread(0)
     , mShutdown(false)
+    , mClearSiteDataSupported(false)
+    , mGetSitesWithDataSupported(false)
     , mNPNIface(NULL)
     , mPlugin(NULL)
     , mProcessStartTime(time(NULL))
     , mTaskFactory(this)
 {
     NS_ASSERTION(mSubprocess, "Out of memory!");
 
     if (!mIdentifiers.Init()) {
@@ -378,36 +380,43 @@ PluginModuleParent::DeallocPPluginInstan
 }
 
 void
 PluginModuleParent::SetPluginFuncs(NPPluginFuncs* aFuncs)
 {
     aFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
     aFuncs->javaClass = nsnull;
 
-    aFuncs->newp = nsnull; // Gecko should always call this through a PluginLibrary object
+    // Gecko should always call these functions through a PluginLibrary object.
+    aFuncs->newp = NULL;
+    aFuncs->clearsitedata = NULL;
+    aFuncs->getsiteswithdata = NULL;
+
     aFuncs->destroy = NPP_Destroy;
     aFuncs->setwindow = NPP_SetWindow;
     aFuncs->newstream = NPP_NewStream;
     aFuncs->destroystream = NPP_DestroyStream;
     aFuncs->asfile = NPP_StreamAsFile;
     aFuncs->writeready = NPP_WriteReady;
     aFuncs->write = NPP_Write;
     aFuncs->print = NPP_Print;
     aFuncs->event = NPP_HandleEvent;
     aFuncs->urlnotify = NPP_URLNotify;
     aFuncs->getvalue = NPP_GetValue;
     aFuncs->setvalue = NPP_SetValue;
     aFuncs->gotfocus = NULL;
     aFuncs->lostfocus = NULL;
     aFuncs->urlredirectnotify = NULL;
 
-    // Provide 'NPP_URLRedirectNotify' if it is supported by the plugin.
+    // Provide 'NPP_URLRedirectNotify', 'NPP_ClearSiteData', and
+    // 'NPP_GetSitesWithData' functionality if it is supported by the plugin.
     bool urlRedirectSupported = false;
-    CallURLRedirectNotifySupported(&urlRedirectSupported);
+    unused << CallOptionalFunctionsSupported(&urlRedirectSupported,
+                                             &mClearSiteDataSupported,
+                                             &mGetSitesWithDataSupported);
     if (urlRedirectSupported) {
       aFuncs->urlredirectnotify = NPP_URLRedirectNotify;
     }
 }
 
 NPError
 PluginModuleParent::NPP_Destroy(NPP instance,
                                 NPSavedData** /*saved*/)
@@ -830,16 +839,51 @@ PluginModuleParent::NPP_New(NPMIMEType p
     if (*error != NPERR_NO_ERROR) {
         NPP_Destroy(instance, 0);
         return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
 }
 
+nsresult
+PluginModuleParent::NPP_ClearSiteData(const char* site, uint64_t flags,
+                                      uint64_t maxAge)
+{
+    if (!mClearSiteDataSupported)
+        return NS_ERROR_NOT_AVAILABLE;
+
+    NPError result;
+    if (!CallNPP_ClearSiteData(NullableString(site), flags, maxAge, &result))
+        return NS_ERROR_FAILURE;
+
+    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:
+        return NS_ERROR_FAILURE;
+    }
+}
+
+nsresult
+PluginModuleParent::NPP_GetSitesWithData(InfallibleTArray<nsCString>& result)
+{
+    if (!mGetSitesWithDataSupported)
+        return NS_ERROR_NOT_AVAILABLE;
+
+    if (!CallNPP_GetSitesWithData(&result))
+        return NS_ERROR_FAILURE;
+
+    return NS_OK;
+}
+
 bool
 PluginModuleParent::AnswerNPN_GetValue_WithBoolReturn(const NPNVariable& aVariable,
                                                       NPError* aError,
                                                       bool* aBoolVal)
 {
     NPBool boolVal = false;
     *aError = mozilla::plugins::parent::_getvalue(nsnull, aVariable, &boolVal);
     *aBoolVal = boolVal ? true : false;
--- a/dom/plugins/PluginModuleParent.h
+++ b/dom/plugins/PluginModuleParent.h
@@ -244,28 +244,34 @@ private:
                                  void *aValue, NPError* error);
 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_OS2)
     virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error);
 #endif
     virtual nsresult NPP_New(NPMIMEType pluginType, NPP instance,
                              uint16_t mode, int16_t argc, char* argn[],
                              char* argv[], NPSavedData* saved,
                              NPError* error);
+    virtual nsresult NPP_ClearSiteData(const char* site, uint64_t flags,
+                                       uint64_t maxAge);
+    virtual nsresult NPP_GetSitesWithData(InfallibleTArray<nsCString>& result);
+
 private:
     void WritePluginExtraDataForMinidump(const nsAString& id);
     void WriteExtraDataForHang();
     void CleanupFromTimeout();
     static int TimeoutChanged(const char* aPref, void* aModule);
     void NotifyPluginCrashed();
 
     nsCString mCrashNotes;
     PluginProcessParent* mSubprocess;
     // the plugin thread in mSubprocess
     NativeThreadId mPluginThread;
     bool mShutdown;
+    bool mClearSiteDataSupported;
+    bool mGetSitesWithDataSupported;
     const NPNetscapeFuncs* mNPNIface;
     nsDataHashtable<nsVoidPtrHashKey, PluginIdentifierParent*> mIdentifiers;
     nsNPAPIPlugin* mPlugin;
     time_t mProcessStartTime;
     ScopedRunnableMethodFactory<PluginModuleParent> mTaskFactory;
     nsString mPluginDumpID;
     nsString mBrowserDumpID;
     nsString mHangID;
--- a/js/src/xpconnect/src/xpc.msg
+++ b/js/src/xpconnect/src/xpc.msg
@@ -217,8 +217,11 @@ XPC_MSG_DEF(NS_ERROR_INSUFFICIENT_DOMAIN
 XPC_MSG_DEF(NS_ERROR_HOST_IS_IP_ADDRESS             , "The host string is an IP address")
 XPC_MSG_DEF(NS_ERROR_NOT_SAME_THREAD                , "Can't access a wrapped JS object from a different thread")
 
 /* storage related codes (from mozStorage.h) */
 XPC_MSG_DEF(NS_ERROR_STORAGE_BUSY                   , "SQLite database connection is busy")
 XPC_MSG_DEF(NS_ERROR_STORAGE_IOERR                  , "SQLite encountered an IO error")
 XPC_MSG_DEF(NS_ERROR_STORAGE_CONSTRAINT             , "SQLite database operation failed because a constraint was violated")
 
+/* plugin related codes (from nsPluginError.h) */
+XPC_MSG_DEF(NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED, "Clearing site data by time range not supported by plugin")
+
--- a/js/src/xpconnect/src/xpcexception.cpp
+++ b/js/src/xpconnect/src/xpcexception.cpp
@@ -39,16 +39,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 /* An implementaion of nsIException. */
 
 #include "xpcprivate.h"
 #include "nsNetError.h"
 #include "mozStorage.h"
+#include "nsPluginError.h"
 
 /***************************************************************************/
 /* Quick and dirty mapping of well known result codes to strings. We only
 *  call this when building an exception object, so iterating the short array
 *  is not too bad.
 *
 *  It sure would be nice to have exceptions declared in idl and available
 *  in some more global way at runtime.
--- a/modules/plugin/base/public/nsIPluginHost.idl
+++ b/modules/plugin/base/public/nsIPluginHost.idl
@@ -284,8 +284,63 @@ interface nsIPluginHost : nsISupports
    */
   [noscript] nsIPluginTag getPluginTagForInstance(in nsIPluginInstance aInstance);
   
 %{C++
   virtual void AddIdleTimeTarget(nsIPluginInstanceOwner* objectFrame, PRBool isVisible) = 0;
   virtual void RemoveIdleTimeTarget(nsIPluginInstanceOwner* objectFrame) = 0;
 %}
 };
+
+/*
+ * Methods for clearing plugin private data. These should be moved onto
+ * nsIPluginHost proper post-Gecko 2.0.
+ */
+[scriptable, uuid(0b0a2fb8-dc2b-4df2-b721-4b7a4008df6c)]
+interface nsIPluginHost_MOZILLA_2_0_BRANCH : nsISupports
+{
+  /*
+   * Flags for use with clearSiteData.
+   *
+   * FLAG_CLEAR_ALL: clear all data associated with a site.
+   * FLAG_CLEAR_CACHE: clear cached data that can be retrieved again without
+   *                   loss of functionality. To be used out of concern for
+   *                   space and not necessarily privacy.
+   */
+  const PRUint32 FLAG_CLEAR_ALL = 0;
+  const PRUint32 FLAG_CLEAR_CACHE = 1;
+
+  /*
+   * Clear site data for a given plugin.
+   *
+   * @param plugin: the plugin to clear data for, such as one returned by
+   *                nsIPluginHost.getPluginTags.
+   * @param domain: the domain to clear data for. If this argument is null,
+   *                clear data for all domains. Otherwise, it must be a domain
+   *                only (not a complete URI or IRI). The base domain for the
+   *                given site will be determined; any data for the base domain
+   *                or its subdomains will be cleared.
+   * @param flags: a flag value defined above.
+   * @param maxAge: the maximum age in seconds of data to clear, inclusive. If
+   *                maxAge is 0, no data is cleared; if it is -1, all data is
+   *                cleared.
+   *
+   * @throws NS_ERROR_INVALID_ARG if the domain argument is malformed.
+   * @throws NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED if maxAge is a value other
+   *         than -1 and the plugin does not support clearing by timerange in
+   *         general or for that particular site and/or flag combination.
+   */
+  void clearSiteData(in nsIPluginTag plugin, in AUTF8String domain,
+                     in PRUint64 flags, in PRInt64 maxAge);
+
+  /*
+   * Determine if a plugin has stored data for a given site.
+   *
+   * @param plugin: the plugin to query, such as one returned by
+   *                nsIPluginHost.getPluginTags.
+   * @param domain: the domain to test. If this argument is null, test if data
+   *                is stored for any site. The base domain for the given domain
+   *                will be determined; if any data for the base domain or its
+   *                subdomains is found, return true.
+   */
+  boolean siteHasData(in nsIPluginTag plugin, in AUTF8String domain);
+};
+
--- a/modules/plugin/base/public/nsPluginError.h
+++ b/modules/plugin/base/public/nsPluginError.h
@@ -40,10 +40,11 @@
 #ifndef nsPluginError_h__
 #define nsPluginError_h__
 
 #include "nsError.h"
 
 #define NS_ERROR_PLUGINS_PLUGINSNOTCHANGED    NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_PLUGINS,1000)
 #define NS_ERROR_PLUGIN_DISABLED    NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_PLUGINS,1001)
 #define NS_ERROR_PLUGIN_BLOCKLISTED    NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_PLUGINS,1002)
+#define NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_PLUGINS,1003)
 
 #endif   // nsPluginError_h__
--- a/modules/plugin/base/src/PluginPRLibrary.cpp
+++ b/modules/plugin/base/src/PluginPRLibrary.cpp
@@ -64,18 +64,21 @@ PluginPRLibrary::NP_Initialize(NPNetscap
   } else {
     NP_InitializeFunc pfNP_Initialize = (NP_InitializeFunc)
       PR_FindFunctionSymbol(mLibrary, "NP_Initialize");
     if (!pfNP_Initialize)
       return NS_ERROR_FAILURE;
     *error = pfNP_Initialize(bFuncs, pFuncs);
   }
 
-  // save NPP_New
+
+  // Save pointers to functions that get called through PluginLibrary itself.
   mNPP_New = pFuncs->newp;
+  mNPP_ClearSiteData = pFuncs->clearsitedata;
+  mNPP_GetSitesWithData = pFuncs->getsiteswithdata;
   return NS_OK;
 }
 #else
 nsresult
 PluginPRLibrary::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error)
 {
   CALLING_CONVENTION_HACK
 
@@ -163,18 +166,20 @@ PluginPRLibrary::NP_GetEntryPoints(NPPlu
   } else {
     NP_GetEntryPointsFunc pfNP_GetEntryPoints = (NP_GetEntryPointsFunc)
       PR_FindFunctionSymbol(mLibrary, "NP_GetEntryPoints");
     if (!pfNP_GetEntryPoints)
       return NS_ERROR_FAILURE;
     *error = pfNP_GetEntryPoints(pFuncs);
   }
 
-  // save NPP_New
+  // Save pointers to functions that get called through PluginLibrary itself.
   mNPP_New = pFuncs->newp;
+  mNPP_ClearSiteData = pFuncs->clearsitedata;
+  mNPP_GetSitesWithData = pFuncs->getsiteswithdata;
   return NS_OK;
 }
 #endif
 
 nsresult
 PluginPRLibrary::NPP_New(NPMIMEType pluginType, NPP instance,
 			 uint16_t mode, int16_t argc, char* argn[],
 			 char* argv[], NPSavedData* saved,
@@ -182,16 +187,63 @@ PluginPRLibrary::NPP_New(NPMIMEType plug
 {
   if (!mNPP_New)
     return NS_ERROR_FAILURE;
   *error = mNPP_New(pluginType, instance, mode, argc, argn, argv, saved);
   return NS_OK;
 }
 
 nsresult
+PluginPRLibrary::NPP_ClearSiteData(const char* site, uint64_t flags,
+                                   uint64_t maxAge)
+{
+  if (!mNPP_ClearSiteData) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NPError result = mNPP_ClearSiteData(site, flags, maxAge);
+
+  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:
+    return NS_ERROR_FAILURE;
+  }
+}
+
+nsresult
+PluginPRLibrary::NPP_GetSitesWithData(InfallibleTArray<nsCString>& result)
+{
+  if (!mNPP_GetSitesWithData) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  result.Clear();
+
+  char** sites = mNPP_GetSitesWithData();
+  if (!sites) {
+    return NS_OK;
+  }
+
+  char** iterator = sites;
+  while (*iterator) {
+    result.AppendElement(*iterator);
+    NS_Free(*iterator);
+    ++iterator;
+  }
+  NS_Free(sites);
+
+  return NS_OK;
+}
+
+nsresult
 PluginPRLibrary::AsyncSetWindow(NPP instance, NPWindow* window)
 {
   nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata;
   NS_ENSURE_TRUE(inst, NS_ERROR_NULL_POINTER);
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 nsresult
--- a/modules/plugin/base/src/PluginPRLibrary.h
+++ b/modules/plugin/base/src/PluginPRLibrary.h
@@ -58,16 +58,18 @@ public:
         mNP_GetMIMEDescription(nsnull),
 #if defined(XP_UNIX) && !defined(XP_MACOSX)
         mNP_GetValue(nsnull),
 #endif
 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_OS2)
         mNP_GetEntryPoints(nsnull),
 #endif
         mNPP_New(nsnull),
+        mNPP_ClearSiteData(nsnull),
+        mNPP_GetSitesWithData(nsnull),
         mLibrary(aLibrary)
     {
         NS_ASSERTION(mLibrary, "need non-null lib");
         // addref here??
     }
 
     virtual ~PluginPRLibrary()
     {
@@ -128,30 +130,36 @@ public:
     virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error);
 #endif
 
     virtual nsresult NPP_New(NPMIMEType pluginType, NPP instance,
                              uint16_t mode, int16_t argc, char* argn[],
                              char* argv[], NPSavedData* saved,
                              NPError* error);
 
+    virtual nsresult NPP_ClearSiteData(const char* site, uint64_t flags,
+                                       uint64_t maxAge);
+    virtual nsresult NPP_GetSitesWithData(InfallibleTArray<nsCString>& result);
+
     virtual nsresult AsyncSetWindow(NPP instance, NPWindow* window);
     virtual nsresult GetSurface(NPP instance, gfxASurface** aSurface);
     NS_OVERRIDE virtual bool UseAsyncPainting() { return false; }
 
 private:
     NP_InitializeFunc mNP_Initialize;
     NP_ShutdownFunc mNP_Shutdown;
     NP_GetMIMEDescriptionFunc mNP_GetMIMEDescription;
 #if defined(XP_UNIX) && !defined(XP_MACOSX)
     NP_GetValueFunc mNP_GetValue;
 #endif
 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_OS2)
     NP_GetEntryPointsFunc mNP_GetEntryPoints;
 #endif
     NPP_NewProcPtr mNPP_New;
+    NPP_ClearSiteDataPtr mNPP_ClearSiteData;
+    NPP_GetSitesWithDataPtr mNPP_GetSitesWithData;
     PRLibrary* mLibrary;
 };
 
 
 } // namespace mozilla
 
 #endif  // ifndef PluginPRLibrary_h
--- a/modules/plugin/base/src/nsNPAPIPlugin.cpp
+++ b/modules/plugin/base/src/nsNPAPIPlugin.cpp
@@ -469,17 +469,17 @@ GetNewPluginLibrary(nsPluginTag *aPlugin
     return PluginModuleParent::LoadModule(aPluginTag->mFullPath.get());
   }
 #endif
   return new PluginPRLibrary(aPluginTag->mFullPath.get(), aPluginTag->mLibrary);
 }
 
 // Creates an nsNPAPIPlugin object. One nsNPAPIPlugin object exists per plugin (not instance).
 nsresult
-nsNPAPIPlugin::CreatePlugin(nsPluginTag *aPluginTag, nsIPlugin** aResult)
+nsNPAPIPlugin::CreatePlugin(nsPluginTag *aPluginTag, nsNPAPIPlugin** aResult)
 {
   *aResult = nsnull;
 
   if (!aPluginTag) {
     return NS_ERROR_FAILURE;
   }
 
   CheckClassInitialized();
--- a/modules/plugin/base/src/nsNPAPIPlugin.h
+++ b/modules/plugin/base/src/nsNPAPIPlugin.h
@@ -81,17 +81,17 @@ public:
   nsNPAPIPlugin();
   virtual ~nsNPAPIPlugin();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPLUGIN
 
   // Constructs and initializes an nsNPAPIPlugin object. A NULL file path
   // will prevent this from calling NP_Initialize.
-  static nsresult CreatePlugin(nsPluginTag *aPluginTag, nsIPlugin** aResult);
+  static nsresult CreatePlugin(nsPluginTag *aPluginTag, nsNPAPIPlugin** aResult);
 
   PluginLibrary* GetLibrary();
   // PluginFuncs() can't fail but results are only valid if GetLibrary() succeeds
   NPPluginFuncs* PluginFuncs();
 
 #if defined(XP_MACOSX) && !defined(__LP64__)
   void SetPluginRefNum(short aRefNum);
 #endif
--- a/modules/plugin/base/src/nsPluginHost.cpp
+++ b/modules/plugin/base/src/nsPluginHost.cpp
@@ -156,16 +156,17 @@
 
 #if defined(XP_WIN)
 #include "nsIWindowMediator.h"
 #include "nsIBaseWindow.h"
 #include "windows.h"
 #include "winbase.h"
 #endif
 
+using namespace mozilla;
 using mozilla::TimeStamp;
 
 // Null out a strong ref to a linked list iteratively to avoid
 // exhausting the stack (bug 486349).
 #define NS_ITERATIVE_UNREF_LIST(type_, list_, mNext_)                \
   {                                                                  \
     while (list_) {                                                  \
       type_ temp = list_->mNext_;                                    \
@@ -425,18 +426,19 @@ nsPluginHost::nsPluginHost()
 nsPluginHost::~nsPluginHost()
 {
   PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("nsPluginHost::dtor\n"));
 
   Destroy();
   sInst = nsnull;
 }
 
-NS_IMPL_ISUPPORTS4(nsPluginHost,
+NS_IMPL_ISUPPORTS5(nsPluginHost,
                    nsIPluginHost,
+                   nsIPluginHost_MOZILLA_2_0_BRANCH,
                    nsIObserver,
                    nsITimerCallback,
                    nsISupportsWeakReference)
 
 nsPluginHost*
 nsPluginHost::GetInst()
 {
   if (!sInst) {
@@ -1726,17 +1728,17 @@ static nsresult ConvertToNative(nsIUnico
   rv = aEncoder->Convert(utf16.get(), &len,
                          aNativeString.BeginWriting(), &outLen);
   NS_ENSURE_SUCCESS(rv, rv);
   aNativeString.SetLength(outLen);
   return NS_OK;
 }
 
 static nsresult CreateNPAPIPlugin(nsPluginTag *aPluginTag,
-                                  nsIPlugin **aOutNPAPIPlugin)
+                                  nsNPAPIPlugin **aOutNPAPIPlugin)
 {
   // If this is an in-process plugin we'll need to load it here if we haven't already.
 #ifdef MOZ_IPC
   if (!nsNPAPIPlugin::RunPluginOOP(aPluginTag)) {
 #else
   if (!aPluginTag->mLibrary) {
 #endif
     if (aPluginTag->mFullPath.IsEmpty())
@@ -1783,17 +1785,17 @@ static nsresult CreateNPAPIPlugin(nsPlug
   nsPluginFile pluginFile(pluginPath);
   short pluginRefNum = pluginFile.OpenPluginResource();
 #endif
 
   rv = nsNPAPIPlugin::CreatePlugin(aPluginTag, aOutNPAPIPlugin);
 
 #if defined(XP_MACOSX) && !defined(__LP64__)
   if (NS_SUCCEEDED(rv))
-    static_cast<nsNPAPIPlugin*>(*aOutNPAPIPlugin)->SetPluginRefNum(pluginRefNum);
+    (*aOutNPAPIPlugin)->SetPluginRefNum(pluginRefNum);
   else if (pluginRefNum > 0)
     ::CloseResFile(pluginRefNum);
   ::UseResFile(appRefNum);
 #endif
 
   return rv;
 }
 
@@ -1816,17 +1818,17 @@ NS_IMETHODIMP nsPluginHost::GetPlugin(co
     aMimeType, pluginTag->mFileName.get()));
 
 #ifdef NS_DEBUG
     if (aMimeType && !pluginTag->mFileName.IsEmpty())
       printf("For %s found plugin %s\n", aMimeType, pluginTag->mFileName.get());
 #endif
 
     // Create a plugin object if necessary
-    nsCOMPtr<nsIPlugin> plugin = pluginTag->mEntryPoint;
+    nsRefPtr<nsNPAPIPlugin> plugin = pluginTag->mEntryPoint;
     if (!plugin) {
       rv = CreateNPAPIPlugin(pluginTag, getter_AddRefs(plugin));
       if (NS_FAILED(rv))
         return rv;
       pluginTag->mEntryPoint = plugin;
     }
 
     NS_ADDREF(*aPlugin = plugin);
@@ -1836,16 +1838,214 @@ NS_IMETHODIMP nsPluginHost::GetPlugin(co
   PLUGIN_LOG(PLUGIN_LOG_NORMAL,
   ("nsPluginHost::GetPlugin End mime=%s, rv=%d, plugin=%p name=%s\n",
   aMimeType, rv, *aPlugin,
   (pluginTag ? pluginTag->mFileName.get() : "(not found)")));
 
   return rv;
 }
 
+// Normalize 'host' to ACE.
+nsresult
+nsPluginHost::NormalizeHostname(nsCString& host)
+{
+  if (IsASCII(host)) {
+    ToLowerCase(host);
+    return NS_OK;
+  }
+
+  if (!mIDNService) {
+    nsresult rv;
+    mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return mIDNService->ConvertUTF8toACE(host, host);
+}
+
+// Enumerate a 'sites' array returned by GetSitesWithData and determine if
+// any of them have a base domain in common with 'domain'; if so, append them
+// to the 'result' array. If 'firstMatchOnly' is true, return after finding the
+// first match.
+nsresult
+nsPluginHost::EnumerateSiteData(const nsACString& domain,
+                                const nsTArray<nsCString>& sites,
+                                InfallibleTArray<nsCString>& result,
+                                bool firstMatchOnly)
+{
+  NS_ASSERTION(!domain.IsVoid(), "null domain string");
+
+  nsresult rv;
+  if (!mTLDService) {
+    mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Get the base domain from the domain.
+  nsCString baseDomain;
+  rv = mTLDService->GetBaseDomainFromHost(domain, 0, baseDomain);
+  bool isIP = rv == NS_ERROR_HOST_IS_IP_ADDRESS;
+  if (isIP || rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+    // The base domain is the site itself. However, we must be careful to
+    // normalize.
+    baseDomain = domain;
+    rv = NormalizeHostname(baseDomain);
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Enumerate the array of sites with data.
+  for (PRUint32 i = 0; i < sites.Length(); ++i) {
+    const nsCString& site = sites[i];
+
+    // Check if the site is an IP address.
+    bool siteIsIP =
+      site.Length() >= 2 && site.First() == '[' && site.Last() == ']';
+    if (siteIsIP != isIP)
+      continue;
+
+    nsCString siteBaseDomain;
+    if (siteIsIP) {
+      // Strip the '[]'.
+      siteBaseDomain = Substring(site, 1, site.Length() - 2);
+    } else {
+      // Determine the base domain of the site.
+      rv = mTLDService->GetBaseDomainFromHost(site, 0, siteBaseDomain);
+      if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+        // The base domain is the site itself. However, we must be careful to
+        // normalize.
+        siteBaseDomain = site;
+        rv = NormalizeHostname(siteBaseDomain);
+        NS_ENSURE_SUCCESS(rv, rv);
+      } else if (NS_FAILED(rv)) {
+        return rv;
+      }
+    }
+
+    // At this point, we can do an exact comparison of the two domains.
+    if (baseDomain != siteBaseDomain) {
+      continue;
+    }
+
+    // Append the site to the result array.
+    result.AppendElement(site);
+
+    // If we're supposed to return early, do so.
+    if (firstMatchOnly) {
+      break;
+    }
+  }
+
+  return NS_OK;
+}
+
+// Ensure that 'plugin' is still a live, valid tag, and that it's loaded.
+nsPluginTag*
+nsPluginHost::EnsurePlugin(nsIPluginTag* plugin)
+{
+  LoadPlugins();
+
+  // Make sure the nsIPluginTag we're given is still live.
+  nsPluginTag* tag;
+  for (tag = mPlugins; tag && tag != plugin; tag = tag->mNext);
+  if (!tag) {
+    return NULL;
+  }
+
+  nsRefPtr<nsNPAPIPlugin> entrypoint = tag->mEntryPoint;
+  if (!entrypoint) {
+    nsresult rv = CreateNPAPIPlugin(tag, getter_AddRefs(entrypoint));
+    if (NS_FAILED(rv)) {
+      return NULL;
+    }
+
+    tag->mEntryPoint = entrypoint;
+  }
+
+  return tag;
+}
+
+NS_IMETHODIMP
+nsPluginHost::ClearSiteData(nsIPluginTag* plugin, const nsACString& domain,
+                            PRUint64 flags, PRInt64 maxAge)
+{
+  // maxAge must be either a nonnegative integer or -1.
+  NS_ENSURE_ARG(maxAge >= 0 || maxAge == -1);
+
+  nsPluginTag* tag = EnsurePlugin(plugin);
+  if (!tag) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  PluginLibrary* library = tag->mEntryPoint->GetLibrary();
+
+  // If 'domain' is the null string, clear everything.
+  if (domain.IsVoid()) {
+    return library->NPP_ClearSiteData(NULL, flags, maxAge);
+  }
+
+  // Get the list of sites from the plugin.
+  InfallibleTArray<nsCString> sites;
+  nsresult rv = library->NPP_GetSitesWithData(sites);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Enumerate the sites and build a list of matches.
+  InfallibleTArray<nsCString> matches;
+  rv = EnumerateSiteData(domain, sites, matches, false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Clear the matches.
+  for (PRUint32 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;
+}
+
+NS_IMETHODIMP
+nsPluginHost::SiteHasData(nsIPluginTag* plugin, const nsACString& domain,
+                          PRBool* result)
+{
+  nsPluginTag* tag = EnsurePlugin(plugin);
+  if (!tag) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  PluginLibrary* library = tag->mEntryPoint->GetLibrary();
+
+  // Get the list of sites from the plugin.
+  InfallibleTArray<nsCString> sites;
+  nsresult rv = library->NPP_GetSitesWithData(sites);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // 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<nsCString> matches;
+  rv = EnumerateSiteData(domain, sites, matches, true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *result = !matches.IsEmpty();
+  return NS_OK;
+}
+
 // XXX called from ScanPluginsDirectory only when told to filter
 // currently 'unwanted' plugins are Java, and all other plugins except
 // Acrobat, Flash, Quicktime and Shockwave
 static PRBool isUnwantedPlugin(nsPluginTag * tag)
 {
   if (tag->mFileName.IsEmpty())
     return PR_TRUE;
 
--- a/modules/plugin/base/src/nsPluginHost.h
+++ b/modules/plugin/base/src/nsPluginHost.h
@@ -56,16 +56,18 @@
 #include "nsISupportsArray.h"
 #include "nsIPrefBranch.h"
 #include "nsWeakReference.h"
 #include "nsThreadUtils.h"
 #include "nsTArray.h"
 #include "nsTObserverArray.h"
 #include "nsITimer.h"
 #include "nsPluginTags.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIIDNService.h"
 
 class nsNPAPIPlugin;
 class nsIComponentManager;
 class nsIFile;
 class nsIChannel;
 
 #if defined(XP_MACOSX) && !defined(NP_NO_CARBON)
 #define MAC_CARBON_PLUGINS
@@ -83,30 +85,32 @@ public:
   PRInt64     mLastModifiedTime;
   bool        mSeen;
   
   nsRefPtr<nsInvalidPluginTag> mPrev;
   nsRefPtr<nsInvalidPluginTag> mNext;
 };
 
 class nsPluginHost : public nsIPluginHost,
+                     public nsIPluginHost_MOZILLA_2_0_BRANCH,
                      public nsIObserver,
                      public nsITimerCallback,
                      public nsSupportsWeakReference
 {
 public:
   nsPluginHost();
   virtual ~nsPluginHost();
 
   static nsPluginHost* GetInst();
 
   NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPLUGINHOST
+  NS_DECL_NSIPLUGINHOST_MOZILLA_2_0_BRANCH
   NS_DECL_NSIOBSERVER
   NS_DECL_NSITIMERCALLBACK
 
   NS_IMETHOD
   GetURL(nsISupports* pluginInst, 
          const char* url, 
          const char* target = NULL,
          nsIPluginStreamListener* streamListener = NULL,
@@ -287,16 +291,27 @@ private:
   nsTArray< nsRefPtr<nsNPAPIPluginInstance> > mInstances;
 
   nsCOMPtr<nsIFile> mPluginRegFile;
   nsCOMPtr<nsIPrefBranch> mPrefService;
 #ifdef XP_WIN
   nsRefPtr<nsPluginDirServiceProvider> mPrivateDirServiceProvider;
 #endif
 
+  nsCOMPtr<nsIEffectiveTLDService> mTLDService;
+  nsCOMPtr<nsIIDNService> mIDNService;
+
+  // Helpers for ClearSiteData and SiteHasData.
+  nsresult NormalizeHostname(nsCString& host);
+  nsresult EnumerateSiteData(const nsACString& domain,
+                             const nsTArray<nsCString>& sites,
+                             InfallibleTArray<nsCString>& result,
+                             bool firstMatchOnly);
+  nsPluginTag* EnsurePlugin(nsIPluginTag* plugin);
+
   nsWeakPtr mCurrentDocument; // weak reference, we use it to id document only
 
   static nsIFile *sPluginTempDir;
 
   // We need to hold a global ptr to ourselves because we register for
   // two different CIDs for some reason...
   static nsPluginHost* sInst;
 
--- a/modules/plugin/base/src/nsPluginTags.cpp
+++ b/modules/plugin/base/src/nsPluginTags.cpp
@@ -49,16 +49,17 @@
 #include "nsIPrefBranch.h"
 #include "nsPluginsDir.h"
 #include "nsPluginHost.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsIPlatformCharset.h"
 #include "nsICharsetConverterManager.h"
 #include "nsPluginLogging.h"
 #include "nsICategoryManager.h"
+#include "nsNPAPIPlugin.h"
 #include "mozilla/TimeStamp.h"
 
 using mozilla::TimeStamp;
 
 inline char* new_str(const char* str)
 {
   if (str == nsnull)
     return nsnull;
--- a/modules/plugin/base/src/nsPluginTags.h
+++ b/modules/plugin/base/src/nsPluginTags.h
@@ -105,17 +105,17 @@ public:
   nsPluginHost *mPluginHost;
   nsCString     mName; // UTF-8
   nsCString     mDescription; // UTF-8
   PRInt32       mVariants;
   char          **mMimeTypeArray;
   nsTArray<nsCString> mMimeDescriptionArray; // UTF-8
   char          **mExtensionsArray;
   PRLibrary     *mLibrary;
-  nsCOMPtr<nsIPlugin> mEntryPoint;
+  nsRefPtr<nsNPAPIPlugin> mEntryPoint;
   PRPackedBool  mCanUnloadLibrary;
   PRPackedBool  mIsJavaPlugin;
   PRPackedBool  mIsNPRuntimeEnabledJavaPlugin;
   nsCString     mFileName; // UTF-8
   nsCString     mFullPath; // UTF-8
   nsCString     mVersion;  // UTF-8
   PRInt64       mLastModifiedTime;
 private:
--- a/modules/plugin/test/mochitest/Makefile.in
+++ b/modules/plugin/test/mochitest/Makefile.in
@@ -92,16 +92,17 @@ include $(topsrcdir)/config/rules.mk
   test_GCrace.html \
   test_propertyAndMethod.html \
   test_bug539565-1.html \
   test_bug539565-2.html \
   test_enumerate.html \
   test_npruntime_construct.html \
   307-xo-redirect.sjs \
   test_redirect_handling.html \
+  test_clear_site_data.html \
   $(NULL)
 
 #  test_plugin_scroll_painting.html \ bug 596491
 #  test_npruntime_npnsetexception.html \ Disabled for e10s
 
 ifeq ($(OS_ARCH),WINNT)
 _MOCHITEST_FILES += \
   test_windowed_invalidate.html \
new file mode 100644
--- /dev/null
+++ b/modules/plugin/test/mochitest/test_clear_site_data.html
@@ -0,0 +1,203 @@
+<html>
+<head>
+  <title>NPAPI ClearSiteData/GetSitesWithData Functionality</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/PluginUtils.js"></script>
+</head>
+<body>
+  <embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
+
+  <script class="testbody" type="application/javascript">
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+    SimpleTest.waitForExplicitFinish();
+
+    const pluginHostIface = Components.interfaces.nsIPluginHost_MOZILLA_2_0_BRANCH;
+    var pluginHost = Components.classes["@mozilla.org/plugin/host;1"].
+                     getService(pluginHostIface);
+    const FLAG_CLEAR_ALL = pluginHostIface.FLAG_CLEAR_ALL;
+    const FLAG_CLEAR_CACHE = pluginHostIface.FLAG_CLEAR_CACHE;
+
+    // Make sure clearing by timerange is supported.
+    var p = document.getElementById("plugin1");
+    p.setSitesWithDataCapabilities(true);
+
+    ok(PluginUtils.withTestPlugin(runTest), "Test plugin found");
+
+    function stored(needles) {
+      var something = pluginHost.siteHasData(this.pluginTag, null);
+      if (!needles)
+        return something;
+
+      if (!something)
+        return false;
+
+      for (var i = 0; i < needles.length; ++i) {
+        if (!pluginHost.siteHasData(this.pluginTag, needles[i]))
+          return false;
+      }
+      return true;
+    }
+
+    function checkThrows(fn, result) {
+      try {
+        fn();
+        throw new Error("bad exception");
+      } catch (e) {
+        is(e.result, result, "Correct exception thrown");
+      }
+    }
+
+    function runTest(pluginTag) {
+      netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+      this.pluginTag = pluginTag;
+
+      p.setSitesWithData(
+        "foo.com:0:5," +
+        "foo.com:0:7," +
+        "bar.com:0:10," +
+        "baz.com:0:10," +
+        "foo.com:1:7," +
+        "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);
+      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);
+      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);
+      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);
+      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);
+      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);
+      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);
+
+      // 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);
+
+      // 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);
+      ok(!stored(["baz.com"]), "Data cleared for baz.com");
+      ok(!stored(null), "All data cleared");
+
+      // Set data to test subdomains, IP literals, and 'localhost'-like hosts.
+      p.setSitesWithData(
+        "foo.com:0:0," +
+        "bar.foo.com:0:0," +
+        "baz.foo.com:0:0," +
+        "bar.com:0:0," +
+        "[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");
+
+      // Clear data for "foo.com" and its subdomains.
+      pluginHost.clearSiteData(pluginTag, "foo.com", FLAG_CLEAR_ALL, -1);
+      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);
+      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);
+      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);
+      ok(!stored(null), "All data cleared");
+
+      // Set data to test international domains.
+      p.setSitesWithData(
+        "b\u00FCcher.es:0:0," +
+        "b\u00FCcher.uk:0:0," +
+        "xn--bcher-kva.NZ:0:0"
+      );
+
+      // 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");
+
+      // Clear data for the UTF-8 version.
+      pluginHost.clearSiteData(pluginTag, "b\u00FCcher.es", FLAG_CLEAR_ALL, -1);
+      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);
+      ok(!stored(["b\u00FCcher.uk"]), "Data cleared for UTF-8 representation");
+      ok(!stored(["xn--bcher-kva.uk"]), "Data cleared for ACE representation");
+
+      // The NPAPI spec requires that the plugin report sites in normalized
+      // 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);
+      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();
+    }
+  </script>
+</body>
+</html>
--- a/modules/plugin/test/testplugin/nptest.cpp
+++ b/modules/plugin/test/testplugin/nptest.cpp
@@ -39,16 +39,17 @@
 #include "mozilla/IntentionalCrash.h"
 
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
 #include <iostream>
 #include <string>
 #include <sstream>
+#include <list>
 
 #ifdef XP_WIN
 #include <process.h>
 #include <float.h>
 #include <windows.h>
 #define getpid _getpid
 #else
 #include <unistd.h>
@@ -157,16 +158,18 @@ static bool getTopLevelWindowActivationS
 static bool getTopLevelWindowActivationEventCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getFocusState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getFocusEventCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getEventModel(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getReflector(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool isVisible(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getWindowPosition(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool constructObject(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+static bool setSitesWithData(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+static bool setSitesWithDataCapabilities(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 
 static const NPUTF8* sPluginMethodIdentifierNames[] = {
   "npnEvaluateTest",
   "npnInvokeTest",
   "npnInvokeDefaultTest",
   "setUndefinedValueTest",
   "identifierToStringTest",
   "timerTest",
@@ -211,17 +214,19 @@ static const NPUTF8* sPluginMethodIdenti
   "getTopLevelWindowActivationState",
   "getTopLevelWindowActivationEventCount",
   "getFocusState",
   "getFocusEventCount",
   "getEventModel",
   "getReflector",
   "isVisible",
   "getWindowPosition",
-  "constructObject"
+  "constructObject",
+  "setSitesWithData",
+  "setSitesWithDataCapabilities"
 };
 static NPIdentifier sPluginMethodIdentifiers[ARRAY_LENGTH(sPluginMethodIdentifierNames)];
 static const ScriptableFunction sPluginMethodFunctions[] = {
   npnEvaluateTest,
   npnInvokeTest,
   npnInvokeDefaultTest,
   setUndefinedValueTest,
   identifierToStringTest,
@@ -267,17 +272,19 @@ static const ScriptableFunction sPluginM
   getTopLevelWindowActivationState,
   getTopLevelWindowActivationEventCount,
   getFocusState,
   getFocusEventCount,
   getEventModel,
   getReflector,
   isVisible,
   getWindowPosition,
-  constructObject
+  constructObject,
+  setSitesWithData,
+  setSitesWithDataCapabilities
 };
 
 STATIC_ASSERT(ARRAY_LENGTH(sPluginMethodIdentifierNames) ==
               ARRAY_LENGTH(sPluginMethodFunctions));
 
 static const NPUTF8* sPluginPropertyIdentifierNames[] = {
   "propertyAndMethod"
 };
@@ -342,16 +349,28 @@ static int32_t sCurrentInstanceCountWatc
  */
 static int32_t sInstanceCount = 0;
 /**
  * True when we've had a startWatchingInstanceCount with no corresponding
  * stopWatchingInstanceCount.
  */
 static bool sWatchingInstanceCount = false;
 
+/**
+ * A list representing sites for which the plugin has stored data. See
+ * NPP_ClearSiteData and NPP_GetSitesWithData.
+ */
+struct siteData {
+  string site;
+  uint64_t flags;
+  uint64_t age;
+};
+static list<siteData>* sSitesWithData;
+static bool sClearByAgeSupported;
+
 static void initializeIdentifiers()
 {
   if (!sIdentifiersInitialized) {
     NPN_GetStringIdentifiers(sPluginMethodIdentifierNames,
         ARRAY_LENGTH(sPluginMethodIdentifierNames), sPluginMethodIdentifiers);
     NPN_GetStringIdentifiers(sPluginPropertyIdentifierNames,
         ARRAY_LENGTH(sPluginPropertyIdentifierNames), sPluginPropertyIdentifiers);
 
@@ -590,16 +609,18 @@ static bool fillPluginFunctionTable(NPPl
   pFuncs->writeready = NPP_WriteReady;
   pFuncs->write = NPP_Write;
   pFuncs->print = NPP_Print;
   pFuncs->event = NPP_HandleEvent;
   pFuncs->urlnotify = NPP_URLNotify;
   pFuncs->getvalue = NPP_GetValue;
   pFuncs->setvalue = NPP_SetValue;
   pFuncs->urlredirectnotify = NPP_URLRedirectNotify;
+  pFuncs->clearsitedata = NPP_ClearSiteData;
+  pFuncs->getsiteswithdata = NPP_GetSitesWithData;
 
   return true;
 }
 
 #if defined(XP_MACOSX)
 NP_EXPORT(NPError) NP_Initialize(NPNetscapeFuncs* bFuncs)
 #elif defined(XP_WIN) || defined(XP_OS2)
 NPError OSCALL NP_Initialize(NPNetscapeFuncs* bFuncs)
@@ -1341,16 +1362,94 @@ NPP_URLRedirectNotify(NPP instance, cons
       NPN_ReleaseVariantValue(&result);
     }
     NPN_URLRedirectResponse(instance, notifyData, nd->allowRedirects);
     return;
   }
   NPN_URLRedirectResponse(instance, notifyData, true);
 }
 
+NPError
+NPP_ClearSiteData(const char* site, uint64_t flags, uint64_t maxAge)
+{
+  if (!sSitesWithData)
+    return NPERR_NO_ERROR;
+
+  // Error condition: no support for clear-by-age
+  if (!sClearByAgeSupported && maxAge != uint64_t(int64_t(-1)))
+    return NPERR_TIME_RANGE_NOT_SUPPORTED;
+
+  // Iterate over list and remove matches
+  list<siteData>::iterator iter = sSitesWithData->begin();
+  list<siteData>::iterator end = sSitesWithData->end();
+  while (iter != end) {
+    const siteData& data = *iter;
+    list<siteData>::iterator next = iter;
+    ++next;
+    if ((!site || data.site.compare(site) == 0) &&
+        (flags == NP_CLEAR_ALL || data.flags & flags) &&
+        data.age <= maxAge) {
+      sSitesWithData->erase(iter);
+    }
+    iter = next;
+  }
+
+  return NPERR_NO_ERROR;
+}
+
+char**
+NPP_GetSitesWithData()
+{
+  int length = 0;
+  char** result;
+
+  if (sSitesWithData)
+    length = sSitesWithData->size();
+
+  // Allocate the maximum possible size the list could be.
+  result = static_cast<char**>(NPN_MemAlloc((length + 1) * sizeof(char*)));
+  result[length] = NULL;
+
+  if (length == 0) {
+    // Represent the no site data case as an array of length 1 with a NULL
+    // entry.
+    return result;
+  }
+
+  // Iterate the list of stored data, and build a list of strings.
+  list<string> sites;
+  {
+    list<siteData>::iterator iter = sSitesWithData->begin();
+    list<siteData>::iterator end = sSitesWithData->end();
+    for (; iter != end; ++iter) {
+      const siteData& data = *iter;
+      sites.push_back(data.site);
+    }
+  }
+
+  // Remove duplicate strings.
+  sites.sort();
+  sites.unique();
+
+  // Add strings to the result array, and null terminate.
+  {
+    int i = 0;
+    list<string>::iterator iter = sites.begin();
+    list<string>::iterator end = sites.end();
+    for (; iter != end; ++iter, ++i) {
+      const string& site = *iter;
+      result[i] = static_cast<char*>(NPN_MemAlloc(site.length() + 1));
+      memcpy(result[i], site.c_str(), site.length() + 1);
+    }
+  }
+  result[sites.size()] = NULL;
+
+  return result;
+}
+
 //
 // npapi browser functions
 //
 
 bool
 NPN_SetProperty(NPP instance, NPObject* obj, NPIdentifier propertyName, const NPVariant* value)
 {
   return sBrowserFuncs->setproperty(instance, obj, propertyName, value);
@@ -3268,8 +3367,62 @@ bool constructObject(NPObject* npobj, co
     return false;
 
   NPObject* ctor = NPVARIANT_TO_OBJECT(args[0]);
   
   NPP npp = static_cast<TestNPObject*>(npobj)->npp;
 
   return NPN_Construct(npp, ctor, args + 1, argCount - 1, result);
 }
+
+bool setSitesWithData(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
+{
+  if (argCount != 1 || !NPVARIANT_IS_STRING(args[0]))
+    return false;
+
+  // Clear existing data.
+  delete sSitesWithData;
+
+  const NPString* str = &NPVARIANT_TO_STRING(args[0]);
+  if (str->UTF8Length == 0)
+    return true;
+
+  // Parse the comma-delimited string into a vector.
+  sSitesWithData = new list<siteData>;
+  const char* iterator = str->UTF8Characters;
+  const char* end = iterator + str->UTF8Length;
+  while (1) {
+    const char* next = strchr(iterator, ',');
+    if (!next)
+      next = end;
+
+    // Parse out the three tokens into a siteData struct.
+    const char* siteEnd = strchr(iterator, ':');
+    *((char*) siteEnd) = NULL;
+    const char* flagsEnd = strchr(siteEnd + 1, ':');
+    *((char*) flagsEnd) = NULL;
+    *((char*) next) = NULL;
+    
+    siteData data;
+    data.site = string(iterator);
+    data.flags = atoi(siteEnd + 1);
+    data.age = atoi(flagsEnd + 1);
+    
+    sSitesWithData->push_back(data);
+
+    if (next == end)
+      break;
+
+    iterator = next + 1;
+  }
+
+  return true;
+}
+
+bool setSitesWithDataCapabilities(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
+{
+  if (argCount != 1 || !NPVARIANT_IS_BOOLEAN(args[0]))
+    return false;
+
+  sClearByAgeSupported = NPVARIANT_TO_BOOLEAN(args[0]);
+  return true;
+}
+