diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp
index 65ef53443b3f629ed15acad5926444505ceeef03..7b8f51ae3c8131f6a8182d919880e28d0735b832 100644
--- a/indra/llui/llnotifications.cpp
+++ b/indra/llui/llnotifications.cpp
@@ -48,122 +48,38 @@
 
 const std::string NOTIFICATION_PERSIST_VERSION = "0.93";
 
-// local channel for notification history
-class LLNotificationHistoryChannel : public LLNotificationChannel
+// Local channel for persistent notifications
+// Stores only persistent notifications.
+// Class users can use connectChanged() to process persistent notifications
+// (see LLNotificationStorage for example).
+class LLPersistentNotificationChannel : public LLNotificationChannel
 {
-	LOG_CLASS(LLNotificationHistoryChannel);
+	LOG_CLASS(LLPersistentNotificationChannel);
 public:
-	LLNotificationHistoryChannel(const std::string& filename) : 
-		LLNotificationChannel("History", "Visible", &historyFilter, LLNotificationComparators::orderByUUID()),
-		mFileName(filename)
+	LLPersistentNotificationChannel() :
+		LLNotificationChannel("Persistent", "Visible", &notificationFilter, LLNotificationComparators::orderByUUID())
 	{
-		connectChanged(boost::bind(&LLNotificationHistoryChannel::historyHandler, this, _1));
-		loadPersistentNotifications();
 	}
 
 private:
-	bool historyHandler(const LLSD& payload)
-	{
-		// we ignore "load" messages, but rewrite the persistence file on any other
-		std::string sigtype = payload["sigtype"];
-		if (sigtype != "load")
-		{
-			savePersistentNotifications();
-		}
-		return false;
-	}
-
-	// The history channel gets all notifications except those that have been cancelled
-	static bool historyFilter(LLNotificationPtr pNotification)
-	{
-		return !pNotification->isCancelled();
-	}
 
-	void savePersistentNotifications()
+	// The channel gets all persistent notifications except those that have been canceled
+	static bool notificationFilter(LLNotificationPtr pNotification)
 	{
-		/* NOTE: As of 2009-11-09 the reload of notifications on startup does not
-		work, and has not worked for months.  Skip saving notifications until the
-		read can be fixed, because this hits the disk once per notification and
-		causes log spam.  James
-
-		llinfos << "Saving open notifications to " << mFileName << llendl;
+		bool handle_notification = false;
 
-		llofstream notify_file(mFileName.c_str());
-		if (!notify_file.is_open()) 
-		{
-			llwarns << "Failed to open " << mFileName << llendl;
-			return;
-		}
-
-		LLSD output;
-		output["version"] = NOTIFICATION_PERSIST_VERSION;
-		LLSD& data = output["data"];
-
-		for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)
-		{
-			if (!LLNotifications::instance().templateExists((*it)->getName())) continue;
+		handle_notification = pNotification->isPersistent()
+			&& !pNotification->isCancelled();
 
-			// only store notifications flagged as persisting
-			LLNotificationTemplatePtr templatep = LLNotifications::instance().getTemplate((*it)->getName());
-			if (!templatep->mPersist) continue;
-
-			data.append((*it)->asLLSD());
-		}
-
-		LLPointer<LLSDFormatter> formatter = new LLSDXMLFormatter();
-		formatter->format(output, notify_file, LLSDFormatter::OPTIONS_PRETTY);
-		*/
+		return handle_notification;
 	}
 
-	void loadPersistentNotifications()
-	{
-		llinfos << "Loading open notifications from " << mFileName << llendl;
-
-		llifstream notify_file(mFileName.c_str());
-		if (!notify_file.is_open()) 
-		{
-			llwarns << "Failed to open " << mFileName << llendl;
-			return;
-		}
-
-		LLSD input;
-		LLPointer<LLSDParser> parser = new LLSDXMLParser();
-		if (parser->parse(notify_file, input, LLSDSerialize::SIZE_UNLIMITED) < 0)
-		{
-			llwarns << "Failed to parse open notifications" << llendl;
-			return;
-		}
-
-		if (input.isUndefined()) return;
-		std::string version = input["version"];
-		if (version != NOTIFICATION_PERSIST_VERSION)
-		{
-			llwarns << "Bad open notifications version: " << version << llendl;
-			return;
-		}
-		LLSD& data = input["data"];
-		if (data.isUndefined()) return;
-
-		LLNotifications& instance = LLNotifications::instance();
-		for (LLSD::array_const_iterator notification_it = data.beginArray();
-			notification_it != data.endArray();
-			++notification_it)
-		{
-			instance.add(LLNotificationPtr(new LLNotification(*notification_it)));
-		}
-	}
-
-	//virtual
 	void onDelete(LLNotificationPtr pNotification)
 	{
-		// we want to keep deleted notifications in our log
+		// we want to keep deleted notifications in our log, otherwise some 
+		// notifications will be lost on exit.
 		mItems.insert(pNotification);
-		
-		return;
 	}
-	
-private:
-	std::string mFileName;
 };
 
 bool filterIgnoredNotifications(LLNotificationPtr notification)
@@ -417,6 +333,10 @@ LLNotification::LLNotification(const LLNotification::Params& p) :
 
 		mTemporaryResponder = true;
 	}
+	else if(p.functor.responder.isChosen())
+	{
+		mResponder = p.functor.responder;
+	}
 
 	if(p.responder.isProvided())
 	{
@@ -462,6 +382,12 @@ LLSD LLNotification::asLLSD()
 	output["priority"] = (S32)mPriority;
 	output["responseFunctor"] = mResponseFunctorName;
 	output["reusable"] = mIsReusable;
+
+	if(mResponder)
+	{
+		output["responder"] = mResponder->asLLSD();
+	}
+
 	return output;
 }
 
@@ -571,12 +497,20 @@ void LLNotification::respond(const LLSD& response)
 	// *TODO may remove mRespondedTo and use mResponce.isDefined() in isRespondedTo()
 	mRespondedTo = true;
 	mResponse = response;
-	// look up the functor
-	LLNotificationFunctorRegistry::ResponseFunctor functor = 
-		LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName);
-	// and then call it
-	functor(asLLSD(), response);
-	
+
+	if(mResponder)
+	{
+		mResponder->handleRespond(asLLSD(), response);
+	}
+	else
+	{
+		// look up the functor
+		LLNotificationFunctorRegistry::ResponseFunctor functor =
+			LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName);
+		// and then call it
+		functor(asLLSD(), response);
+	}
+
 	if (mTemporaryResponder && !isReusable())
 	{
 		LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName);
@@ -621,6 +555,11 @@ void LLNotification::setResponseFunctor(const LLNotificationFunctorRegistry::Res
 	LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, cb);
 }
 
+void LLNotification::setResponseFunctor(const LLNotificationResponderPtr& responder)
+{
+	mResponder = responder;
+}
+
 bool LLNotification::payloadContainsAll(const std::vector<std::string>& required_fields) const
 {
 	for(std::vector<std::string>::const_iterator required_fields_it = required_fields.begin(); 
@@ -1116,12 +1055,9 @@ void LLNotifications::createDefaultChannels()
 	LLNotificationChannel::buildChannel("Visible", "Ignore",
 		&LLNotificationFilters::includeEverything);
 
-	// create special history channel
-	//std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_PER_SL_ACCOUNT, "open_notifications.xml" );
-	// use ^^^ when done debugging notifications serialization
-	std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_USER_SETTINGS, "open_notifications.xml" );
+	// create special persistent notification channel
 	// this isn't a leak, don't worry about the empty "new"
-	new LLNotificationHistoryChannel(notifications_log_file);
+	new LLPersistentNotificationChannel();
 
 	// connect action methods to these channels
 	LLNotifications::instance().getChannel("Expiration")->
diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h
index 1799ca65b7ac4eb9f62d6103b60e996ab122402f..c942a3251203ebd728e0ca7dfa4d6929cd29a611 100644
--- a/indra/llui/llnotifications.h
+++ b/indra/llui/llnotifications.h
@@ -116,8 +116,23 @@ typedef enum e_notification_priority
 	NOTIFICATION_PRIORITY_CRITICAL
 } ENotificationPriority;
 
+class LLNotificationResponderInterface
+{
+public:
+	LLNotificationResponderInterface(){};
+	virtual ~LLNotificationResponderInterface(){};
+
+	virtual void handleRespond(const LLSD& notification, const LLSD& response) = 0;
+
+	virtual LLSD asLLSD() = 0;
+
+	virtual void fromLLSD(const LLSD& params) = 0;
+};
+
 typedef boost::function<void (const LLSD&, const LLSD&)> LLNotificationResponder;
 
+typedef boost::shared_ptr<LLNotificationResponderInterface> LLNotificationResponderPtr;
+
 typedef LLFunctorRegistry<LLNotificationResponder> LLNotificationFunctorRegistry;
 typedef LLFunctorRegistration<LLNotificationResponder> LLNotificationFunctorRegistration;
 
@@ -303,10 +318,12 @@ friend class LLNotifications;
 		{
 			Alternative<std::string>										name;
 			Alternative<LLNotificationFunctorRegistry::ResponseFunctor>	function;
+			Alternative<LLNotificationResponderPtr>						responder;
 
 			Functor()
 			:	name("functor_name"),
-				function("functor")
+				function("functor"),
+				responder("responder")
 			{}
 		};
 		Optional<Functor>						functor;
@@ -349,12 +366,13 @@ friend class LLNotifications;
 	bool mIgnored;
 	ENotificationPriority mPriority;
 	LLNotificationFormPtr mForm;
-	void* mResponderObj;
+	void* mResponderObj; // TODO - refactor/remove this field
 	bool mIsReusable;
-	
+	LLNotificationResponderPtr mResponder;
+
 	// a reference to the template
 	LLNotificationTemplatePtr mTemplatep;
-	
+
 	/*
 	 We want to be able to store and reload notifications so that they can survive
 	 a shutdown/restart of the client. So we can't simply pass in callbacks;
@@ -393,6 +411,8 @@ friend class LLNotifications;
 
 	void setResponseFunctor(const LLNotificationFunctorRegistry::ResponseFunctor& cb);
 
+	void setResponseFunctor(const LLNotificationResponderPtr& responder);
+
 	typedef enum e_response_template_type
 	{
 		WITHOUT_DEFAULT_BUTTON,
@@ -459,7 +479,12 @@ friend class LLNotifications;
 	{
 		return mTemplatep->mName;
 	}
-	
+
+	bool isPersistent() const
+	{
+		return mTemplatep->mPersist;
+	}
+
 	const LLUUID& id() const
 	{
 		return mId;
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index efb16d1e4217ea2c635d47e8c68d77580c6a9468..b1419de2963b445297a1a44fab81e4df3266d394 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -294,6 +294,7 @@ set(viewer_SOURCE_FILES
     llnotificationmanager.cpp
     llnotificationofferhandler.cpp
     llnotificationscripthandler.cpp
+    llnotificationstorage.cpp
     llnotificationtiphandler.cpp
     lloutputmonitorctrl.cpp
     llpanelavatar.cpp
@@ -795,6 +796,7 @@ set(viewer_HEADER_FILES
     llnetmap.h
     llnotificationhandler.h
     llnotificationmanager.h
+    llnotificationstorage.h
     lloutputmonitorctrl.h
     llpanelavatar.h
     llpanelavatartag.h
diff --git a/indra/newview/llchannelmanager.cpp b/indra/newview/llchannelmanager.cpp
index 769387c26cb976682180391350eaad304759c969..4f9434030f0a7588b8ab99c64ac990dcd8359173 100644
--- a/indra/newview/llchannelmanager.cpp
+++ b/indra/newview/llchannelmanager.cpp
@@ -35,6 +35,7 @@
 #include "llchannelmanager.h"
 
 #include "llappviewer.h"
+#include "llnotificationstorage.h"
 #include "llviewercontrol.h"
 #include "llviewerwindow.h"
 #include "llrootview.h"
@@ -107,31 +108,35 @@ void LLChannelManager::onLoginCompleted()
 	if(!away_notifications)
 	{
 		onStartUpToastClose();
-		return;
 	}
-	
-	// create a channel for the StartUp Toast
-	LLChannelManager::Params p;
-	p.id = LLUUID(gSavedSettings.getString("StartUpChannelUUID"));
-	p.channel_align = CA_RIGHT;
-	mStartUpChannel = createChannel(p);
-
-	if(!mStartUpChannel)
+	else
 	{
-		onStartUpToastClose();
-		return;
-	}
+		// create a channel for the StartUp Toast
+		LLChannelManager::Params p;
+		p.id = LLUUID(gSavedSettings.getString("StartUpChannelUUID"));
+		p.channel_align = CA_RIGHT;
+		mStartUpChannel = createChannel(p);
 
-	gViewerWindow->getRootView()->addChild(mStartUpChannel);
+		if(!mStartUpChannel)
+		{
+			onStartUpToastClose();
+		}
+		else
+		{
+			gViewerWindow->getRootView()->addChild(mStartUpChannel);
 
-	// init channel's position and size
-	S32 channel_right_bound = gViewerWindow->getWorldViewRectScaled().mRight - gSavedSettings.getS32("NotificationChannelRightMargin"); 
-	S32 channel_width = gSavedSettings.getS32("NotifyBoxWidth");
-	mStartUpChannel->init(channel_right_bound - channel_width, channel_right_bound);
-	mStartUpChannel->setMouseDownCallback(boost::bind(&LLNotificationWellWindow::onStartUpToastClick, LLNotificationWellWindow::getInstance(), _2, _3, _4));
+			// init channel's position and size
+			S32 channel_right_bound = gViewerWindow->getWorldViewRectScaled().mRight - gSavedSettings.getS32("NotificationChannelRightMargin"); 
+			S32 channel_width = gSavedSettings.getS32("NotifyBoxWidth");
+			mStartUpChannel->init(channel_right_bound - channel_width, channel_right_bound);
+			mStartUpChannel->setMouseDownCallback(boost::bind(&LLNotificationWellWindow::onStartUpToastClick, LLNotificationWellWindow::getInstance(), _2, _3, _4));
+
+			mStartUpChannel->setCommitCallback(boost::bind(&LLChannelManager::onStartUpToastClose, this));
+			mStartUpChannel->createStartUpToast(away_notifications, gSavedSettings.getS32("StartUpToastLifeTime"));
+		}
+	}
 
-	mStartUpChannel->setCommitCallback(boost::bind(&LLChannelManager::onStartUpToastClose, this));
-	mStartUpChannel->createStartUpToast(away_notifications, gSavedSettings.getS32("StartUpToastLifeTime"));
+	LLPersistentNotificationStorage::getInstance()->loadNotifications();
 }
 
 //--------------------------------------------------------------------------
diff --git a/indra/newview/llnotificationstorage.cpp b/indra/newview/llnotificationstorage.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..316ff4324c297a6c5a9f8d8fdcdf25cf5f3973db
--- /dev/null
+++ b/indra/newview/llnotificationstorage.cpp
@@ -0,0 +1,228 @@
+/**
+* @file llnotificationstorage.cpp
+* @brief LLPersistentNotificationStorage class implementation
+*
+* $LicenseInfo:firstyear=2010&license=viewergpl$
+*
+* Copyright (c) 2010, Linden Research, Inc.
+*
+* Second Life Viewer Source Code
+* The source code in this file ("Source Code") is provided by Linden Lab
+* to you under the terms of the GNU General Public License, version 2.0
+* ("GPL"), unless you have obtained a separate licensing agreement
+* ("Other License"), formally executed by you and Linden Lab.  Terms of
+* the GPL can be found in doc/GPL-license.txt in this distribution, or
+* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+*
+* There are special exceptions to the terms and conditions of the GPL as
+* it is applied to this Source Code. View the full text of the exception
+* in the file doc/FLOSS-exception.txt in this software distribution, or
+* online at
+* http://secondlifegrid.net/programs/open_source/licensing/flossexception
+*
+* By copying, modifying or distributing this software, you acknowledge
+* that you have read and understood your obligations described above,
+* and agree to abide by those obligations.
+*
+* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+* COMPLETENESS OR PERFORMANCE.
+* $/LicenseInfo$
+*/
+
+#include "llviewerprecompiledheaders.h" // must be first include
+#include "llnotificationstorage.h"
+
+#include "llxmlnode.h" // for linux compilers
+
+#include "llchannelmanager.h"
+#include "llscreenchannel.h"
+#include "llscriptfloater.h"
+#include "llsdserialize.h"
+#include "llviewermessage.h"
+
+//////////////////////////////////////////////////////////////////////////
+
+class LLResponderRegistry
+{
+public:
+
+	static void registerResponders();
+
+	static LLNotificationResponderInterface* createResponder(const std::string& notification_name, const LLSD& params);
+
+private:
+
+	template<typename RESPONDER_TYPE>
+	static LLNotificationResponderInterface* create(const LLSD& params)
+	{
+		RESPONDER_TYPE* responder = new RESPONDER_TYPE();
+		responder->fromLLSD(params);
+		return responder;
+	}
+
+	typedef boost::function<LLNotificationResponderInterface* (const LLSD& params)> responder_constructor_t;
+
+	static void add(const std::string& notification_name, const responder_constructor_t& ctr);
+
+private:
+
+	typedef std::map<std::string, responder_constructor_t> build_map_t;
+
+	static build_map_t sBuildMap;
+};
+
+//////////////////////////////////////////////////////////////////////////
+
+LLPersistentNotificationStorage::LLPersistentNotificationStorage()
+{
+	mFileName = gDirUtilp->getExpandedFilename ( LL_PATH_PER_SL_ACCOUNT, "open_notifications.xml" );
+}
+
+bool LLPersistentNotificationStorage::onPersistentChannelChanged(const LLSD& payload)
+{
+	// we ignore "load" messages, but rewrite the persistence file on any other
+	const std::string sigtype = payload["sigtype"].asString();
+	if ("load" != sigtype)
+	{
+		saveNotifications();
+	}
+	return false;
+}
+
+void LLPersistentNotificationStorage::saveNotifications()
+{
+	// TODO - think about save optimization.
+
+	llofstream notify_file(mFileName.c_str());
+	if (!notify_file.is_open())
+	{
+		llwarns << "Failed to open " << mFileName << llendl;
+		return;
+	}
+
+	LLSD output;
+	LLSD& data = output["data"];
+
+	LLNotificationChannelPtr history_channel = LLNotifications::instance().getChannel("Persistent");
+	LLNotificationSet::iterator it = history_channel->begin();
+
+	for ( ; history_channel->end() != it; ++it)
+	{
+		LLNotificationPtr notification = *it;
+
+		// After a notification was placed in Persist channel, it can become
+		// responded, expired - in this case we are should not save it
+		if(notification->isRespondedTo()
+			|| notification->isExpired())
+		{
+			continue;
+		}
+
+		data.append(notification->asLLSD());
+	}
+
+	LLPointer<LLSDFormatter> formatter = new LLSDXMLFormatter();
+	formatter->format(output, notify_file, LLSDFormatter::OPTIONS_PRETTY);
+}
+
+void LLPersistentNotificationStorage::loadNotifications()
+{
+	LLResponderRegistry::registerResponders();
+
+	LLNotifications::instance().getChannel("Persistent")->
+		connectChanged(boost::bind(&LLPersistentNotificationStorage::onPersistentChannelChanged, this, _1));
+
+	llifstream notify_file(mFileName.c_str());
+	if (!notify_file.is_open())
+	{
+		llwarns << "Failed to open " << mFileName << llendl;
+		return;
+	}
+
+	LLSD input;
+	LLPointer<LLSDParser> parser = new LLSDXMLParser();
+	if (parser->parse(notify_file, input, LLSDSerialize::SIZE_UNLIMITED) < 0)
+	{
+		llwarns << "Failed to parse open notifications" << llendl;
+		return;
+	}
+
+	if (input.isUndefined())
+	{
+		return;
+	}
+
+	LLSD& data = input["data"];
+	if (data.isUndefined())
+	{
+		return;
+	}
+
+	using namespace LLNotificationsUI;
+	LLScreenChannel* notification_channel = dynamic_cast<LLScreenChannel*>(LLChannelManager::getInstance()->
+		findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID"))));
+
+	LLNotifications& instance = LLNotifications::instance();
+
+	for (LLSD::array_const_iterator notification_it = data.beginArray();
+		notification_it != data.endArray();
+		++notification_it)
+	{
+		LLSD notification_params = *notification_it;
+		LLNotificationPtr notification(new LLNotification(notification_params));
+
+		LLNotificationResponderPtr responder(LLResponderRegistry::
+			createResponder(notification_params["name"], notification_params["responder"]));
+		notification->setResponseFunctor(responder);
+
+		instance.add(notification);
+
+		// hide script floaters so they don't confuse the user and don't overlap startup toast
+		LLScriptFloaterManager::getInstance()->setFloaterVisible(notification->getID(), false);
+
+		if(notification_channel)
+		{
+			// hide saved toasts so they don't confuse the user
+			notification_channel->hideToast(notification->getID());
+		}
+	}
+}
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+LLResponderRegistry::build_map_t LLResponderRegistry::sBuildMap;
+
+void LLResponderRegistry::registerResponders()
+{
+	sBuildMap.clear();
+
+	add("ObjectGiveItem", &create<LLOfferInfo>);
+	add("UserGiveItem", &create<LLOfferInfo>);
+}
+
+LLNotificationResponderInterface* LLResponderRegistry::createResponder(const std::string& notification_name, const LLSD& params)
+{
+	build_map_t::const_iterator it = sBuildMap.find(notification_name);
+	if(sBuildMap.end() == it)
+	{
+		llwarns << "Responder for notification \'" << notification_name << "\' is not registered" << llendl;
+		return NULL;
+	}
+	responder_constructor_t ctr = it->second;
+	return ctr(params);
+}
+
+void LLResponderRegistry::add(const std::string& notification_name, const responder_constructor_t& ctr)
+{
+	if(sBuildMap.find(notification_name) != sBuildMap.end())
+	{
+		llwarns << "Responder is already registered : " << notification_name << llendl;
+		llassert(!"Responder already registered");
+	}
+	sBuildMap[notification_name] = ctr;
+}
+
+// EOF
diff --git a/indra/newview/llnotificationstorage.h b/indra/newview/llnotificationstorage.h
new file mode 100644
index 0000000000000000000000000000000000000000..5050781a85f4cbe0537a7b8937b8388cf917eb02
--- /dev/null
+++ b/indra/newview/llnotificationstorage.h
@@ -0,0 +1,65 @@
+/**
+* @file llnotificationstorage.h
+* @brief LLNotificationStorage class declaration
+*
+* $LicenseInfo:firstyear=2010&license=viewergpl$
+*
+* Copyright (c) 2010, Linden Research, Inc.
+*
+* Second Life Viewer Source Code
+* The source code in this file ("Source Code") is provided by Linden Lab
+* to you under the terms of the GNU General Public License, version 2.0
+* ("GPL"), unless you have obtained a separate licensing agreement
+* ("Other License"), formally executed by you and Linden Lab.  Terms of
+* the GPL can be found in doc/GPL-license.txt in this distribution, or
+* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+*
+* There are special exceptions to the terms and conditions of the GPL as
+* it is applied to this Source Code. View the full text of the exception
+* in the file doc/FLOSS-exception.txt in this software distribution, or
+* online at
+* http://secondlifegrid.net/programs/open_source/licensing/flossexception
+*
+* By copying, modifying or distributing this software, you acknowledge
+* that you have read and understood your obligations described above,
+* and agree to abide by those obligations.
+*
+* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+* COMPLETENESS OR PERFORMANCE.
+* $/LicenseInfo$
+*/
+
+#ifndef LL_NOTIFICATIONSTORAGE_H
+#define LL_NOTIFICATIONSTORAGE_H
+
+#include "llnotifications.h"
+
+// Class that saves not responded(unread) notifications.
+// Unread notifications are saved in open_notifications.xml in SL account folder
+//
+// Notifications that should be saved(if unread) are marked with persist="true" in notifications.xml
+// Notifications using functor responders are saved automatically (see llviewermessage.cpp
+// lure_callback_reg for example).
+// Notifications using object responders(LLOfferInfo) need additional tuning. Responder object should
+// be a) serializable(implement LLNotificationResponderInterface),
+// b) registered with LLResponderRegistry (found in llnotificationstorage.cpp).
+class LLPersistentNotificationStorage : public LLSingleton<LLPersistentNotificationStorage>
+{
+	LOG_CLASS(LLPersistentNotificationStorage);
+public:
+
+	LLPersistentNotificationStorage();
+
+	void saveNotifications();
+
+	void loadNotifications();
+
+private:
+
+	bool onPersistentChannelChanged(const LLSD& payload);
+
+	std::string mFileName;
+};
+
+#endif // LL_NOTIFICATIONSTORAGE_H
diff --git a/indra/newview/llpanelgroup.h b/indra/newview/llpanelgroup.h
index 136868a60d26e98230de5eb134f7b7011a1f08cd..359f25238397601ddaa56e410bfc4090d8b16a23 100644
--- a/indra/newview/llpanelgroup.h
+++ b/indra/newview/llpanelgroup.h
@@ -37,7 +37,7 @@
 #include "lltimer.h"
 #include "llvoiceclient.h"
 
-struct LLOfferInfo;
+class LLOfferInfo;
 
 const S32 UPDATE_MEMBERS_PER_FRAME = 500;
 
diff --git a/indra/newview/llscriptfloater.cpp b/indra/newview/llscriptfloater.cpp
index f35cb3516a81296fa4f5b09261625f802dea3503..11b6c0a3aeb702601520de973fc931fd7cbbd481 100644
--- a/indra/newview/llscriptfloater.cpp
+++ b/indra/newview/llscriptfloater.cpp
@@ -539,4 +539,14 @@ bool LLScriptFloaterManager::getFloaterPosition(const LLUUID& object_id, Floater
 	return false;
 }
 
+void LLScriptFloaterManager::setFloaterVisible(const LLUUID& notification_id, bool visible)
+{
+	LLScriptFloater* floater = LLFloaterReg::findTypedInstance<LLScriptFloater>(
+		"script_floater", notification_id);
+	if(floater)
+	{
+		floater->setVisible(visible);
+	}
+}
+
 // EOF
diff --git a/indra/newview/llscriptfloater.h b/indra/newview/llscriptfloater.h
index ec3ec4b540db0f699b6218ac986173ac653813a2..dc0cfc2400dd8f560d6fabf8b4523a9374e98b5e 100644
--- a/indra/newview/llscriptfloater.h
+++ b/indra/newview/llscriptfloater.h
@@ -99,6 +99,8 @@ class LLScriptFloaterManager : public LLSingleton<LLScriptFloaterManager>
 
 	bool getFloaterPosition(const LLUUID& object_id, FloaterPositionInfo& fpi);
 
+	void setFloaterVisible(const LLUUID& notification_id, bool visible);
+
 protected:
 
 	typedef std::map<std::string, EObjectType> object_type_map;
diff --git a/indra/newview/lltoastnotifypanel.cpp b/indra/newview/lltoastnotifypanel.cpp
index c9d2d404c0504edc4a9dedb650c89c79b7182970..089163929e5e57fa1b085e3633fb803fc1f63fe4 100644
--- a/indra/newview/lltoastnotifypanel.cpp
+++ b/indra/newview/lltoastnotifypanel.cpp
@@ -493,7 +493,7 @@ void LLToastNotifyPanel::onClickButton(void* data)
 	{
 		sButtonClickSignal(self->mNotification->getID(), button_name);
 
-		if(new_info)
+		if(new_info && !self->mNotification->isPersistent())
 		{
 			self->mNotification->setResponseFunctor(
 				boost::bind(&LLOfferInfo::inventory_offer_callback, new_info, _1, _2));
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index 960d9919ab48dbc55f603aefa4af54b1b730d649..2c39a94b1eb823d28ef7fd7ed70174b974cdc23d 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -1244,6 +1244,16 @@ void inventory_offer_mute_callback(const LLUUID& blocked_id,
 			gSavedSettings.getString("NotificationChannelUUID")), OfferMatcher(blocked_id));
 }
 
+LLOfferInfo::LLOfferInfo()
+ : LLNotificationResponderInterface()
+ , mFromGroup(FALSE)
+ , mFromObject(FALSE)
+ , mIM(IM_NOTHING_SPECIAL)
+ , mType(LLAssetType::AT_NONE)
+ , mPersist(false)
+{
+}
+
 LLOfferInfo::LLOfferInfo(const LLSD& sd)
 {
 	mIM = (EInstantMessage)sd["im_type"].asInteger();
@@ -1257,6 +1267,7 @@ LLOfferInfo::LLOfferInfo(const LLSD& sd)
 	mFromName = sd["from_name"].asString();
 	mDesc = sd["description"].asString();
 	mHost = LLHost(sd["sender"].asString());
+	mPersist = sd["persist"].asBoolean();
 }
 
 LLOfferInfo::LLOfferInfo(const LLOfferInfo& info)
@@ -1272,6 +1283,7 @@ LLOfferInfo::LLOfferInfo(const LLOfferInfo& info)
 	mFromName = info.mFromName;
 	mDesc = info.mDesc;
 	mHost = info.mHost;
+	mPersist = info.mPersist;
 }
 
 LLSD LLOfferInfo::asLLSD()
@@ -1288,9 +1300,15 @@ LLSD LLOfferInfo::asLLSD()
 	sd["from_name"] = mFromName;
 	sd["description"] = mDesc;
 	sd["sender"] = mHost.getIPandPort();
+	sd["persist"] = mPersist;
 	return sd;
 }
 
+void LLOfferInfo::fromLLSD(const LLSD& params)
+{
+	*this = params;
+}
+
 void LLOfferInfo::send_auto_receive_response(void)
 {	
 	LLMessageSystem* msg = gMessageSystem;
@@ -1330,6 +1348,21 @@ void LLOfferInfo::send_auto_receive_response(void)
 	}
 }
 
+void LLOfferInfo::handleRespond(const LLSD& notification, const LLSD& response)
+{
+	initRespondFunctionMap();
+
+	const std::string name = notification["name"].asString();
+	if(mRespondFunctions.find(name) == mRespondFunctions.end())
+	{
+		llwarns << "Unexpected notification name : " << name << llendl;
+		llassert(!"Unexpected notification name");
+		return;
+	}
+
+	mRespondFunctions[name](notification, response);
+}
+
 bool LLOfferInfo::inventory_offer_callback(const LLSD& notification, const LLSD& response)
 {
 	LLChat chat;
@@ -1466,7 +1499,10 @@ bool LLOfferInfo::inventory_offer_callback(const LLSD& notification, const LLSD&
 		gInventory.addObserver(opener);
 	}
 
-	delete this;
+	if(!mPersist)
+	{
+		delete this;
+	}
 	return false;
 }
 
@@ -1632,7 +1668,10 @@ bool LLOfferInfo::inventory_task_offer_callback(const LLSD& notification, const
 		gInventory.addObserver(opener);
 	}
 
-	delete this;
+	if(!mPersist)
+	{
+		delete this;
+	}
 	return false;
 }
 
@@ -1648,6 +1687,15 @@ class LLPostponedOfferNotification: public LLPostponedNotification
 	}
 };
 
+void LLOfferInfo::initRespondFunctionMap()
+{
+	if(mRespondFunctions.empty())
+	{
+		mRespondFunctions["ObjectGiveItem"] = boost::bind(&LLOfferInfo::inventory_task_offer_callback, this, _1, _2);
+		mRespondFunctions["UserGiveItem"] = boost::bind(&LLOfferInfo::inventory_offer_callback, this, _1, _2);
+	}
+}
+
 void inventory_offer_handler(LLOfferInfo* info)
 {
 	//Until throttling is implmented, busy mode should reject inventory instead of silently
@@ -1764,7 +1812,8 @@ void inventory_offer_handler(LLOfferInfo* info)
 		// Inventory Slurls don't currently work for non agent transfers, so only display the object name.
 		args["ITEM_SLURL"] = msg;
 		// Note: sets inventory_task_offer_callback as the callback
-		p.substitutions(args).payload(payload).functor.function(boost::bind(&LLOfferInfo::inventory_task_offer_callback, info, _1, _2));
+		p.substitutions(args).payload(payload).functor.responder(LLNotificationResponderPtr(info));
+		info->mPersist = true;
 		p.name = name_found ? "ObjectGiveItem" : "ObjectGiveItemUnknownUser";
 		// Pop up inv offer chiclet and let the user accept (keep), or reject (and silently delete) the inventory.
 		LLNotifications::instance().add(p);
@@ -1776,7 +1825,8 @@ void inventory_offer_handler(LLOfferInfo* info)
 		// *TODO fix memory leak
 		// inventory_offer_callback() is not invoked if user received notification and 
 		// closes viewer(without responding the notification)
-		p.substitutions(args).payload(payload).functor.function(boost::bind(&LLOfferInfo::inventory_offer_callback, info, _1, _2));
+		p.substitutions(args).payload(payload).functor.responder(LLNotificationResponderPtr(info));
+		info->mPersist = true;
 		p.name = "UserGiveItem";
 		
 		// Prefetch the item into your local inventory.
diff --git a/indra/newview/llviewermessage.h b/indra/newview/llviewermessage.h
index 7c021dc05f246d834bdc6287695ab70eb0efa8da..72ad3c8926085d878fe11afdb7addc2deeb04900 100644
--- a/indra/newview/llviewermessage.h
+++ b/indra/newview/llviewermessage.h
@@ -40,6 +40,7 @@
 #include "lluuid.h"
 #include "message.h"
 #include "stdenums.h"
+#include "llnotifications.h"
 
 //
 // Forward declarations
@@ -210,11 +211,10 @@ bool highlight_offered_item(const LLUUID& item_id);
 
 void set_dad_inventory_item(LLInventoryItem* inv_item, const LLUUID& into_folder_uuid);
 
-struct LLOfferInfo
+class LLOfferInfo : public LLNotificationResponderInterface
 {
-        LLOfferInfo()
-	:	mFromGroup(FALSE), mFromObject(FALSE),
-		mIM(IM_NOTHING_SPECIAL), mType(LLAssetType::AT_NONE) {};
+public:
+	LLOfferInfo();
 	LLOfferInfo(const LLSD& sd);
 
 	LLOfferInfo(const LLOfferInfo& info);
@@ -232,12 +232,27 @@ struct LLOfferInfo
 	std::string mFromName;
 	std::string mDesc;
 	LLHost mHost;
+	bool mPersist;
+
+	// LLNotificationResponderInterface implementation
+	/*virtual*/ LLSD asLLSD();
+	/*virtual*/ void fromLLSD(const LLSD& params);
+	/*virtual*/ void handleRespond(const LLSD& notification, const LLSD& response);
 
-	LLSD asLLSD();
 	void send_auto_receive_response(void);
+
+	// TODO - replace all references with handleRespond()
 	bool inventory_offer_callback(const LLSD& notification, const LLSD& response);
 	bool inventory_task_offer_callback(const LLSD& notification, const LLSD& response);
 
+private:
+
+	void initRespondFunctionMap();
+
+	typedef boost::function<bool (const LLSD&, const LLSD&)> respond_function_t;
+	typedef std::map<std::string, respond_function_t> respond_function_map_t;
+
+	respond_function_map_t mRespondFunctions;
 };
 
 void process_feature_disabled_message(LLMessageSystem* msg, void**);
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 07304eb14e2672a79f97861176c9ca9c5acd71f6..5d184fea3a7e339c39518a8d7b7708f336bcd423 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -5066,6 +5066,7 @@ No valid parcel could be found.
   <notification
    icon="notify.tga"
    name="ObjectGiveItem"
+   persist="true"
    type="offer">
 An object named [OBJECTFROMNAME] owned by [NAME_SLURL] has given you this [OBJECTTYPE]:
 [ITEM_SLURL]
@@ -5110,6 +5111,7 @@ An object named [OBJECTFROMNAME] owned by (an unknown Resident) has given you th
   <notification
    icon="notify.tga"
    name="UserGiveItem"
+   persist="true"
    type="offer">
 [NAME_SLURL] has given you this [OBJECTTYPE]:
 [ITEM_SLURL]
@@ -5162,6 +5164,7 @@ An object named [OBJECTFROMNAME] owned by (an unknown Resident) has given you th
   <notification
    icon="notify.tga"
    name="TeleportOffered"
+   persist="true"
    type="offer">
 [NAME_SLURL] has offered to teleport you to their location:
 
@@ -5207,6 +5210,7 @@ An object named [OBJECTFROMNAME] owned by (an unknown Resident) has given you th
   <notification
    icon="notify.tga"
    name="OfferFriendship"
+   persist="true"
    type="offer">
 [NAME_SLURL] is offering friendship.