diff --git a/autobuild.xml b/autobuild.xml
index fc27c83e1fea43d498b99f7ba881cf88c1fefd4b..d09d862f1b9e715a5862df604800a92106a59296 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -2701,9 +2701,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>2e8d817e7837dd6f4284b13fa3f5c15e</string>
+              <string>9e1b5515ab59b4e9cfeef6626d65d03d</string>
               <key>url</key>
-              <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104765/917714/viewer_manager-3.0.575083-darwin64-575083.tar.bz2</string>
+              <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/108609/945996/viewer_manager-3.0.577252-darwin64-577252.tar.bz2</string>
             </map>
             <key>name</key>
             <string>darwin64</string>
@@ -2713,9 +2713,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>3efa80faaf537e39a77218cd6efa9409</string>
+              <string>a3c599595ecc8fb987a5499fca42520a</string>
               <key>url</key>
-              <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104766/917721/viewer_manager-3.0.575083-windows-575083.tar.bz2</string>
+              <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/108610/946003/viewer_manager-3.0.577252-windows-577252.tar.bz2</string>
             </map>
             <key>name</key>
             <string>windows</string>
@@ -2726,7 +2726,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
         <key>source_type</key>
         <string>hg</string>
         <key>version</key>
-        <string>3.0.575083</string>
+        <string>3.0.577252</string>
       </map>
       <key>vlc-bin</key>
       <map>
diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp
index 23936f0526e5cc5c0050f8839e7b12637653a4a6..97a38ea99253b9ed4cda572523dce6516f8b0e7f 100644
--- a/indra/llcommon/llprocess.cpp
+++ b/indra/llcommon/llprocess.cpp
@@ -272,6 +272,14 @@ class ReadPipeImpl: public LLProcess::ReadPipe
 					boost::bind(&ReadPipeImpl::tick, this, _1));
 	}
 
+    ~ReadPipeImpl()
+    {
+        if (mConnection.connected())
+        {
+            mConnection.disconnect();
+        }
+    }
+
 	// Much of the implementation is simply connecting the abstract virtual
 	// methods with implementation data concealed from the base class.
 	virtual std::istream& get_istream() { return mStream; }
diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py
index 778de909627cc59cfdd1a190e168621ef151ead8..185e8e25c671c1d35cd65b9a1b609251c78f66b1 100755
--- a/indra/llcorehttp/tests/test_llcorehttp_peer.py
+++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py
@@ -38,7 +38,6 @@
 from http.server import HTTPServer, BaseHTTPRequestHandler
 
 
-from llbase.fastest_elementtree import parse as xml_parse
 from llbase import llsd
 
 # we're in llcorehttp/tests ; testrunner.py is found in llmessage/tests
diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp
index 6de99dfbff81953bf427de9a5de847ffcb046762..4b7363c8e5e4f8bda1d137aa11ee134b0a0a19bb 100644
--- a/indra/llfilesystem/lldiskcache.cpp
+++ b/indra/llfilesystem/lldiskcache.cpp
@@ -35,7 +35,6 @@
 #include "llassettype.h"
 #include "lldir.h"
 #include <boost/filesystem.hpp>
-#include <boost/range/iterator_range.hpp>
 #include <chrono>
 
 #include "lldiskcache.h"
@@ -100,19 +99,20 @@ void LLDiskCache::purge()
 #endif
     if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed())
     {
-        for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path, ec), {}))
+        boost::filesystem::directory_iterator iter(cache_path, ec);
+        while (iter != boost::filesystem::directory_iterator() && !ec.failed())
         {
-            if (boost::filesystem::is_regular_file(entry, ec) && !ec.failed())
+            if (boost::filesystem::is_regular_file(*iter, ec) && !ec.failed())
             {
-                if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos)
+                if ((*iter).path().string().find(mCacheFilenamePrefix) != std::string::npos)
                 {
-                    uintmax_t file_size = boost::filesystem::file_size(entry, ec);
+                    uintmax_t file_size = boost::filesystem::file_size(*iter, ec);
                     if (ec.failed())
                     {
                         continue;
                     }
-                    const std::string file_path = entry.path().string();
-                    const std::time_t file_time = boost::filesystem::last_write_time(entry, ec);
+                    const std::string file_path = (*iter).path().string();
+                    const std::time_t file_time = boost::filesystem::last_write_time(*iter, ec);
                     if (ec.failed())
                     {
                         continue;
@@ -121,6 +121,7 @@ void LLDiskCache::purge()
                     file_info.push_back(file_info_t(file_time, { file_size, file_path }));
                 }
             }
+            iter.increment(ec);
         }
     }
 
@@ -348,19 +349,21 @@ void LLDiskCache::clearCache()
 #endif
     if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed())
     {
-        for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path, ec), {}))
+        boost::filesystem::directory_iterator iter(cache_path, ec);
+        while (iter != boost::filesystem::directory_iterator() && !ec.failed())
         {
-            if (boost::filesystem::is_regular_file(entry, ec) && !ec.failed())
+            if (boost::filesystem::is_regular_file(*iter, ec) && !ec.failed())
             {
-                if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos)
+                if ((*iter).path().string().find(mCacheFilenamePrefix) != std::string::npos)
                 {
-                    boost::filesystem::remove(entry, ec);
+                    boost::filesystem::remove(*iter, ec);
                     if (ec.failed())
                     {
-                        LL_WARNS() << "Failed to delete cache file " << entry << ": " << ec.message() << LL_ENDL;
+                        LL_WARNS() << "Failed to delete cache file " << *iter << ": " << ec.message() << LL_ENDL;
                     }
                 }
             }
+            iter.increment(ec);
         }
     }
 }
@@ -379,20 +382,22 @@ void LLDiskCache::removeOldVFSFiles()
 #endif
     if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed())
     {
-        for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path, ec), {}))
+        boost::filesystem::directory_iterator iter(cache_path, ec);
+        while (iter != boost::filesystem::directory_iterator() && !ec.failed())
         {
-            if (boost::filesystem::is_regular_file(entry, ec) && !ec.failed())
+            if (boost::filesystem::is_regular_file(*iter, ec) && !ec.failed())
             {
-                if ((entry.path().string().find(CACHE_FORMAT) != std::string::npos) ||
-                    (entry.path().string().find(DB_FORMAT) != std::string::npos))
+                if (((*iter).path().string().find(CACHE_FORMAT) != std::string::npos) ||
+                    ((*iter).path().string().find(DB_FORMAT) != std::string::npos))
                 {
-                    boost::filesystem::remove(entry, ec);
+                    boost::filesystem::remove(*iter, ec);
                     if (ec.failed())
                     {
-                        LL_WARNS() << "Failed to delete cache file " << entry << ": " << ec.message() << LL_ENDL;
+                        LL_WARNS() << "Failed to delete cache file " << *iter << ": " << ec.message() << LL_ENDL;
                     }
                 }
             }
+            iter.increment(ec);
         }
     }
 }
@@ -418,19 +423,21 @@ uintmax_t LLDiskCache::dirFileSize(const std::string dir)
 #endif
     if (boost::filesystem::is_directory(dir_path, ec) && !ec.failed())
     {
-        for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(dir_path, ec), {}))
+        boost::filesystem::directory_iterator iter(dir_path, ec);
+        while (iter != boost::filesystem::directory_iterator() && !ec.failed())
         {
-            if (boost::filesystem::is_regular_file(entry, ec) && !ec.failed())
+            if (boost::filesystem::is_regular_file(*iter, ec) && !ec.failed())
             {
-                if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos)
+                if ((*iter).path().string().find(mCacheFilenamePrefix) != std::string::npos)
                 {
-                    uintmax_t file_size = boost::filesystem::file_size(entry, ec);
+                    uintmax_t file_size = boost::filesystem::file_size(*iter, ec);
                     if (!ec.failed())
                     {
                         total_file_size += file_size;
                     }
                 }
             }
+            iter.increment(ec);
         }
     }
 
diff --git a/indra/llmessage/llregionflags.h b/indra/llmessage/llregionflags.h
index c13f39df9bda93e0cc263e72e1e3cc24252840d5..8548ed51e74b6cd455c189dc6a9bb8371bd94a62 100644
--- a/indra/llmessage/llregionflags.h
+++ b/indra/llmessage/llregionflags.h
@@ -89,6 +89,8 @@ const U64 REGION_FLAGS_ALLOW_VOICE = (1 << 28);
 const U64 REGION_FLAGS_BLOCK_PARCEL_SEARCH = (1 << 29);
 const U64 REGION_FLAGS_DENY_AGEUNVERIFIED	= (1 << 30);
 
+const U64 REGION_FLAGS_DENY_BOTS = (1 << 31);
+
 const U64 REGION_FLAGS_DEFAULT = REGION_FLAGS_ALLOW_LANDMARK |
 								 REGION_FLAGS_ALLOW_SET_HOME |
                                  REGION_FLAGS_ALLOW_PARCEL_CHANGES |
diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp
index 1fbbad06d478ca19d9cd7d27150bcba76bb0bf83..756d0b5db8e171aff785eb619cc43ccd82ecb055 100644
--- a/indra/llplugin/llpluginprocessparent.cpp
+++ b/indra/llplugin/llpluginprocessparent.cpp
@@ -162,6 +162,11 @@ LLPluginProcessParent::~LLPluginProcessParent()
     {   // If we are quitting, the network sockets will already have been destroyed.
         killSockets();
     }
+
+    if (mPolling.connected())
+    {
+        mPolling.disconnect();
+    }
 }
 
 /*static*/
diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index d413fab2700206e3a24c6f36f7bcfee1c30c9bae..2303cd24b7c8e5bf1ad35caded2ab11316c90ac4 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -759,11 +759,13 @@ void LLFloater::closeFloater(bool app_quitting)
         }
 
 		// now close dependent floater
-		for(handle_set_iter_t dependent_it = mDependents.begin();
-			dependent_it != mDependents.end(); )
+		while(mDependents.size() > 0)
 		{
+            handle_set_iter_t dependent_it = mDependents.begin();
 			LLFloater* floaterp = dependent_it->get();
-            dependent_it = mDependents.erase(dependent_it);
+            // normally removeDependentFloater will do this, but in
+            // case floaterp is somehow invalid or orphaned, erase now
+            mDependents.erase(dependent_it);
             if (floaterp)
             {
                 floaterp->mDependeeHandle = LLHandle<LLFloater>();
diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp
index 1547a4ba5c89aa0c0df716e48720989f5e08451f..6a9070634cdd6c2c73effffe75114c2ef84e43ea 100644
--- a/indra/llui/llurlentry.cpp
+++ b/indra/llui/llurlentry.cpp
@@ -206,6 +206,7 @@ bool LLUrlEntryBase::isWikiLinkCorrect(const std::string &labeled_url) const
     {
         return (chr == L'\u02D0') // "Modifier Letter Colon"
             || (chr == L'\uFF1A') // "Fullwidth Colon"
+            || (chr == L'\u2236') // "Ratio"
             || (chr == L'\uFE55'); // "Small Colon"
     },
         L'\u003A'); // Colon
diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp
index c9d7013a11ac5dba1887d0d66cbc9f64c17ca1e4..23f3dca3fbdfc5b9262efac792c36d5a89a6a2c4 100644
--- a/indra/llui/llurlregistry.cpp
+++ b/indra/llui/llurlregistry.cpp
@@ -169,7 +169,7 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL
 	for (it = mUrlEntry.begin(); it != mUrlEntry.end(); ++it)
 	{
 		//Skip for url entry icon if content is not trusted
-		if(!is_content_trusted && (mUrlEntryIcon == *it))
+		if((mUrlEntryIcon == *it) && ((text.find("Hand") != std::string::npos) || !is_content_trusted))
 		{
 			continue;
 		}
diff --git a/indra/llwindow/llwindowmacosx-objc.mm b/indra/llwindow/llwindowmacosx-objc.mm
index 57c3d862956ec5dd483ec06c66765270f08139b5..690fe058dbf56e9838d39a2b29840e7429c6b8a0 100644
--- a/indra/llwindow/llwindowmacosx-objc.mm
+++ b/indra/llwindow/llwindowmacosx-objc.mm
@@ -215,6 +215,7 @@ NSWindowRef createNSWindow(int x, int y, int width, int height)
 													  styleMask:NSTitledWindowMask | NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSTexturedBackgroundWindowMask backing:NSBackingStoreBuffered defer:NO];
 	[window makeKeyAndOrderFront:nil];
 	[window setAcceptsMouseMovedEvents:TRUE];
+    [window setRestorable:FALSE]; // Viewer manages state from own settings
 	return window;
 }
 
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index 1d6084f4e4011bb8cd9026524932bffb02dda1a5..2e560ddb0a6ae8727deb497cd6d16dae3111c076 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -3905,42 +3905,48 @@ void LLWindowWin32::allowLanguageTextInput(LLPreeditor *preeditor, BOOL b)
 
 	sLanguageTextInputAllowed = b;
 
-	if ( sLanguageTextInputAllowed )
-	{
-		// Allowing: Restore the previous IME status, so that the user has a feeling that the previous 
-		// text input continues naturally.  Be careful, however, the IME status is meaningful only during the user keeps 
-		// using same Input Locale (aka Keyboard Layout).
-		if (sWinIMEOpened && GetKeyboardLayout(0) == sWinInputLocale)
-		{
-			HIMC himc = LLWinImm::getContext(mWindowHandle);
-			LLWinImm::setOpenStatus(himc, TRUE);
-			LLWinImm::setConversionStatus(himc, sWinIMEConversionMode, sWinIMESentenceMode);
-			LLWinImm::releaseContext(mWindowHandle, himc);
-		}
-	}
-	else
-	{
-		// Disallowing: Turn off the IME so that succeeding key events bypass IME and come to us directly.
-		// However, do it after saving the current IME  status.  We need to restore the status when
-		//   allowing language text input again.
-		sWinInputLocale = GetKeyboardLayout(0);
-		sWinIMEOpened = LLWinImm::isIME(sWinInputLocale);
-		if (sWinIMEOpened)
-		{
-			HIMC himc = LLWinImm::getContext(mWindowHandle);
-			sWinIMEOpened = LLWinImm::getOpenStatus(himc);
-			if (sWinIMEOpened)
-			{
-				LLWinImm::getConversionStatus(himc, &sWinIMEConversionMode, &sWinIMESentenceMode);
+    if (sLanguageTextInputAllowed)
+    {
+        mWindowThread->post([=]()
+        {
+            // Allowing: Restore the previous IME status, so that the user has a feeling that the previous 
+            // text input continues naturally.  Be careful, however, the IME status is meaningful only during the user keeps 
+            // using same Input Locale (aka Keyboard Layout).
+            if (sWinIMEOpened && GetKeyboardLayout(0) == sWinInputLocale)
+            {
+                HIMC himc = LLWinImm::getContext(mWindowHandle);
+                LLWinImm::setOpenStatus(himc, TRUE);
+                LLWinImm::setConversionStatus(himc, sWinIMEConversionMode, sWinIMESentenceMode);
+                LLWinImm::releaseContext(mWindowHandle, himc);
+            }
+        });
+    }
+    else
+    {
+        mWindowThread->post([=]()
+        {
+            // Disallowing: Turn off the IME so that succeeding key events bypass IME and come to us directly.
+            // However, do it after saving the current IME  status.  We need to restore the status when
+            //   allowing language text input again.
+            sWinInputLocale = GetKeyboardLayout(0);
+            sWinIMEOpened = LLWinImm::isIME(sWinInputLocale);
+            if (sWinIMEOpened)
+            {
+                HIMC himc = LLWinImm::getContext(mWindowHandle);
+                sWinIMEOpened = LLWinImm::getOpenStatus(himc);
+                if (sWinIMEOpened)
+                {
+                    LLWinImm::getConversionStatus(himc, &sWinIMEConversionMode, &sWinIMESentenceMode);
 
-				// We need both ImmSetConversionStatus and ImmSetOpenStatus here to surely disable IME's 
-				// keyboard hooking, because Some IME reacts only on the former and some other on the latter...
-				LLWinImm::setConversionStatus(himc, IME_CMODE_NOCONVERSION, sWinIMESentenceMode);
-				LLWinImm::setOpenStatus(himc, FALSE);
-			}
-			LLWinImm::releaseContext(mWindowHandle, himc);
- 		}
-	}
+                    // We need both ImmSetConversionStatus and ImmSetOpenStatus here to surely disable IME's 
+                    // keyboard hooking, because Some IME reacts only on the former and some other on the latter...
+                    LLWinImm::setConversionStatus(himc, IME_CMODE_NOCONVERSION, sWinIMESentenceMode);
+                    LLWinImm::setOpenStatus(himc, FALSE);
+                }
+                LLWinImm::releaseContext(mWindowHandle, himc);
+            }
+        });
+    }
 }
 
 void LLWindowWin32::fillCandidateForm(const LLCoordGL& caret, const LLRect& bounds, 
@@ -4137,6 +4143,10 @@ void LLWindowWin32::handleStartCompositionMessage()
 
 void LLWindowWin32::handleCompositionMessage(const U32 indexes)
 {
+    if (!mPreeditor)
+    {
+        return;
+    }
 	BOOL needs_update = FALSE;
 	LLWString result_string;
 	LLWString preedit_string;
@@ -4435,7 +4445,7 @@ BOOL LLWindowWin32::handleImeRequests(WPARAM request, LPARAM param, LRESULT *res
 				LLWString context = find_context(wtext, preedit, preedit_length, &context_offset);
 				preedit -= context_offset;
 				preedit_length = llmin(preedit_length, (S32)context.length() - preedit);
-				if (preedit_length && preedit >= 0)
+				if (preedit_length > 0 && preedit >= 0)
 				{
 					// IMR_DOCUMENTFEED may be called when we have an active preedit.
 					// We should pass the context string *excluding* the preedit string.
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index 0673dfa857a900ad047072c8e4d022791164d3aa..8a6857721315b5b0c7971894276125d6ae6ce348 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-6.6.10
+6.6.11
diff --git a/indra/newview/app_settings/settings_per_account.xml b/indra/newview/app_settings/settings_per_account.xml
index 537744b44c0c7e987f7f61588e18cb80290ab77e..e89d1cbda4f62c4b9450319630aed29ba781c0f5 100644
--- a/indra/newview/app_settings/settings_per_account.xml
+++ b/indra/newview/app_settings/settings_per_account.xml
@@ -220,6 +220,17 @@
         <key>Value</key>
             <integer>1</integer>
         </map>
+    <key>FetchGroupChatHistory</key>
+      <map>
+        <key>Comment</key>
+        <string>Fetch recent messages from group chat servers when a group window opens</string>
+        <key>Persist</key>
+        <integer>1</integer>
+        <key>Type</key>
+        <string>Boolean</string>
+        <key>Value</key>
+        <integer>1</integer>
+      </map>
     <key>VoiceCallsFriendsOnly</key>
     <map>
         <key>Comment</key>
diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp
index 8d2e3905d1f50fcb2e5a7d027ad7251f0937e49a..77131efd754009ff0efccdfd362f9b143e62923a 100644
--- a/indra/newview/llagentcamera.cpp
+++ b/indra/newview/llagentcamera.cpp
@@ -133,7 +133,6 @@ LLAgentCamera::LLAgentCamera() :
 	mCameraFOVZoomFactor(0.f),
 	mCameraCurrentFOVZoomFactor(0.f),
 	mCameraFocusOffset(),
-	mCameraFOVDefault(DEFAULT_FIELD_OF_VIEW),
 
 	mCameraCollidePlane(),
 
@@ -155,7 +154,6 @@ LLAgentCamera::LLAgentCamera() :
 	mFocusObject(NULL),
 	mFocusObjectDist(0.f),
 	mFocusObjectOffset(),
-	mFocusDotRadius( 0.1f ),			// meters
 	mTrackFocusObject(TRUE),
 
 	mAtKey(0), // Either 1, 0, or -1... indicates that movement-key is pressed
@@ -2361,6 +2359,11 @@ void LLAgentCamera::changeCameraToCustomizeAvatar()
 	gAgent.standUp(); // force stand up
 	gViewerWindow->getWindow()->resetBusyCount();
 
+    if (LLSelectMgr::getInstance()->getSelection()->isAttachment())
+    {
+        LLSelectMgr::getInstance()->deselectAll();
+    }
+
 	if (gFaceEditToolset)
 	{
 		LLToolMgr::getInstance()->setCurrentToolset(gFaceEditToolset);
diff --git a/indra/newview/llagentcamera.h b/indra/newview/llagentcamera.h
index 89680f95dc28982ffadcb0c26fcf05b54e1886a1..d27cdb0c5ca03e54c163f69f0dc1de15da6de763 100644
--- a/indra/newview/llagentcamera.h
+++ b/indra/newview/llagentcamera.h
@@ -152,7 +152,6 @@ class LLAgentCamera
 	F32				mTargetCameraDistance;			// Target camera offset from avatar
 	F32				mCameraFOVZoomFactor;			// Amount of fov zoom applied to camera when zeroing in on an object
 	F32				mCameraCurrentFOVZoomFactor;	// Interpolated fov zoom
-	F32				mCameraFOVDefault;				// Default field of view that is basis for FOV zoom effect
 	LLVector4		mCameraCollidePlane;			// Colliding plane for camera
 	F32				mCameraZoomFraction;			// Mousewheel driven fraction of zoom
 	LLVector3		mCameraPositionAgent;			// Camera position in agent coordinates
@@ -167,7 +166,6 @@ class LLAgentCamera
 	// Follow
 	//--------------------------------------------------------------------
 public:
-	void			setUsingFollowCam(bool using_follow_cam);
 	bool 			isfollowCamLocked();
 private:
 	LLFollowCam 	mFollowCam; 			// Ventrella
@@ -233,7 +231,6 @@ class LLAgentCamera
 	LLPointer<LLViewerObject> mFocusObject;
 	F32				mFocusObjectDist;
 	LLVector3		mFocusObjectOffset;
-	F32				mFocusDotRadius; 				// Meters
 	BOOL			mTrackFocusObject;
 	
 	//--------------------------------------------------------------------
diff --git a/indra/newview/llagentui.cpp b/indra/newview/llagentui.cpp
index 3410a37890505052f86db52601015e878f6f469c..c19ad2ae6f9e8158ff4147408a339dcc09a418d2 100644
--- a/indra/newview/llagentui.cpp
+++ b/indra/newview/llagentui.cpp
@@ -53,7 +53,16 @@ void LLAgentUI::buildSLURL(LLSLURL& slurl, const bool escaped /*= true*/)
       LLViewerRegion *regionp = gAgent.getRegion();
       if (regionp)
       {
-		  return_slurl = LLSLURL(regionp->getName(), gAgent.getPositionGlobal());
+          // Make sure coordinates are within current region
+          LLVector3d global_pos = gAgent.getPositionGlobal();
+          LLVector3d region_origin = regionp->getOriginGlobal();
+          // -1 otherwise slurl will fmod 256 to 0.
+          // And valid slurl range is supposed to be 0..255
+          F64 max_val = REGION_WIDTH_METERS - 1;
+          global_pos.mdV[VX] = llclamp(global_pos[VX], region_origin[VX], region_origin[VX] + max_val);
+          global_pos.mdV[VY] = llclamp(global_pos[VY], region_origin[VY], region_origin[VY] + max_val);
+
+          return_slurl = LLSLURL(regionp->getName(), global_pos);
       }
 	slurl = return_slurl;
 }
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 1df88fc248b9255b26d52373e251e0cc7da5233a..8d77a39c743448d18db7ec75594b0e479fdb12a4 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -5483,7 +5483,8 @@ void LLAppViewer::disconnectViewer()
     {
         gInventory.cache(gInventory.getRootFolderID(), gAgent.getID());
         if (gInventory.getLibraryRootFolderID().notNull()
-            && gInventory.getLibraryOwnerID().notNull())
+            && gInventory.getLibraryOwnerID().notNull()
+            && !mSecondInstance) // agent is unique, library isn't
         {
             gInventory.cache(
                 gInventory.getLibraryRootFolderID(),
diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp
index 11e03688a75ae4718bd6e39787cf592272fc4627..cc454417c646ece744395ec2990ab5c47d5059dc 100644
--- a/indra/newview/lldrawpoolbump.cpp
+++ b/indra/newview/lldrawpoolbump.cpp
@@ -79,7 +79,9 @@ static S32 cube_channel = -1;
 static S32 diffuse_channel = -1;
 static S32 bump_channel = -1;
 
-#define LL_BUMPLIST_MULTITHREADED 0 // TODO -- figure out why this doesn't work
+// Enabled after changing LLViewerTexture::mNeedsCreateTexture to an
+// LLAtomicBool; this should work just fine, now. HB
+#define LL_BUMPLIST_MULTITHREADED 1
 
 
 // static 
diff --git a/indra/newview/llestateinfomodel.cpp b/indra/newview/llestateinfomodel.cpp
index 4fdb86059286b13d4b12ec5fc665182b40b0dd4d..5be56660d65fb9a093d717a85ee3bb6c43f8e07e 100644
--- a/indra/newview/llestateinfomodel.cpp
+++ b/indra/newview/llestateinfomodel.cpp
@@ -74,6 +74,7 @@ bool LLEstateInfoModel::getDenyAgeUnverified()		const {	return getFlag(REGION_FL
 bool LLEstateInfoModel::getAllowVoiceChat()			const { return getFlag(REGION_FLAGS_ALLOW_VOICE); }
 bool LLEstateInfoModel::getAllowAccessOverride()	const { return getFlag(REGION_FLAGS_ALLOW_ACCESS_OVERRIDE); }
 bool LLEstateInfoModel::getAllowEnvironmentOverride() const { return getFlag(REGION_FLAGS_ALLOW_ENVIRONMENT_OVERRIDE); }
+bool LLEstateInfoModel::getDenyScriptedAgents()     const { return getFlag(REGION_FLAGS_DENY_BOTS); }
 
 void LLEstateInfoModel::setUseFixedSun(bool val)			{ setFlag(REGION_FLAGS_SUN_FIXED, 				val);	}
 void LLEstateInfoModel::setIsExternallyVisible(bool val)	{ setFlag(REGION_FLAGS_EXTERNALLY_VISIBLE,		val);	}
@@ -83,6 +84,7 @@ void LLEstateInfoModel::setDenyAgeUnverified(bool val)		{ setFlag(REGION_FLAGS_D
 void LLEstateInfoModel::setAllowVoiceChat(bool val)		    { setFlag(REGION_FLAGS_ALLOW_VOICE,				val);	}
 void LLEstateInfoModel::setAllowAccessOverride(bool val)    { setFlag(REGION_FLAGS_ALLOW_ACCESS_OVERRIDE,   val);   }
 void LLEstateInfoModel::setAllowEnvironmentOverride(bool val) { setFlag(REGION_FLAGS_ALLOW_ENVIRONMENT_OVERRIDE, val); }
+void LLEstateInfoModel::setDenyScriptedAgents(bool val)     { setFlag(REGION_FLAGS_DENY_BOTS, val); }
 
 void LLEstateInfoModel::update(const strings_t& strings)
 {
@@ -148,6 +150,7 @@ void LLEstateInfoModel::commitEstateInfoCapsCoro(std::string url)
     body["allow_direct_teleport"] = getAllowDirectTeleport();
     body["deny_anonymous"] = getDenyAnonymous();
     body["deny_age_unverified"] = getDenyAgeUnverified();
+    body["block_bots"] = getDenyScriptedAgents();
     body["allow_voice_chat"] = getAllowVoiceChat();
     body["override_public_access"] = getAllowAccessOverride();
 
@@ -222,6 +225,7 @@ std::string LLEstateInfoModel::getInfoDump()
 	dump["allow_direct_teleport"] = getAllowDirectTeleport();
 	dump["deny_anonymous"       ] = getDenyAnonymous();
 	dump["deny_age_unverified"  ] = getDenyAgeUnverified();
+    dump["block_bots"           ] = getDenyScriptedAgents();
 	dump["allow_voice_chat"     ] = getAllowVoiceChat();
     dump["override_public_access"] = getAllowAccessOverride();
     dump["override_environment"] = getAllowEnvironmentOverride();
diff --git a/indra/newview/llestateinfomodel.h b/indra/newview/llestateinfomodel.h
index d6f00c573cf11c619d9ee62917c6bde60d31938e..cfe91a2930976ffd7fe915aef048ce1f50b36877 100644
--- a/indra/newview/llestateinfomodel.h
+++ b/indra/newview/llestateinfomodel.h
@@ -57,6 +57,7 @@ class LLEstateInfoModel : public LLSingleton<LLEstateInfoModel>
 	bool				getAllowVoiceChat()			const;
     bool                getAllowAccessOverride()    const;
     bool                getAllowEnvironmentOverride() const;
+    bool                getDenyScriptedAgents()     const;
 
 	const std::string&	getName()					const { return mName; }
 	const LLUUID&		getOwnerID()				const { return mOwnerID; }
@@ -72,6 +73,7 @@ class LLEstateInfoModel : public LLSingleton<LLEstateInfoModel>
 	void setAllowVoiceChat(bool val);
     void setAllowAccessOverride(bool val);
     void setAllowEnvironmentOverride(bool val);
+    void setDenyScriptedAgents(bool val);
 
 	void setSunHour(F32 sun_hour) { mSunHour = sun_hour; }
 
diff --git a/indra/newview/llfloateravatarpicker.cpp b/indra/newview/llfloateravatarpicker.cpp
index 0186c4aebefa2dea030c0d903de74a928bc164ee..2422596f60ae1312bc55bda66cc281b1ae2e2db0 100644
--- a/indra/newview/llfloateravatarpicker.cpp
+++ b/indra/newview/llfloateravatarpicker.cpp
@@ -428,13 +428,18 @@ void LLFloaterAvatarPicker::findCoro(std::string url, LLUUID queryID, std::strin
 
     if (status || (status == LLCore::HttpStatus(HTTP_BAD_REQUEST)))
     {
-        LLFloaterAvatarPicker* floater =
-            LLFloaterReg::findTypedInstance<LLFloaterAvatarPicker>("avatar_picker", name);
-        if (floater)
-        {
-            result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
-            floater->processResponse(queryID, result);
-        }
+        result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
+    }
+    else
+    {
+        result["failure_reason"] = status.toString();
+    }
+
+    LLFloaterAvatarPicker* floater =
+        LLFloaterReg::findTypedInstance<LLFloaterAvatarPicker>("avatar_picker", name);
+    if (floater)
+    {
+        floater->processResponse(queryID, result);
     }
 }
 
@@ -672,59 +677,67 @@ void LLFloaterAvatarPicker::processResponse(const LLUUID& query_id, const LLSD&
 	{
 		LLScrollListCtrl* search_results = getChild<LLScrollListCtrl>("SearchResults");
 
-		LLSD agents = content["agents"];
-
-		// clear "Searching" label on first results
-		search_results->deleteAllItems();
-
-		LLSD item;
-		LLSD::array_const_iterator it = agents.beginArray();
-		for ( ; it != agents.endArray(); ++it)
-		{
-			const LLSD& row = *it;
-			if (row["id"].asUUID() != gAgent.getID() || !mExcludeAgentFromSearchResults)
-			{
-				item["id"] = row["id"];
-				LLSD& columns = item["columns"];
-				columns[0]["column"] = "name";
-				columns[0]["value"] = row["display_name"];
-				columns[1]["column"] = "username";
-				columns[1]["value"] = row["username"];
-				search_results->addElement(item);
-
-				// add the avatar name to our list
-				LLAvatarName avatar_name;
-				avatar_name.fromLLSD(row);
-				sAvatarNameMap[row["id"].asUUID()] = avatar_name;
-			}
-		}
+        // clear "Searching" label on first results
+        search_results->deleteAllItems();
 
-		if (search_results->isEmpty())
-		{
-			std::string name = "'" + getChild<LLUICtrl>("Edit")->getValue().asString() + "'";
-			LLSD item;
-			item["id"] = LLUUID::null;
-			item["columns"][0]["column"] = "name";
-			item["columns"][0]["value"] = name;
-			item["columns"][1]["column"] = "username";
-			item["columns"][1]["value"] = getString("not_found_text");
-			search_results->addElement(item);
-			search_results->setEnabled(false);
-			getChildView("ok_btn")->setEnabled(false);
-		}
-		else
-		{
-			getChildView("ok_btn")->setEnabled(true);
-			search_results->setEnabled(true);
-			search_results->sortByColumnIndex(1, TRUE);
-			std::string text = getChild<LLUICtrl>("Edit")->getValue().asString();
-			if (!search_results->selectItemByLabel(text, TRUE, 1))
-			{
-				search_results->selectFirstItem();
-			}			
-			onList();
-			search_results->setFocus(TRUE);
-		}
+        if (content.has("failure_reason"))
+        {
+            getChild<LLScrollListCtrl>("SearchResults")->setCommentText(content["failure_reason"].asString());
+            getChildView("ok_btn")->setEnabled(false);
+        }
+        else
+        {
+            LLSD agents = content["agents"];
+
+            LLSD item;
+            LLSD::array_const_iterator it = agents.beginArray();
+            for (; it != agents.endArray(); ++it)
+            {
+                const LLSD& row = *it;
+                if (row["id"].asUUID() != gAgent.getID() || !mExcludeAgentFromSearchResults)
+                {
+                    item["id"] = row["id"];
+                    LLSD& columns = item["columns"];
+                    columns[0]["column"] = "name";
+                    columns[0]["value"] = row["display_name"];
+                    columns[1]["column"] = "username";
+                    columns[1]["value"] = row["username"];
+                    search_results->addElement(item);
+
+                    // add the avatar name to our list
+                    LLAvatarName avatar_name;
+                    avatar_name.fromLLSD(row);
+                    sAvatarNameMap[row["id"].asUUID()] = avatar_name;
+                }
+            }
+
+            if (search_results->isEmpty())
+            {
+                std::string name = "'" + getChild<LLUICtrl>("Edit")->getValue().asString() + "'";
+                LLSD item;
+                item["id"] = LLUUID::null;
+                item["columns"][0]["column"] = "name";
+                item["columns"][0]["value"] = name;
+                item["columns"][1]["column"] = "username";
+                item["columns"][1]["value"] = getString("not_found_text");
+                search_results->addElement(item);
+                search_results->setEnabled(false);
+                getChildView("ok_btn")->setEnabled(false);
+            }
+            else
+            {
+                getChildView("ok_btn")->setEnabled(true);
+                search_results->setEnabled(true);
+                search_results->sortByColumnIndex(1, TRUE);
+                std::string text = getChild<LLUICtrl>("Edit")->getValue().asString();
+                if (!search_results->selectItemByLabel(text, TRUE, 1))
+                {
+                    search_results->selectFirstItem();
+                }
+                onList();
+                search_results->setFocus(TRUE);
+            }
+        }
 	}
 }
 
diff --git a/indra/newview/llfloaterdisplayname.cpp b/indra/newview/llfloaterdisplayname.cpp
index 3b0c67415a35883eb8fe01973d01f5a061836842..19bc865d8b92635bda1afa4cbc16a13e69b9ddf4 100644
--- a/indra/newview/llfloaterdisplayname.cpp
+++ b/indra/newview/llfloaterdisplayname.cpp
@@ -47,6 +47,7 @@ class LLFloaterDisplayName : public LLFloater
 	virtual ~LLFloaterDisplayName() { }
 	/*virtual*/	BOOL	postBuild();
 	void onSave();
+	void onReset();
 	void onCancel();
 	/*virtual*/ void onOpen(const LLSD& key);
 	
@@ -101,6 +102,7 @@ void LLFloaterDisplayName::onOpen(const LLSD& key)
 
 BOOL LLFloaterDisplayName::postBuild()
 {
+	getChild<LLUICtrl>("reset_btn")->setCommitCallback(boost::bind(&LLFloaterDisplayName::onReset, this));	
 	getChild<LLUICtrl>("cancel_btn")->setCommitCallback(boost::bind(&LLFloaterDisplayName::onCancel, this));	
 	getChild<LLUICtrl>("save_btn")->setCommitCallback(boost::bind(&LLFloaterDisplayName::onSave, this));	
 	
@@ -156,6 +158,20 @@ void LLFloaterDisplayName::onCancel()
 	setVisible(false);
 }
 
+void LLFloaterDisplayName::onReset()
+{
+    LLAvatarName av_name;
+    if (!LLAvatarNameCache::get(gAgent.getID(), &av_name))
+    {
+        return;
+    }
+    getChild<LLUICtrl>("display_name_editor")->setValue(av_name.getCompleteName());
+
+    getChild<LLUICtrl>("display_name_confirm")->clear();
+    getChild<LLUICtrl>("display_name_confirm")->setFocus(TRUE);
+}
+
+
 void LLFloaterDisplayName::onSave()
 {
 	std::string display_name_utf8 = getChild<LLUICtrl>("display_name_editor")->getValue().asString();
diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp
index 64f84cbe4fc50536e85cff83e6527f91f29dbdcd..da7a4733c7dab78f244349c905fdef9ca8c5494c 100644
--- a/indra/newview/llfloaterregioninfo.cpp
+++ b/indra/newview/llfloaterregioninfo.cpp
@@ -1880,6 +1880,7 @@ BOOL LLPanelEstateInfo::postBuild()
 	initCtrl("allow_direct_teleport");
 	initCtrl("limit_payment");
 	initCtrl("limit_age_verified");
+    initCtrl("limit_bots");
 	initCtrl("voice_chat_check");
     initCtrl("parcel_access_override");
 
@@ -1903,12 +1904,14 @@ void LLPanelEstateInfo::refresh()
 	getChildView("Only Allow")->setEnabled(public_access);
 	getChildView("limit_payment")->setEnabled(public_access);
 	getChildView("limit_age_verified")->setEnabled(public_access);
+    getChildView("limit_bots")->setEnabled(public_access);
 
 	// if this is set to false, then the limit fields are meaningless and should be turned off
 	if (public_access == false)
 	{
 		getChild<LLUICtrl>("limit_payment")->setValue(false);
 		getChild<LLUICtrl>("limit_age_verified")->setValue(false);
+        getChild<LLUICtrl>("limit_bots")->setValue(false);
 	}
 }
 
@@ -1925,6 +1928,7 @@ void LLPanelEstateInfo::refreshFromEstate()
 	getChild<LLUICtrl>("limit_payment")->setValue(estate_info.getDenyAnonymous());
 	getChild<LLUICtrl>("limit_age_verified")->setValue(estate_info.getDenyAgeUnverified());
     getChild<LLUICtrl>("parcel_access_override")->setValue(estate_info.getAllowAccessOverride());
+    getChild<LLUICtrl>("limit_bots")->setValue(estate_info.getDenyScriptedAgents());
 
 	// Ensure appriopriate state of the management UI
 	updateControls(gAgent.getRegion());
@@ -1968,6 +1972,7 @@ bool LLPanelEstateInfo::callbackChangeLindenEstate(const LLSD& notification, con
 			estate_info.setDenyAgeUnverified(getChild<LLUICtrl>("limit_age_verified")->getValue().asBoolean());
 			estate_info.setAllowVoiceChat(getChild<LLUICtrl>("voice_chat_check")->getValue().asBoolean());
             estate_info.setAllowAccessOverride(getChild<LLUICtrl>("parcel_access_override")->getValue().asBoolean());
+            estate_info.setDenyScriptedAgents(getChild<LLUICtrl>("limit_bots")->getValue().asBoolean());
             // JIGGLYPUFF
             //estate_info.setAllowAccessOverride(getChild<LLUICtrl>("")->getValue().asBoolean());
 			// send the update to sim
diff --git a/indra/newview/llhudnametag.cpp b/indra/newview/llhudnametag.cpp
index 952fbf8e4b109e4793147ed4ad1611a89ca41d4b..ab6a64157c8ba78e0e11ff52ddb3408e734192ee 100644
--- a/indra/newview/llhudnametag.cpp
+++ b/indra/newview/llhudnametag.cpp
@@ -56,7 +56,6 @@ const F32 HORIZONTAL_PADDING = 16.f;
 const F32 VERTICAL_PADDING = 12.f;
 const F32 LINE_PADDING = 3.f;			// aka "leading"
 const F32 BUFFER_SIZE = 2.f;
-const F32 HUD_TEXT_MAX_WIDTH = 190.f;
 const S32 NUM_OVERLAP_ITERATIONS = 10;
 const F32 POSITION_DAMPING_TC = 0.2f;
 const F32 MAX_STABLE_CAMERA_VELOCITY = 0.1f;
@@ -67,6 +66,8 @@ const F32 LOD_2_SCREEN_COVERAGE = 0.40f;
 std::set<LLPointer<LLHUDNameTag> > LLHUDNameTag::sTextObjects;
 std::vector<LLPointer<LLHUDNameTag> > LLHUDNameTag::sVisibleTextObjects;
 BOOL LLHUDNameTag::sDisplayText = TRUE ;
+const F32 LLHUDNameTag::NAMETAG_MAX_WIDTH = 298.f;
+const F32 LLHUDNameTag::HUD_TEXT_MAX_WIDTH = 190.f;
 
 bool llhudnametag_further_away::operator()(const LLPointer<LLHUDNameTag>& lhs, const LLPointer<LLHUDNameTag>& rhs) const
 {
@@ -414,7 +415,8 @@ void LLHUDNameTag::addLine(const std::string &text_utf8,
 						const LLColor4& color,
 						const LLFontGL::StyleFlags style,
 						const LLFontGL* font,
-						const bool use_ellipses)
+						const bool use_ellipses,
+						F32 max_pixels)
 {
 	LLWString wline = utf8str_to_wstring(text_utf8);
 	if (!wline.empty())
@@ -431,7 +433,7 @@ void LLHUDNameTag::addLine(const std::string &text_utf8,
 		tokenizer tokens(wline, sep);
 		tokenizer::iterator iter = tokens.begin();
 
-        const F32 max_pixels = HUD_TEXT_MAX_WIDTH;
+        max_pixels = llmin(max_pixels, NAMETAG_MAX_WIDTH);
         while (iter != tokens.end())
         {
             U32 line_length = 0;
@@ -488,7 +490,7 @@ void LLHUDNameTag::setLabel(const std::string &label_utf8)
 	addLabel(label_utf8);
 }
 
-void LLHUDNameTag::addLabel(const std::string& label_utf8)
+void LLHUDNameTag::addLabel(const std::string& label_utf8, F32 max_pixels)
 {
 	LLWString wstr = utf8string_to_wstring(label_utf8);
 	if (!wstr.empty())
@@ -502,13 +504,15 @@ void LLHUDNameTag::addLabel(const std::string& label_utf8)
 		tokenizer tokens(wstr, sep);
 		tokenizer::iterator iter = tokens.begin();
 
+        max_pixels = llmin(max_pixels, NAMETAG_MAX_WIDTH);
+
 		while (iter != tokens.end())
 		{
 			U32 line_length = 0;
 			do	
 			{
 				S32 segment_length = mFontp->maxDrawableChars(iter->substr(line_length).c_str(), 
-					HUD_TEXT_MAX_WIDTH, wstr.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE);
+                    max_pixels, wstr.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE);
 				LLHUDTextSegment segment(iter->substr(line_length, segment_length), LLFontGL::NORMAL, mColor, mFontp);
 				mLabelSegments.push_back(segment);
 				line_length += segment_length;
@@ -695,7 +699,7 @@ void LLHUDNameTag::updateSize()
 		const LLFontGL* fontp = iter->mFont;
 		height += fontp->getLineHeight();
 		height += LINE_PADDING;
-		width = llmax(width, llmin(iter->getWidth(fontp), HUD_TEXT_MAX_WIDTH));
+		width = llmax(width, llmin(iter->getWidth(fontp), NAMETAG_MAX_WIDTH));
 		++iter;
 	}
 
@@ -709,7 +713,7 @@ void LLHUDNameTag::updateSize()
 	while (iter != mLabelSegments.end())
 	{
 		height += mFontp->getLineHeight();
-		width = llmax(width, llmin(iter->getWidth(mFontp), HUD_TEXT_MAX_WIDTH));
+		width = llmax(width, llmin(iter->getWidth(mFontp), NAMETAG_MAX_WIDTH));
 		++iter;
 	}
 	
diff --git a/indra/newview/llhudnametag.h b/indra/newview/llhudnametag.h
index 7577dd5de66b6946d756d8c1ed838b32209f6306..361e4d4f4b50598afa0203ae452f038f786685e0 100644
--- a/indra/newview/llhudnametag.h
+++ b/indra/newview/llhudnametag.h
@@ -85,6 +85,9 @@ class LLHUDNameTag : public LLHUDObject
 		ALIGN_VERT_CENTER
 	} EVertAlignment;
 
+    static const F32 NAMETAG_MAX_WIDTH; // 298px, made to fit 31 M's
+    static const F32 HUD_TEXT_MAX_WIDTH; // 190px
+
 public:
 	// Set entire string, eliminating existing lines
 	void setString(const std::string& text_utf8);
@@ -92,11 +95,17 @@ class LLHUDNameTag : public LLHUDObject
 	void clearString();
 
 	// Add text a line at a time, allowing custom formatting
-	void addLine(const std::string &text_utf8, const LLColor4& color, const LLFontGL::StyleFlags style = LLFontGL::NORMAL, const LLFontGL* font = NULL, const bool use_ellipses = false);
+	void addLine(
+        const std::string &text_utf8,
+        const LLColor4& color,
+        const LLFontGL::StyleFlags style = LLFontGL::NORMAL,
+        const LLFontGL* font = NULL,
+        const bool use_ellipses = false,
+        F32 max_pixels = HUD_TEXT_MAX_WIDTH);
 
 	// For bubble chat, set the part above the chat text
 	void setLabel(const std::string& label_utf8);
-	void addLabel(const std::string& label_utf8);
+	void addLabel(const std::string& label_utf8, F32 max_pixels = HUD_TEXT_MAX_WIDTH);
 
 	// Sets the default font for lines with no font specified
 	void setFont(const LLFontGL* font);
diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp
index af2d168aefef8211ef667cd3234a86241ce0934f..3536b839899ead94b282bca2740747456a14e6be 100644
--- a/indra/newview/llimprocessing.cpp
+++ b/indra/newview/llimprocessing.cpp
@@ -453,7 +453,7 @@ void LLIMProcessing::processNewMessage(LLUUID from_id,
     BOOL is_friend = (LLAvatarTracker::instance().getBuddyInfo(from_id) == NULL) ? false : true;
     BOOL accept_im_from_only_friend = gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly");
     BOOL is_linden = chat.mSourceType != CHAT_SOURCE_OBJECT &&
-        LLMuteList::getInstance()->isLinden(name);
+        LLMuteList::isLinden(name);
 
     chat.mMuted = is_muted;
     chat.mFromID = from_id;
@@ -521,7 +521,9 @@ void LLIMProcessing::processNewMessage(LLUUID from_id,
                     dialog,
                     parent_estate_id,
                     region_id,
-                    position);
+                    position,
+                    false,      // is_region_msg
+                    timestamp);
 
                 if (!gIMMgr->isDNDMessageSend(session_id))
                 {
@@ -592,7 +594,8 @@ void LLIMProcessing::processNewMessage(LLUUID from_id,
                         parent_estate_id,
                         region_id,
                         position,
-                        region_message);
+                        region_message,
+                        timestamp);
                 }
                 else
                 {
@@ -1111,7 +1114,9 @@ void LLIMProcessing::processNewMessage(LLUUID from_id,
                     IM_SESSION_INVITE,
                     parent_estate_id,
                     region_id,
-                    position);
+                    position,
+                    false,      // is_region_msg
+                    timestamp);
             }
             else
             {
@@ -1131,12 +1136,14 @@ void LLIMProcessing::processNewMessage(LLUUID from_id,
                     from_id,
                     name,
                     buffer,
-                    IM_OFFLINE == offline,
-                    ll_safe_string((char*)binary_bucket),
+                    (IM_OFFLINE == offline),
+                    ll_safe_string((char*)binary_bucket),   // session name
                     IM_SESSION_INVITE,
                     parent_estate_id,
                     region_id,
-                    position);
+                    position,
+                    false,      // is_region_msg
+                    timestamp);
             }
             break;
 
diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp
index 3607e49b4abbc8bf3dc573a981da8f227b95b830..6880cf217189e663448827cfef7a99394b3909fa 100644
--- a/indra/newview/llimview.cpp
+++ b/indra/newview/llimview.cpp
@@ -1,25 +1,25 @@
-/** 
+/**
  * @file LLIMMgr.cpp
  * @brief Container for Instant Messaging
  *
  * $LicenseInfo:firstyear=2001&license=viewerlgpl$
  * Second Life Viewer Source Code
  * Copyright (C) 2010, Linden Research, Inc.
- * 
+ *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation;
  * version 2.1 of the License only.
- * 
+ *
  * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
- * 
+ *
  * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  * $/LicenseInfo$
  */
@@ -41,6 +41,7 @@
 #include "llstring.h"
 #include "lltextutil.h"
 #include "lltrans.h"
+#include "lltranslate.h"
 #include "lluictrlfactory.h"
 #include "llfloaterimsessiontab.h"
 #include "llagent.h"
@@ -76,11 +77,17 @@ const static std::string ADHOC_NAME_SUFFIX(" Conference");
 const static std::string NEARBY_P2P_BY_OTHER("nearby_P2P_by_other");
 const static std::string NEARBY_P2P_BY_AGENT("nearby_P2P_by_agent");
 
+// Markers inserted around translated part of chat text
+const static std::string XL8_START_TAG(" (");
+const static std::string XL8_END_TAG(")");
+const S32 XL8_PADDING = 3;  // XL8_START_TAG.size() + XL8_END_TAG.size()
+
 /** Timeout of outgoing session initialization (in seconds) */
 const static U32 SESSION_INITIALIZATION_TIMEOUT = 30;
 
 void startConfrenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents);
 void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType);
+void chatterBoxHistoryCoro(std::string url, LLUUID sessionId, std::string from, std::string message, U32 timestamp);
 void start_deprecated_conference_chat(const LLUUID& temp_session_id, const LLUUID& creator_id, const LLUUID& other_participant_id, const LLSD& agents_to_invite);
 
 const LLUUID LLOutgoingCallDialog::OCD_KEY = LLUUID("7CF78E11-0CFE-498D-ADB9-1417BF03DDB4");
@@ -111,7 +118,7 @@ void process_dnd_im(const LLSD& notification)
     LLUUID sessionID = data["SESSION_ID"].asUUID();
     LLUUID fromID = data["FROM_ID"].asUUID();
 
-    //re-create the IM session if needed 
+    //re-create the IM session if needed
     //(when coming out of DND mode upon app restart)
     if(!gIMMgr->hasSession(sessionID))
     {
@@ -122,13 +129,13 @@ void process_dnd_im(const LLSD& notification)
         {
             name = av_name.getDisplayName();
         }
-		
-        
-        LLIMModel::getInstance()->newSession(sessionID, 
-            name, 
-            IM_NOTHING_SPECIAL, 
-            fromID, 
-            false, 
+
+
+        LLIMModel::getInstance()->newSession(sessionID,
+            name,
+            IM_NOTHING_SPECIAL,
+            fromID,
+            false,
             false); //will need slight refactor to retrieve whether offline message or not (assume online for now)
     }
 
@@ -311,8 +318,8 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg)
 			}
 			else
 			{
-				if (is_dnd_msg && (ON_TOP == conversations_floater_status || 
-									NOT_ON_TOP == conversations_floater_status || 
+				if (is_dnd_msg && (ON_TOP == conversations_floater_status ||
+									NOT_ON_TOP == conversations_floater_status ||
 									CLOSED == conversations_floater_status))
 				{
 					im_box->highlightConversationItemWidget(session_id, true);
@@ -371,7 +378,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg)
 	}
 	if (store_dnd_message)
 	{
-		// If in DND mode, allow notification to be stored so upon DND exit 
+		// If in DND mode, allow notification to be stored so upon DND exit
 		// the user will be notified with some limitations (see 'is_dnd_msg' flag checks)
 		if(session_id.notNull()
 			&& participant_id.notNull()
@@ -392,7 +399,7 @@ void startConfrenceCoro(std::string url,
 {
     LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
     LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
-        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("TwitterConnect", httpPolicy));
+        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ConferenceChatStart", httpPolicy));
     LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
 
     LLSD postData;
@@ -432,7 +439,7 @@ void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvit
 {
     LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
     LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
-        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("TwitterConnect", httpPolicy));
+        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ConferenceInviteStart", httpPolicy));
     LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
 
     LLSD postData;
@@ -510,8 +517,124 @@ void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvit
 
 }
 
+void translateSuccess(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text,
+                        U64 time_n_flags, std::string originalMsg, std::string expectLang, std::string translation, const std::string detected_language)
+{
+    std::string message_txt(utf8_text);
+    // filter out non-interesting responses
+    if (!translation.empty()
+        && ((detected_language.empty()) || (expectLang != detected_language))
+        && (LLStringUtil::compareInsensitive(translation, originalMsg) != 0))
+    {   // Note - if this format changes, also fix code in addMessagesFromServerHistory()
+        message_txt += XL8_START_TAG + LLTranslate::removeNoTranslateTags(translation) + XL8_END_TAG;
+    }
+
+    // Extract info packed in time_n_flags
+    bool log2file =      (bool)(time_n_flags & (1LL << 32));
+    bool is_region_msg = (bool)(time_n_flags & (1LL << 33));
+    U32 time_stamp = (U32)(time_n_flags & 0x00000000ffffffff);
+
+    LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file, is_region_msg, time_stamp);
+}
+
+void translateFailure(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text,
+                        U64 time_n_flags, int status, const std::string err_msg)
+{
+    std::string message_txt(utf8_text);
+    std::string msg = LLTrans::getString("TranslationFailed", LLSD().with("[REASON]", err_msg));
+    LLStringUtil::replaceString(msg, "\n", " "); // we want one-line error messages
+    message_txt += XL8_START_TAG + msg + XL8_END_TAG;
+
+    // Extract info packed in time_n_flags
+    bool log2file = (bool)(time_n_flags & (1LL << 32));
+    bool is_region_msg = (bool)(time_n_flags & (1LL << 33));
+    U32 time_stamp = (U32)(time_n_flags & 0x00000000ffffffff);
+
+    LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file, is_region_msg, time_stamp);
+}
+
+void chatterBoxHistoryCoro(std::string url, LLUUID sessionId, std::string from, std::string message, U32 timestamp)
+{   // if parameters from, message and timestamp have values, they are a message that opened chat
+    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
+    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
+        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ChatHistory", httpPolicy));
+    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+
+    LLSD postData;
+    postData["method"] = "fetch history";
+    postData["session-id"] = sessionId;
+
+    LL_DEBUGS("ChatHistory") << sessionId << ": Chat history posting " << postData << " to " << url
+        << ", from " << from << ", message " << message << ", timestamp " << (S32)timestamp << LL_ENDL;
+
+    LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData);
+
+    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+    if (!status)
+    {
+        LL_WARNS("ChatHistory") << sessionId << ": Bad HTTP response in chatterBoxHistoryCoro"
+            << ", results: " << httpResults << LL_ENDL;
+        return;
+    }
+
+    // Add history to IM session
+    LLSD history = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT];
+
+    LL_DEBUGS("ChatHistory") << sessionId << ": Chat server history fetch returned " << history << LL_ENDL;
+
+    try
+    {
+        LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(sessionId);
+        if (session && history.isArray())
+        {   // Result array is sorted oldest to newest
+            if (history.size() > 0)
+            {   // History from the chat server has an integer 'time' value timestamp.   Create 'datetime' string which will match
+                // what we have from the local history cache
+                for (LLSD::array_iterator cur_server_hist = history.beginArray(), endLists = history.endArray();
+                    cur_server_hist != endLists;
+                    cur_server_hist++)
+                {
+                    if ((*cur_server_hist).isMap())
+                    {   // Take the 'time' value from the server and make the date-time string that will be in local cache log files
+                        //   {'from_id':u7aa8c222-8a81-450e-b3d1-9c28491ef717,'message':'Can you hear me now?','from':'Chat Tester','num':i86,'time':r1.66501e+09}
+                        U32 timestamp = (U32)((*cur_server_hist)[LL_IM_TIME].asInteger());
+                        (*cur_server_hist)[LL_IM_DATE_TIME] = LLLogChat::timestamp2LogString(timestamp, true);
+                    }
+                }
+
+                session->addMessagesFromServerHistory(history, from, message, timestamp);
+
+                // Display the newly added messages
+                LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance<LLFloaterIMSession>("impanel", sessionId);
+                if (floater && floater->isInVisibleChain())
+                {
+                    floater->updateMessages();
+                }
+            }
+            else
+            {
+                LL_DEBUGS("ChatHistory") << sessionId << ": Empty history from chat server, nothing to add" << LL_ENDL;
+            }
+        }
+        else if (session && !history.isArray())
+        {
+            LL_WARNS("ChatHistory") << sessionId << ": Bad array data fetching chat history" << LL_ENDL;
+        }
+        else
+        {
+            LL_WARNS("ChatHistory") << sessionId << ": Unable to find session fetching chat history" << LL_ENDL;
+        }
+    }
+    catch (...)
+    {
+        LOG_UNHANDLED_EXCEPTION("chatterBoxHistoryCoro");
+        LL_WARNS("ChatHistory") << "chatterBoxHistoryCoro unhandled exception while processing data for session " << sessionId << LL_ENDL;
+    }
+}
 
-LLIMModel::LLIMModel() 
+LLIMModel::LLIMModel()
 {
 	addNewMsgCallback(boost::bind(&LLFloaterIMSession::newIMCallback, _1));
 	addNewMsgCallback(boost::bind(&on_new_message, _1));
@@ -556,25 +679,25 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string&
 		else
 		{
 			mSessionType = ADHOC_SESSION;
-		} 
+		}
 	}
 
 	if(mVoiceChannel)
 	{
 		mVoiceChannelStateChangeConnection = mVoiceChannel->setStateChangedCallback(boost::bind(&LLIMSession::onVoiceChannelStateChanged, this, _1, _2, _3));
 	}
-		
+
 	mSpeakers = new LLIMSpeakerMgr(mVoiceChannel);
 
 	// All participants will be added to the list of people we've recently interacted with.
 
-	// we need to add only _active_ speakers...so comment this. 
+	// we need to add only _active_ speakers...so comment this.
 	// may delete this later on cleanup
 	//mSpeakers->addListener(&LLRecentPeople::instance(), "add");
 
 	//we need to wait for session initialization for outgoing ad-hoc and group chat session
 	//correct session id for initiated ad-hoc chat will be received from the server
-	if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID, 
+	if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID,
 		mInitialTargetIDs, mType))
 	{
 		//we don't need to wait for any responses
@@ -656,7 +779,7 @@ void LLIMModel::LLIMSession::onVoiceChannelStateChanged(const LLVoiceChannel::ES
 					LLStringUtil::format_map_t string_args;
 					string_args["[NAME]"] = other_avatar_name;
 					message = LLTrans::getString("name_started_call", string_args);
-					LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, message);				
+					LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, message);
 					break;
 				}
 			case LLVoiceChannel::STATE_CONNECTED :
@@ -760,14 +883,21 @@ void LLIMModel::LLIMSession::sessionInitReplyReceived(const LLUUID& new_session_
 	}
 }
 
-void LLIMModel::LLIMSession::addMessage(const std::string& from, const LLUUID& from_id, const std::string& utf8_text, const std::string& time, const bool is_history, bool is_region_msg)
+void LLIMModel::LLIMSession::addMessage(const std::string& from,
+                                        const LLUUID& from_id,
+                                        const std::string& utf8_text,
+                                        const std::string& time,
+                                        const bool is_history,  // comes from a history file or chat server
+                                        const bool is_region_msg,
+                                        const U32 timestamp)   // may be zero
 {
 	LLSD message;
 	message["from"] = from;
 	message["from_id"] = from_id;
 	message["message"] = utf8_text;
-	message["time"] = time; 
-	message["index"] = (LLSD::Integer)mMsgs.size(); 
+	message["time"] = time;         // string used in display, may be full data YYYY/MM/DD HH:MM or just HH:MM
+    message["timestamp"] = (S32)timestamp;          // use string? LLLogChat::timestamp2LogString(timestamp, true);
+	message["index"] = (LLSD::Integer)mMsgs.size();
 	message["is_history"] = is_history;
 	message["is_region_msg"] = is_region_msg;
 
@@ -788,7 +918,7 @@ void LLIMModel::LLIMSession::addMessage(const std::string& from, const LLUUID& f
 		}
 	}
 
-	mMsgs.push_front(message); 
+	mMsgs.push_front(message);          // Add most recent messages to the front of mMsgs
 
 	if (mSpeakers && from_id.notNull())
 	{
@@ -797,35 +927,281 @@ void LLIMModel::LLIMSession::addMessage(const std::string& from, const LLUUID& f
 	}
 }
 
-void LLIMModel::LLIMSession::addMessagesFromHistory(const std::list<LLSD>& history)
+void LLIMModel::LLIMSession::addMessagesFromHistoryCache(const chat_message_list_t& history)
 {
-	std::list<LLSD>::const_iterator it = history.begin();
-	while (it != history.end())
-	{
-		const LLSD& msg = *it;
+    // Add the messages from the local cached chat history to the session window
+    for (const auto& msg : history)
+    {
+        std::string from = msg[LL_IM_FROM];
+        LLUUID from_id;
+        if (msg[LL_IM_FROM_ID].isDefined())
+        {
+            from_id = msg[LL_IM_FROM_ID].asUUID();
+        }
+        else
+        {   // convert it to a legacy name if we have a complete name
+            std::string legacy_name = gCacheName->buildLegacyName(from);
+            from_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name);
+        }
 
-		std::string from = msg[LL_IM_FROM];
-		LLUUID from_id;
-		if (msg[LL_IM_FROM_ID].isDefined())
-		{
-			from_id = msg[LL_IM_FROM_ID].asUUID();
-		}
-		else
-		{
-			// convert it to a legacy name if we have a complete name
-			std::string legacy_name = gCacheName->buildLegacyName(from);
-			from_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name);
-		}
+        // Save the last minute of messages so we can merge with the chat server history.
+        // Really would be nice to have a numeric timestamp in the local cached chat file
+        const std::string & msg_time_str = msg[LL_IM_DATE_TIME].asString();
+        if (mLastHistoryCacheDateTime != msg_time_str)
+        {
+            mLastHistoryCacheDateTime = msg_time_str;   // Reset to the new time
+            mLastHistoryCacheMsgs.clear();
+        }
+        mLastHistoryCacheMsgs.push_front(msg);
+        LL_DEBUGS("ChatHistory") << mSessionID << ": Adding history cache message: " << msg << LL_ENDL;
 
-		std::string timestamp = msg[LL_IM_TIME];
-		std::string text = msg[LL_IM_TEXT];
+        // Add message from history cache to the display
+        addMessage(from, from_id, msg[LL_IM_TEXT], msg[LL_IM_TIME], true, false, 0);   // from history data, not region message, no timestamp
+    }
+}
 
-		addMessage(from, from_id, text, timestamp, true);
+void LLIMModel::LLIMSession::addMessagesFromServerHistory(const LLSD& history,             // Array of chat messages from chat server
+                                                        const std::string& target_from,    // Sender of message that opened chat
+                                                        const std::string& target_message, // Message text that opened chat
+                                                        U32 timestamp)                     // timestamp of message that opened chat
+{   // Add messages from history returned by the chat server.
+
+    // The session mMsgs may contain chat messages from the local history cache file, and possibly one or more newly
+    // arrived chat messages.   If the chat window was manually opened, these will be empty and history can
+    // more easily merged.    The history from the server, however, may overlap what is in the file and those must also be merged.
+
+    // At this point, the session mMsgs can have
+    //   no messages
+    //   nothing from history file cache, but one or more very recently arrived messages,
+    //   messages from history file cache, no recent chat
+    //   messages from history file cache, one or more very recent messages
+    //
+    // The chat history from server can possibly contain:
+    //   no messages
+    //   messages that start back before anything in the local file (obscure case, but possible)
+    //   messages that match messages from the history file cache
+    //   messages from the last hour, new to the viewer
+    //   one or more messages that match most recently received chat (the one that opened the window)
+    // In other words:
+    //   messages from chat server may or may not match what we already have in mMsgs
+    //   We can drop anything that is during the time span covered by the local cache file
+    //   To keep things simple, drop any chat data older than the local cache file
+
+    if (!history.isArray())
+    {
+        LL_WARNS("ChatHistory") << mSessionID << ": Unexpected history data not array, type " << (S32)history.type() << LL_ENDL;
+        return;
+    }
 
-		it++;
-	}
+    if (history.size() == 0)
+    {   // If history is empty
+        LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() has empty history, nothing to merge" << LL_ENDL;
+        return;
+    }
+
+    if (history.size() == 1 &&          // Server chat history has one entry,
+        target_from.length() > 0 &&     // and we have a chat message that just arrived
+        mMsgs.size() > 0)               // and we have some data in the window - assume the history message is there.
+    {   // This is the common case where a group chat is silent for a while, and then one message is sent.
+        LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() only has chat message just received." << LL_ENDL;
+        return;
+    }
+
+    LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() starting with mMsg.size() " << mMsgs.size()
+        << " adding history with " << history.size() << " messages"
+        << ", target_from: " << target_from
+        << ", target_message: " << target_message
+        << ", timestamp: " << (S32)timestamp << LL_ENDL;
+
+    // At start of merging, mMsgs is either empty, has some chat messages read from a local cache file, and may have
+    // one or more messages that just arrived from the server.
+    U32 match_timestamp = 0;
+    chat_message_list_t shift_msgs;
+    if (mMsgs.size() > 0 &&
+        target_from.length() > 0
+        && target_message.length() > 0)
+    {   // Find where to insert the history messages by popping off a few in the session.
+        // The most common case is one duplciate message, the one that opens a chat session
+        while (mMsgs.size() > 0)
+        {
+            // The "time" value from mMsgs is a string, either just time HH:MM or a full date and time
+            LLSD cur_msg = mMsgs.front();       // Get most recent message from the chat display (front of mMsgs list)
+
+            if (cur_msg.isMap())
+            {
+                LL_DEBUGS("ChatHistoryCompare") << mSessionID << ": Finding insertion point, looking at cur_msg: " << cur_msg << LL_ENDL;
+
+                match_timestamp = cur_msg["timestamp"].asInteger();  // get timestamp of message in the session, may be zero
+                if ((S32)timestamp > match_timestamp)
+                {
+                    LL_DEBUGS("ChatHistory") << mSessionID << ": found older chat message: " << cur_msg
+                        << ", timestamp " << (S32)timestamp
+                        << " vs. match_timestamp " << match_timestamp
+                        << ", shift_msgs size is " << shift_msgs.size() << LL_ENDL;
+                    break;
+                }
+                // Have the matching message or one more recent: these need to be at the end
+                shift_msgs.push_front(cur_msg);     // Move chat message to temp list.
+                mMsgs.pop_front();                  // Normally this is just one message
+                LL_DEBUGS("ChatHistory") << mSessionID << ": shifting chat message " << cur_msg
+                    << " to be inserted at end, shift_msgs size is " << shift_msgs.size()
+                    << ", match_timestamp " << match_timestamp
+                    << ", timestamp " << (S32)timestamp << LL_ENDL;
+            }
+            else
+            {
+                LL_DEBUGS("ChatHistory") << mSessionID << ": Unexpected non-map entry in session messages: " << cur_msg << LL_ENDL;
+                return;
+            }
+        }
+    }
+
+    // Now merge messages from server history data into the session display.   The history data
+    // from the local file may overlap with the chat messages from the server.
+    // Drop any messages from the chat server history that are before the latest one from the local history file.
+    // Unfortunately, messages from the local file don't have timestamps - just datetime strings
+    LLSD::array_const_iterator cur_history_iter = history.beginArray();
+    while (cur_history_iter != history.endArray())
+    {
+        const LLSD &cur_server_hist = *cur_history_iter;
+        cur_history_iter++;
+
+        if (cur_server_hist.isMap())
+        {   // Each server history entry looks like
+            //   { 'from':'Laggy Avatar', 'from_id' : u72345678 - 744f - 43b9 - 98af - b06f1c76ddda, 'index' : i24, 'is_history' : 1, 'message' : 'That was slow', 'time' : '02/13/2023 10:03', 'timestamp' : i1676311419 }
+
+            // If we reach the message that opened our window, stop adding messages
+            U32 history_msg_timestamp = (U32)cur_server_hist[LL_IM_TIME].asInteger();
+            if ((match_timestamp > 0 && match_timestamp <= history_msg_timestamp) ||
+                (timestamp > 0 && timestamp <= history_msg_timestamp))
+            {   // we found the message we matched, so stop inserting from chat server history
+                LL_DEBUGS("ChatHistoryCompare") << "Found end of chat history insertion with match_timestamp " << (S32)match_timestamp
+                    << " vs. history_msg_timestamp " << (S32)history_msg_timestamp
+                    << " vs. timestamp " << (S32)timestamp
+                    << LL_ENDL;
+                break;
+            }
+            LL_DEBUGS("ChatHistoryCompare") << "Compared match_timestamp " << (S32)match_timestamp
+                << " vs. history_msg_timestamp " << (S32)history_msg_timestamp << LL_ENDL;
+
+            bool add_chat_to_conversation = true;
+            if (!mLastHistoryCacheDateTime.empty())
+            {   // Skip past the any from server that are older than what we already read from the history file.
+                std::string history_datetime = cur_server_hist[LL_IM_DATE_TIME].asString();
+                if (history_datetime.empty())
+                {
+                    history_datetime = cur_server_hist[LL_IM_TIME].asString();
+                }
+
+                if (history_datetime < mLastHistoryCacheDateTime)
+                {
+                    LL_DEBUGS("ChatHistoryCompare") << "Skipping message from chat server history since it's older than messages the session already has."
+                        << history_datetime << " vs  " << mLastHistoryCacheDateTime << LL_ENDL;
+                    add_chat_to_conversation = false;
+                }
+                else if (history_datetime > mLastHistoryCacheDateTime)
+                {   // The message from the chat server is more recent than the last one from the local cache file.   Add it
+                    LL_DEBUGS("ChatHistoryCompare") << "Found message dated "
+                        << history_datetime << " vs " << mLastHistoryCacheDateTime
+                        << ", adding new message from chat server history " << cur_server_hist << LL_ENDL;
+                }
+                else   // (history_datetime == mLastHistoryCacheDateTime)
+                {      // Messages are in the same minute as the last from the cache log file.
+                    const std::string & history_msg_text = cur_server_hist[LL_IM_TEXT];
+
+                    // Look in the saved messages from the history file that have the same time
+                    for (const auto& scan_msg : mLastHistoryCacheMsgs)
+                    {
+                        LL_DEBUGS("ChatHistoryCompare") << "comparing messages " << scan_msg[LL_IM_TEXT]
+                            << " with " << cur_server_hist << LL_ENDL;
+                        if (scan_msg.size() > 0)
+                        {   // Extra work ... the history_msg_text value may have been translated, i.e. "I am confused (je suis confus)"
+                            //  while the server history will only have the first part "I am confused"
+                            std::string target_compare(scan_msg[LL_IM_TEXT]);
+                            if (target_compare.size() > history_msg_text.size() + XL8_PADDING &&
+                                target_compare.substr(history_msg_text.size(), XL8_START_TAG.size()) == XL8_START_TAG &&
+                                target_compare.substr(target_compare.size() - XL8_END_TAG.size()) == XL8_END_TAG)
+                            {   // This really looks like a "translated string (cadena traducida)" so just compare the source part
+                                LL_DEBUGS("ChatHistory") << mSessionID << ": Found translated chat " << target_compare
+                                    << " when comparing to history " << history_msg_text
+                                    << ", will truncate" << LL_ENDL;
+                                target_compare = target_compare.substr(0, history_msg_text.size());
+                            }
+                            if (history_msg_text == target_compare)
+                            {   // Found a match, so don't add a duplicate chat message to the window
+                                LL_DEBUGS("ChatHistory") << mSessionID << ": Found duplicate message text " << history_msg_text
+                                    << " : " << (S32)history_msg_timestamp << ", matching datetime " << history_datetime << LL_ENDL;
+                                add_chat_to_conversation = false;
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            LLUUID sender_id = cur_server_hist[LL_IM_FROM_ID].asUUID();
+            if (add_chat_to_conversation)
+            {   // Check if they're muted
+                if (LLMuteList::getInstance()->isMuted(sender_id, LLMute::flagTextChat))
+                {
+                    add_chat_to_conversation = false;
+                    LL_DEBUGS("ChatHistory") << mSessionID << ": Skipped adding chat from " << sender_id
+                        << " as muted, message: " << cur_server_hist
+                        << LL_ENDL;
+                }
+            }
+
+            if (add_chat_to_conversation)
+            {   // Finally add message to the chat session
+                std::string chat_time_str = LLConversation::createTimestamp((U64Seconds)history_msg_timestamp);
+                std::string sender_name = cur_server_hist[LL_IM_FROM].asString();
+
+                std::string history_msg_text = cur_server_hist[LL_IM_TEXT].asString();
+                LLSD message;
+                message["from"] = sender_name;
+                message["from_id"] = sender_id;
+                message["message"] = history_msg_text;
+                message["time"] = chat_time_str;
+                message["timestamp"] = (S32)history_msg_timestamp;
+                message["index"] = (LLSD::Integer)mMsgs.size();
+                message["is_history"] = true;
+                mMsgs.push_front(message);
+
+                LL_DEBUGS("ChatHistory") << mSessionID << ": push_front() adding group chat history message " << message << LL_ENDL;
+
+                // Add chat history messages to the local cache file, only in the case where we opened the chat window
+                // Need to solve the logic around messages that arrive and open chat - at this point, they've already been added to the
+                //   local history cache file.   If we append messages here, it will be out of order.
+                if (target_from.empty() && target_message.empty())
+                {
+                    LLIMModel::getInstance()->logToFile(LLIMModel::getInstance()->getHistoryFileName(mSessionID),
+                        sender_name, sender_id, history_msg_text);
+                }
+            }
+        }
+    }
+
+    S32 shifted_size = shift_msgs.size();
+    while (shift_msgs.size() > 0)
+    {   // Finally add back any new messages, and tweak the index value to be correct.
+        LLSD newer_message = shift_msgs.front();
+        shift_msgs.pop_front();
+        S32 old_index = newer_message["index"];
+        newer_message["index"] = (LLSD::Integer)mMsgs.size();   // Update the index to match the new position in the conversation
+        LL_DEBUGS("ChatHistory") << mSessionID << ": Re-adding newest group chat history messages from " << newer_message["from"]
+            << ", text: " << newer_message["message"]
+            << " old index " << old_index << ", new index " << newer_message["index"] << LL_ENDL;
+        mMsgs.push_front(newer_message);
+    }
+
+    LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() exiting with mMsg.size() " << mMsgs.size()
+        << ", shifted " << shifted_size << " messages" << LL_ENDL;
+
+    mLastHistoryCacheDateTime.clear();  // Don't need this data
+    mLastHistoryCacheMsgs.clear();
 }
 
+
 void LLIMModel::LLIMSession::chatFromLogFile(LLLogChat::ELogLineType type, const LLSD& msg, void* userdata)
 {
 	if (!userdata) return;
@@ -834,26 +1210,29 @@ void LLIMModel::LLIMSession::chatFromLogFile(LLLogChat::ELogLineType type, const
 
 	if (type == LLLogChat::LOG_LINE)
 	{
-		self->addMessage("", LLSD(), msg["message"].asString(), "", true);
+        LL_DEBUGS("ChatHistory") << "chatFromLogFile() adding LOG_LINE message from " << msg << LL_ENDL;
+        self->addMessage("", LLSD(), msg["message"].asString(), "", true, false, 0);        // from history data, not region message, no timestamp
 	}
 	else if (type == LLLogChat::LOG_LLSD)
 	{
-		self->addMessage(msg["from"].asString(), msg["from_id"].asUUID(), msg["message"].asString(), msg["time"].asString(), true);
+        LL_DEBUGS("ChatHistory") << "chatFromLogFile() adding LOG_LLSD message from " << msg << LL_ENDL;
+        self->addMessage(msg["from"].asString(), msg["from_id"].asUUID(), msg["message"].asString(), msg["time"].asString(), true, false, 0);  // from history data, not region message, no timestamp
 	}
 }
 
 void LLIMModel::LLIMSession::loadHistory()
 {
 	mMsgs.clear();
+    mLastHistoryCacheMsgs.clear();
+    mLastHistoryCacheDateTime.clear();
 
 	if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") )
 	{
-		std::list<LLSD> chat_history;
-
-		//involves parsing of a chat history
+        // read and parse chat history from local file
+        chat_message_list_t chat_history;
 		LLLogChat::loadChatHistory(mHistoryFileName, chat_history, LLSD(), isGroupChat());
-		addMessagesFromHistory(chat_history);
-	}
+        addMessagesFromHistoryCache(chat_history);
+    }
 }
 
 LLIMModel::LLIMSession* LLIMModel::findIMSession(const LLUUID& session_id) const
@@ -873,7 +1252,7 @@ LLIMModel::LLIMSession* LLIMModel::findAdHocIMSession(const uuid_vec_t& ids)
 	for (; it != mId2SessionMap.end(); ++it)
 	{
 		LLIMSession* session = (*it).second;
-	
+
 		if (!session->isAdHoc()) continue;
 		if (session->mInitialTargetIDs.size() != num) continue;
 
@@ -884,8 +1263,8 @@ LLIMModel::LLIMSession* LLIMModel::findAdHocIMSession(const uuid_vec_t& ids)
 		{
 			tmp_list.remove(*iter);
 			++iter;
-			
-			if (tmp_list.empty()) 
+
+			if (tmp_list.empty())
 			{
 				break;
 			}
@@ -941,7 +1320,7 @@ void LLIMModel::LLIMSession::buildHistoryFileName()
 	if (isAdHoc())
 	{
 		/* in case of outgoing ad-hoc sessions we need to make specilized names
-		* if this naming system is ever changed then the filtering definitions in 
+		* if this naming system is ever changed then the filtering definitions in
 		* lllogchat.cpp need to be change acordingly so that the filtering for the
 		* date stamp code introduced in STORM-102 will work properly and not add
 		* a date stamp to the Ad-hoc conferences.
@@ -954,7 +1333,7 @@ void LLIMModel::LLIMSession::buildHistoryFileName()
 		else
 		{
 			//in case of incoming ad-hoc sessions
-			mHistoryFileName = mName + " " + LLLogChat::timestamp(true) + " " + mSessionID.asString().substr(0, 4);
+			mHistoryFileName = mName + " " + LLLogChat::timestamp2LogString(0, true) + " " + mSessionID.asString().substr(0, 4);
 		}
 	}
 	else if (isP2P()) // look up username to use as the log name
@@ -985,7 +1364,7 @@ void LLIMModel::LLIMSession::buildHistoryFileName()
 LLUUID LLIMModel::LLIMSession::generateHash(const std::set<LLUUID>& sorted_uuids)
 {
 	LLMD5 md5_uuid;
-	
+
 	std::set<LLUUID>::const_iterator it = sorted_uuids.begin();
 	while (it != sorted_uuids.end())
 	{
@@ -1047,7 +1426,7 @@ void LLIMModel::testMessages()
 
 	S32 rand1 = ll_rand(sizeof firstname)/(sizeof firstname[0]);
 	S32 rand2 = ll_rand(sizeof lastname)/(sizeof lastname[0]);
-	
+
 	from = firstname[rand1] + " " + lastname[rand2];
 	bot2_id.generate(from);
 	LLUUID bot2_session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, bot2_id);
@@ -1057,7 +1436,7 @@ void LLIMModel::testMessages()
 }
 
 //session name should not be empty
-bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, 
+bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type,
 						   const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg)
 {
 	if (name.empty())
@@ -1099,7 +1478,7 @@ bool LLIMModel::clearSession(const LLUUID& session_id)
 	return true;
 }
 
-void LLIMModel::getMessages(const LLUUID& session_id, std::list<LLSD>& messages, int start_index, const bool sendNoUnreadMsgs)
+void LLIMModel::getMessages(const LLUUID& session_id, chat_message_list_t& messages, int start_index, const bool sendNoUnreadMsgs)
 {
 	getMessagesSilently(session_id, messages, start_index);
 
@@ -1109,7 +1488,7 @@ void LLIMModel::getMessages(const LLUUID& session_id, std::list<LLSD>& messages,
 	}
 }
 
-void LLIMModel::getMessagesSilently(const LLUUID& session_id, std::list<LLSD>& messages, int start_index)
+void LLIMModel::getMessagesSilently(const LLUUID& session_id, chat_message_list_t& messages, int start_index)
 {
 	LLIMSession* session = findIMSession(session_id);
 	if (!session)
@@ -1120,7 +1499,7 @@ void LLIMModel::getMessagesSilently(const LLUUID& session_id, std::list<LLSD>& m
 
 	int i = session->mMsgs.size() - start_index;
 
-	for (std::list<LLSD>::iterator iter = session->mMsgs.begin();
+	for (chat_message_list_t::iterator iter = session->mMsgs.begin();
 		iter != session->mMsgs.end() && i > 0;
 		iter++)
 	{
@@ -1142,7 +1521,7 @@ void LLIMModel::sendNoUnreadMessages(const LLUUID& session_id)
 
 	session->mNumUnread = 0;
 	session->mParticipantUnreadMessageCount = 0;
-	
+
 	LLSD arg;
 	arg["session_id"] = session_id;
 	arg["num_unread"] = 0;
@@ -1150,17 +1529,23 @@ void LLIMModel::sendNoUnreadMessages(const LLUUID& session_id)
 	mNoUnreadMsgsSignal(arg);
 }
 
-bool LLIMModel::addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, bool is_region_msg) {
-	
+bool LLIMModel::addToHistory(const LLUUID& session_id,
+                             const std::string& from,
+                             const LLUUID& from_id,
+                             const std::string& utf8_text,
+                             bool is_region_msg,
+                             U32 timestamp)
+{
 	LLIMSession* session = findIMSession(session_id);
 
-	if (!session) 
+	if (!session)
 	{
 		LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL;
 		return false;
 	}
 
-	session->addMessage(from, from_id, utf8_text, LLLogChat::timestamp(false), false, is_region_msg); //might want to add date separately
+    // This is where a normal arriving message is added to the session.   Note that the time string created here is without the full date
+	session->addMessage(from, from_id, utf8_text, LLLogChat::timestamp2LogString(timestamp, false), false, is_region_msg, timestamp);
 
 	return true;
 }
@@ -1168,14 +1553,14 @@ bool LLIMModel::addToHistory(const LLUUID& session_id, const std::string& from,
 bool LLIMModel::logToFile(const std::string& file_name, const std::string& from, const LLUUID& from_id, const std::string& utf8_text)
 {
 	if (gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 1)
-	{	
+	{
 		std::string from_name = from;
 
 		LLAvatarName av_name;
-		if (!from_id.isNull() && 
+		if (!from_id.isNull() &&
 			LLAvatarNameCache::get(from_id, &av_name) &&
 			!av_name.isDisplayNameDefault())
-		{	
+		{
 			from_name = av_name.getCompleteName();
 		}
 
@@ -1189,43 +1574,63 @@ bool LLIMModel::logToFile(const std::string& file_name, const std::string& from,
 	}
 }
 
-bool LLIMModel::proccessOnlineOfflineNotification(
-	const LLUUID& session_id, 
-	const std::string& utf8_text)
+void LLIMModel::proccessOnlineOfflineNotification(
+	const LLUUID& session_id,
+    const std::string& utf8_text)
 {
 	// Add system message to history
-	return addMessage(session_id, SYSTEM_FROM, LLUUID::null, utf8_text);
+	addMessage(session_id, SYSTEM_FROM, LLUUID::null, utf8_text);
 }
 
-bool LLIMModel::addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, 
-						   const std::string& utf8_text, bool log2file, bool is_region_msg) { 
+void LLIMModel::addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
+						   const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg, /* = false */ U32 time_stamp /* = 0 */)
+{
+    if (gSavedSettings.getBOOL("TranslateChat") && (from != SYSTEM_FROM))
+    {
+        const std::string from_lang = ""; // leave empty to trigger autodetect
+        const std::string to_lang = LLTranslate::getTranslateLanguage();
+        U64 time_n_flags = ((U64) time_stamp) | (log2file ? (1LL << 32) : 0) | (is_region_msg ? (1LL << 33) : 0);   // boost::bind has limited parameters
+        LLTranslate::translateMessage(from_lang, to_lang, utf8_text,
+            boost::bind(&translateSuccess, session_id, from, from_id, utf8_text, time_n_flags, utf8_text, from_lang, _1, _2),
+            boost::bind(&translateFailure, session_id, from, from_id, utf8_text, time_n_flags, _1, _2));
+    }
+    else
+    {
+        processAddingMessage(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp);
+    }
+}
 
-	LLIMSession* session = addMessageSilently(session_id, from, from_id, utf8_text, log2file, is_region_msg);
-	if (!session) return false;
+void LLIMModel::processAddingMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
+    const std::string& utf8_text, bool log2file, bool is_region_msg, U32 time_stamp)
+{
+    LLIMSession* session = addMessageSilently(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp);
+    if (!session)
+        return;
 
-	//good place to add some1 to recent list
-	//other places may be called from message history.
-	if( !from_id.isNull() &&
-		( session->isP2PSessionType() || session->isAdHocSessionType() ) )
-		LLRecentPeople::instance().add(from_id);
+    //good place to add some1 to recent list
+    //other places may be called from message history.
+    if( !from_id.isNull() &&
+        ( session->isP2PSessionType() || session->isAdHocSessionType() ) )
+        LLRecentPeople::instance().add(from_id);
 
-	// notify listeners
-	LLSD arg;
-	arg["session_id"] = session_id;
-	arg["num_unread"] = session->mNumUnread;
-	arg["participant_unread"] = session->mParticipantUnreadMessageCount;
-	arg["message"] = utf8_text;
-	arg["from"] = from;
-	arg["from_id"] = from_id;
-	arg["time"] = LLLogChat::timestamp(false);
-	arg["session_type"] = session->mSessionType;
-	mNewMsgSignal(arg);
+    // notify listeners
+    LLSD arg;
+    arg["session_id"] = session_id;
+    arg["num_unread"] = session->mNumUnread;
+    arg["participant_unread"] = session->mParticipantUnreadMessageCount;
+    arg["message"] = utf8_text;
+    arg["from"] = from;
+    arg["from_id"] = from_id;
+    arg["time"] = LLLogChat::timestamp2LogString(time_stamp, true);
+    arg["session_type"] = session->mSessionType;
+    arg["is_region_msg"] = is_region_msg;
 
-	return true;
+    mNewMsgSignal(arg);
 }
 
-LLIMModel::LLIMSession* LLIMModel::addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, 
-													 const std::string& utf8_text, bool log2file, bool is_region_msg)
+LLIMModel::LLIMSession* LLIMModel::addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
+													  const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg, /* false */
+                                                      U32 timestamp /* = 0 */)
 {
 	LLIMSession* session = findIMSession(session_id);
 
@@ -1241,12 +1646,12 @@ LLIMModel::LLIMSession* LLIMModel::addMessageSilently(const LLUUID& session_id,
 		from_name = SYSTEM_FROM;
 	}
 
-	addToHistory(session_id, from_name, from_id, utf8_text, is_region_msg);
+	addToHistory(session_id, from_name, from_id, utf8_text, is_region_msg, timestamp);
 	if (log2file)
 	{
 		logToFile(getHistoryFileName(session_id), from_name, from_id, utf8_text);
 	}
-	
+
 	session->mNumUnread++;
 
 	//update count of unread messages from real participant
@@ -1348,7 +1753,7 @@ const std::string& LLIMModel::getHistoryFileName(const LLUUID& session_id) const
 
 
 // TODO get rid of other participant ID
-void LLIMModel::sendTypingState(LLUUID session_id, LLUUID other_participant_id, BOOL typing) 
+void LLIMModel::sendTypingState(LLUUID session_id, LLUUID other_participant_id, BOOL typing)
 {
 	std::string name;
 	LLAgentUI::buildFullname(name);
@@ -1379,7 +1784,7 @@ void LLIMModel::sendLeaveSession(const LLUUID& session_id, const LLUUID& other_p
 			FALSE,
 			gAgent.getSessionID(),
 			other_participant_id,
-			name, 
+			name,
 			LLStringUtil::null,
 			IM_ONLINE,
 			IM_SESSION_LEAVE,
@@ -1400,7 +1805,7 @@ void LLIMModel::sendMessage(const std::string& utf8_text,
 
 	const LLRelationship* info = NULL;
 	info = LLAvatarTracker::instance().getBuddyInfo(other_participant_id);
-	
+
 	U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE;
 	// Old call to send messages to SLim client,  no longer supported.
 	//if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id)))
@@ -1408,7 +1813,7 @@ void LLIMModel::sendMessage(const std::string& utf8_text,
 	//	// User is online through the OOW connector, but not with a regular viewer.  Try to send the message via SLVoice.
 	//	sent = LLVoiceClient::getInstance()->sendTextMessage(other_participant_id, utf8_text);
 	//}
-	
+
 	if(!sent)
 	{
 		// Send message normally.
@@ -1465,7 +1870,7 @@ void LLIMModel::sendMessage(const std::string& utf8_text,
 		}
 	}
 
-	if((dialog == IM_NOTHING_SPECIAL) && 
+	if((dialog == IM_NOTHING_SPECIAL) &&
 	   (other_participant_id.notNull()))
 	{
 		// Do we have to replace the /me's here?
@@ -1503,7 +1908,7 @@ void LLIMModel::sendMessage(const std::string& utf8_text,
 		// to Recent People to prevent showing of an item with (?? ?)(?? ?), sans the spaces. See EXT-8246.
 		// Concrete participants will be added into this list once they sent message in chat.
 		if (IM_SESSION_INVITE == dialog) return;
-			
+
 		if (IM_SESSION_CONFERENCE_START == dialog) // outgoing ad-hoc session
 		{
 			// Add only online members of conference to recent list (EXT-8658)
@@ -1585,7 +1990,7 @@ void start_deprecated_conference_chat(
 	for(S32 i = 0; i < count; ++i)
 	{
 		LLUUID agent_id = agents_to_invite[i].asUUID();
-		
+
 		memcpy(pos, &agent_id, UUID_BYTES);
 		pos += UUID_BYTES;
 	}
@@ -1601,7 +2006,7 @@ void start_deprecated_conference_chat(
 		bucket_size);
 
 	gAgent.sendReliableMessage();
- 
+
 	delete[] bucket;
 }
 
@@ -1822,7 +2227,7 @@ void LLCallDialogManager::onVoiceChannelChangedInt(const LLUUID &session_id)
 {
 	LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(session_id);
 	if(!session)
-	{		
+	{
 		mPreviousSessionlName = mCurrentSessionlName;
 		mCurrentSessionlName = ""; // Empty string results in "Nearby Voice Chat" after substitution
 		return;
@@ -1845,7 +2250,7 @@ void LLCallDialogManager::onVoiceChannelChangedInt(const LLUUID &session_id)
 	if (LLVoiceChannel::getCurrentVoiceChannel()->getState() == LLVoiceChannel::STATE_CALL_STARTED &&
 		LLVoiceChannel::getCurrentVoiceChannel()->getCallDirection() == LLVoiceChannel::OUTGOING_CALL)
 	{
-		
+
 		//*TODO get rid of duplicated code
 		LLSD mCallDialogPayload;
 		mCallDialogPayload["session_id"] = mSession->mSessionID;
@@ -1860,7 +2265,7 @@ void LLCallDialogManager::onVoiceChannelChangedInt(const LLUUID &session_id)
 		if(ocd)
 		{
 			ocd->show(mCallDialogPayload);
-		}	
+		}
 	}
 
 }
@@ -1893,7 +2298,7 @@ void LLCallDialogManager::onVoiceChannelStateChangedInt(const LLVoiceChannel::ES
 	mCallDialogPayload["ended_by_agent"] = ended_by_agent;
 
 	switch(new_state)
-	{			
+	{
 	case LLVoiceChannel::STATE_CALL_STARTED :
 		// do not show "Calling to..." if it is incoming call
 		if(direction == LLVoiceChannel::INCOMING_CALL)
@@ -1922,7 +2327,7 @@ void LLCallDialogManager::onVoiceChannelStateChangedInt(const LLVoiceChannel::ES
 	if(ocd)
 	{
 		ocd->show(mCallDialogPayload);
-	}	
+	}
 }
 
 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1948,9 +2353,9 @@ BOOL LLCallDialog::postBuild()
 {
 	if (!LLDockableFloater::postBuild() || !gToolBarView)
 		return FALSE;
-	
+
 	dockToToolbarButton("speak");
-	
+
 	return TRUE;
 }
 
@@ -1968,20 +2373,20 @@ LLDockControl::DocAt LLCallDialog::getDockControlPos(const std::string& toolbarB
 {
 	LLCommandId command_id(toolbarButtonName);
 	S32 toolbar_loc = gToolBarView->hasCommand(command_id);
-	
+
 	LLDockControl::DocAt doc_at = LLDockControl::TOP;
-	
+
 	switch (toolbar_loc)
 	{
 		case LLToolBarEnums::TOOLBAR_LEFT:
 			doc_at = LLDockControl::RIGHT;
 			break;
-			
+
 		case LLToolBarEnums::TOOLBAR_RIGHT:
 			doc_at = LLDockControl::LEFT;
 			break;
 	}
-	
+
 	return doc_at;
 }
 
@@ -1996,7 +2401,7 @@ LLCallDialog(payload)
 	if(instance && instance->getVisible())
 	{
 		instance->onCancel(instance);
-	}	
+	}
 }
 
 void LLCallDialog::draw()
@@ -2053,7 +2458,7 @@ bool LLCallDialog::lifetimeHasExpired()
 	if (mLifetimeTimer.getStarted())
 	{
 		F32 elapsed_time = mLifetimeTimer.getElapsedTimeF32();
-		if (elapsed_time > mLifetime) 
+		if (elapsed_time > mLifetime)
 		{
 			return true;
 		}
@@ -2095,7 +2500,7 @@ void LLOutgoingCallDialog::show(const LLSD& key)
 	}
 	else
 	{
-		getChild<LLUICtrl>("leaving")->setTextArg("[CURRENT_CHAT]", getString("localchat"));		
+		getChild<LLUICtrl>("leaving")->setTextArg("[CURRENT_CHAT]", getString("localchat"));
 	}
 
 	if (!mPayload["disconnected_channel_name"].asString().empty())
@@ -2119,7 +2524,7 @@ void LLOutgoingCallDialog::show(const LLSD& key)
 	{
 		callee_name = getString("anonymous");
 	}
-	
+
 	LLSD callee_id = mPayload["other_user_id"];
 	// Beautification:  Since you know who you called, just show display name
 	std::string title = callee_name;
@@ -2175,7 +2580,7 @@ void LLOutgoingCallDialog::show(const LLSD& key)
 		{
 			const std::string& nearby_str = mPayload["ended_by_agent"] ? NEARBY_P2P_BY_AGENT : NEARBY_P2P_BY_OTHER;
 			getChild<LLTextBox>(nearby_str)->setVisible(true);
-		} 
+		}
 		else
 		{
 			getChild<LLTextBox>("nearby")->setVisible(true);
@@ -2209,7 +2614,7 @@ void LLOutgoingCallDialog::onCancel(void* user_data)
 
 	LLUUID session_id = self->mPayload["session_id"].asUUID();
 	gIMMgr->endCall(session_id);
-	
+
 	self->closeFloater();
 }
 
@@ -2294,7 +2699,7 @@ BOOL LLIncomingCallDialog::postBuild()
         LL_INFOS("IMVIEW") << "IncomingCall: notify_box_type was not provided" << LL_ENDL;
         return TRUE;
     }
-	
+
 	// init notification's lifetime
 	std::istringstream ss( getString("lifetime") );
 	if (!(ss >> mLifetime))
@@ -2469,7 +2874,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload
 			if (session_name.empty())
 			{
 				LL_WARNS() << "Received an empty session name from a server" << LL_ENDL;
-				
+
 				switch(type){
 				case IM_SESSION_CONFERENCE_START:
 				case IM_SESSION_GROUP_START:
@@ -2487,17 +2892,17 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload
 						if (LLAvatarNameCache::get(caller_id, &av_name))
 						{
 							correct_session_name = av_name.getCompleteName();
-							correct_session_name.append(ADHOC_NAME_SUFFIX); 
+							correct_session_name.append(ADHOC_NAME_SUFFIX);
 						}
 					}
 					LL_INFOS("IMVIEW") << "Corrected session name is " << correct_session_name << LL_ENDL;
 					break;
-				default: 
+				default:
 					LL_WARNS("IMVIEW") << "Received an empty session name from a server and failed to generate a new proper session name" << LL_ENDL;
 					break;
 				}
 			}
-			
+
 			gIMMgr->addSession(correct_session_name, type, session_id, true);
 
 			std::string url = gAgent.getRegion()->getCapability(
@@ -2509,7 +2914,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload
                     boost::bind(&chatterBoxInvitationCoro, url,
                     session_id, inv_type));
 
-				// send notification message to the corresponding chat 
+				// send notification message to the corresponding chat
 				if (payload["notify_box_type"].asString() == "VoiceInviteGroup" || payload["notify_box_type"].asString() == "VoiceInviteAdHoc")
 				{
 					LLStringUtil::format_map_t string_args;
@@ -2544,7 +2949,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload
 			data["session-id"] = session_id;
 
             LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data,
-                "Invitation declined", 
+                "Invitation declined",
                 "Invitation decline failed.");
 		}
 	}
@@ -2564,7 +2969,7 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response)
 	EInstantMessage type = (EInstantMessage)payload["type"].asInteger();
 	LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger();
 	S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
-	switch(option) 
+	switch(option)
 	{
 	case 0: // accept
 		{
@@ -2608,7 +3013,7 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response)
 		}
 	}
 	/* FALLTHROUGH */
-	
+
 	case 1: // decline
 	{
 		if (type == IM_SESSION_P2P_INVITE)
@@ -2624,8 +3029,8 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response)
 			LLSD data;
 			data["method"] = "decline invitation";
 			data["session-id"] = session_id;
-            LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, 
-                "Invitation declined.", 
+            LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data,
+                "Invitation declined.",
                 "Invitation decline failed.");
 		}
 	}
@@ -2634,7 +3039,7 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response)
 	gIMMgr->clearPendingInvitation(session_id);
 	break;
 	}
-	
+
 	return false;
 }
 
@@ -2648,9 +3053,11 @@ LLIMMgr::LLIMMgr()
 	mPendingAgentListUpdates = LLSD::emptyMap();
 
 	LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&LLFloaterIMSession::sRemoveTypingIndicator, _1));
+
+	gSavedPerAccountSettings.declareBOOL("FetchGroupChatHistory", TRUE, "Fetch recent messages from group chat servers when a group window opens", LLControlVariable::PERSIST_ALWAYS);
 }
 
-// Add a message to a session. 
+// Add a message to a session.
 void LLIMMgr::addMessage(
 	const LLUUID& session_id,
 	const LLUUID& target_id,
@@ -2662,7 +3069,8 @@ void LLIMMgr::addMessage(
 	U32 parent_estate_id,
 	const LLUUID& region_id,
 	const LLVector3& position,
-	bool is_region_msg)
+    bool is_region_msg,
+    U32 timestamp)      // May be zero
 {
 	LLUUID other_participant_id = target_id;
 
@@ -2682,7 +3090,7 @@ void LLIMMgr::addMessage(
 		name_is_setted = true;
 	}
 	bool skip_message = false;
-	bool from_linden = LLMuteList::getInstance()->isLinden(from);
+	bool from_linden = LLMuteList::isLinden(from);
     if (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && !from_linden)
 	{
 		// Evaluate if we need to skip this message when that setting is true (default is false)
@@ -2749,6 +3157,14 @@ void LLIMMgr::addMessage(
 				return;
 			}
 
+            // Fetch group chat history, enabled by default.
+            if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory"))
+            {
+                std::string chat_url = gAgent.getRegion()->getCapability("ChatSessionRequest");
+                LLCoros::instance().launch("chatterBoxHistoryCoro",
+                    boost::bind(&chatterBoxHistoryCoro, chat_url, session_id, from, msg, timestamp));
+            }
+
 			//Play sound for new conversations
 			if (!skip_message & !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation") == TRUE))
 			{
@@ -2764,7 +3180,7 @@ void LLIMMgr::addMessage(
 
 	if (!LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !skip_message)
 	{
-		LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, msg, true, is_region_msg);
+		LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, msg, true, is_region_msg, timestamp);
 	}
 
 	// Open conversation floater if offline messages are present
@@ -2779,7 +3195,7 @@ void LLIMMgr::addMessage(
 void LLIMMgr::addSystemMessage(const LLUUID& session_id, const std::string& message_name, const LLSD& args)
 {
 	LLUIString message;
-	
+
 	// null session id means near me (chat history)
 	if (session_id.isNull())
 	{
@@ -2819,7 +3235,7 @@ void LLIMMgr::addSystemMessage(const LLUUID& session_id, const std::string& mess
 S32 LLIMMgr::getNumberOfUnreadIM()
 {
 	std::map<LLUUID, LLIMModel::LLIMSession*>::iterator it;
-	
+
 	S32 num = 0;
 	for(it = LLIMModel::getInstance()->mId2SessionMap.begin(); it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it)
 	{
@@ -2846,7 +3262,7 @@ void LLIMMgr::autoStartCallOnStartup(const LLUUID& session_id)
 {
 	LLIMModel::LLIMSession *session = LLIMModel::getInstance()->findIMSession(session_id);
 	if (!session) return;
-	
+
 	if (session->mSessionInitialized)
 	{
 		startCall(session_id);
@@ -2854,7 +3270,7 @@ void LLIMMgr::autoStartCallOnStartup(const LLUUID& session_id)
 	else
 	{
 		session->mStartCallOnInitialize = true;
-	}	
+	}
 }
 
 LLUUID LLIMMgr::addP2PSession(const std::string& name,
@@ -2891,7 +3307,7 @@ LLUUID LLIMMgr::addSession(
 	return session_id;
 }
 
-// Adds a session using the given session_id.  If the session already exists 
+// Adds a session using the given session_id.  If the session already exists
 // the dialog type is assumed correct. Returns the uuid of the session.
 LLUUID LLIMMgr::addSession(
 	const std::string& name,
@@ -2954,9 +3370,9 @@ LLUUID LLIMMgr::addSession(
 
 	//we don't need to show notes about online/offline, mute/unmute users' statuses for existing sessions
 	if (!new_session) return session_id;
-	
+
     LL_INFOS("IMVIEW") << "LLIMMgr::addSession, new session added, name = " << name << ", session id = " << session_id << LL_ENDL;
-    
+
 	//Per Plan's suggestion commented "explicit offline status warning" out to make Dessie happier (see EXT-3609)
 	//*TODO After February 2010 remove this commented out line if no one will be missing that warning
 	//noteOfflineUsers(session_id, floater, ids);
@@ -2986,7 +3402,7 @@ bool LLIMMgr::leaveSession(const LLUUID& session_id)
 void LLIMMgr::removeSession(const LLUUID& session_id)
 {
 	llassert_always(hasSession(session_id));
-	
+
 	clearPendingInvitation(session_id);
 	clearPendingAgentListUpdates(session_id);
 
@@ -2998,9 +3414,9 @@ void LLIMMgr::removeSession(const LLUUID& session_id)
 }
 
 void LLIMMgr::inviteToSession(
-	const LLUUID& session_id, 
-	const std::string& session_name, 
-	const LLUUID& caller_id, 
+	const LLUUID& session_id,
+	const std::string& session_name,
+	const LLUUID& caller_id,
 	const std::string& caller_name,
 	EInstantMessage type,
 	EInvitationType inv_type,
@@ -3012,7 +3428,7 @@ void LLIMMgr::inviteToSession(
 	std::string question_type = "VoiceInviteQuestionDefault";
 
 	BOOL voice_invite = FALSE;
-	bool is_linden = LLMuteList::getInstance()->isLinden(caller_name);
+	bool is_linden = LLMuteList::isLinden(caller_name);
 
 
 	if(type == IM_SESSION_P2P_INVITE)
@@ -3117,22 +3533,22 @@ void LLIMMgr::inviteToSession(
 	{
 		if (caller_name.empty())
 		{
-			LLAvatarNameCache::get(caller_id, 
+			LLAvatarNameCache::get(caller_id,
 				boost::bind(&LLIMMgr::onInviteNameLookup, payload, _1, _2));
 		}
 		else
 		{
 			LLFloaterReg::showInstance("incoming_call", payload, FALSE);
 		}
-		
-		// Add the caller to the Recent List here (at this point 
+
+		// Add the caller to the Recent List here (at this point
 		// "incoming_call" floater is shown and the recipient can
 		// reject the call), because even if a recipient will reject
 		// the call, the caller should be added to the recent list
 		// anyway. STORM-507.
 		if(type == IM_SESSION_P2P_INVITE)
 			LLRecentPeople::instance().add(caller_id);
-		
+
 		mPendingInvitations[session_id.asString()] = LLSD();
 	}
 }
@@ -3329,7 +3745,7 @@ bool LLIMMgr::startCall(const LLUUID& session_id, LLVoiceChannel::EDirection dir
 {
 	LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id);
 	if (!voice_channel) return false;
-	
+
 	voice_channel->setCallDirection(direction);
 	voice_channel->activate();
 	return true;
@@ -3447,7 +3863,7 @@ void LLIMMgr::noteMutedUsers(const LLUUID& session_id,
 	if(count > 0)
 	{
 		LLIMModel* im_model = LLIMModel::getInstance();
-		
+
 		for(S32 i = 0; i < count; ++i)
 		{
 			if( ml->isMuted(ids.at(i)) )
@@ -3525,7 +3941,15 @@ class LLViewerChatterBoxSessionStartReply : public LLHTTPNode
 				if ( body.has("session_info") )
 				{
 					im_floater->processSessionUpdate(body["session_info"]);
-				}
+
+                    // Send request for chat history, if enabled.
+                    if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory"))
+                    {
+                        std::string url = gAgent.getRegion()->getCapability("ChatSessionRequest");
+                        LLCoros::instance().launch("chatterBoxHistoryCoro",
+                            boost::bind(&chatterBoxHistoryCoro, url, session_id, "", "", 0));
+                    }
+                }
 			}
 
 			gIMMgr->clearPendingAgentListUpdates(session_id);
@@ -3654,7 +4078,7 @@ class LLViewerChatterBoxInvitation : public LLHTTPNode
 			LLUUID session_id = message_params["id"].asUUID();
 			std::vector<U8> bin_bucket = message_params["data"]["binary_bucket"].asBinary();
 			U8 offline = (U8)message_params["offline"].asInteger();
-			
+
 			time_t timestamp =
 				(time_t) message_params["timestamp"].asInteger();
 
@@ -3691,7 +4115,9 @@ class LLViewerChatterBoxInvitation : public LLHTTPNode
 				IM_SESSION_INVITE,
 				message_params["parent_estate_id"].asInteger(),
 				message_params["region_id"].asUUID(),
-				ll_vector3_from_sd(message_params["position"]));
+				ll_vector3_from_sd(message_params["position"]),
+                false,      // is_region_message
+                timestamp);
 
 			if (LLMuteList::getInstance()->isMuted(from_id, name, LLMute::flagTextChat))
 			{
@@ -3717,8 +4143,8 @@ class LLViewerChatterBoxInvitation : public LLHTTPNode
 			}
 
 			gIMMgr->inviteToSession(
-				input["body"]["session_id"].asUUID(), 
-				input["body"]["session_name"].asString(), 
+				input["body"]["session_id"].asUUID(),
+				input["body"]["session_name"].asString(),
 				input["body"]["from_id"].asUUID(),
 				input["body"]["from_name"].asString(),
 				IM_SESSION_INVITE,
@@ -3727,8 +4153,8 @@ class LLViewerChatterBoxInvitation : public LLHTTPNode
 		else if ( input["body"].has("immediate") )
 		{
 			gIMMgr->inviteToSession(
-				input["body"]["session_id"].asUUID(), 
-				input["body"]["session_name"].asString(), 
+				input["body"]["session_id"].asUUID(),
+				input["body"]["session_name"].asString(),
 				input["body"]["from_id"].asUUID(),
 				input["body"]["from_name"].asString(),
 				IM_SESSION_INVITE,
diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h
index 326e8f22e3061829ca0edddbaaf3200c5ed69a4c..946eb02f26bf6a50abbd10881512af61b9040d06 100644
--- a/indra/newview/llimview.h
+++ b/indra/newview/llimview.h
@@ -42,6 +42,7 @@ class LLAvatarName;
 class LLFriendObserver;
 class LLCallDialogManager;	
 class LLIMSpeakerMgr;
+
 /**
  * Timeout Timer for outgoing Ad-Hoc/Group IM sessions which being initialized by the server
  */
@@ -63,11 +64,14 @@ class LLSessionTimeoutTimer : public LLEventTimer
 class LLIMModel :  public LLSingleton<LLIMModel>
 {
 	LLSINGLETON(LLIMModel);
+
 public:
 
-	struct LLIMSession : public boost::signals2::trackable
+    typedef std::list<LLSD> chat_message_list_t;
+
+    struct LLIMSession : public boost::signals2::trackable
 	{
-		typedef enum e_session_type
+        typedef enum e_session_type
 		{   // for now we have 4 predefined types for a session
 			P2P_SESSION,
 			GROUP_SESSION,
@@ -75,15 +79,23 @@ class LLIMModel :  public LLSingleton<LLIMModel>
 			NONE_SESSION,
 		} SType;
 
-		LLIMSession(const LLUUID& session_id, const std::string& name, 
+		LLIMSession(const LLUUID& session_id, const std::string& name,
 			const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg);
 		virtual ~LLIMSession();
 
 		void sessionInitReplyReceived(const LLUUID& new_session_id);
-		void addMessagesFromHistory(const std::list<LLSD>& history);
-		void addMessage(const std::string& from, const LLUUID& from_id, const std::string& utf8_text, const std::string& time, const bool is_history = false, bool is_region_msg = false);
-		void onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction);
-		
+		void addMessagesFromHistoryCache(const std::list<LLSD>& history);        // From local file
+        void addMessagesFromServerHistory(const LLSD& history, const std::string& target_from, const std::string& target_message, U32 timestamp);  // From chat server
+		void addMessage(const std::string& from,
+                        const LLUUID& from_id,
+                        const std::string& utf8_text,
+                        const std::string& time,
+                        const bool is_history,
+                        const bool is_region_msg,
+                        U32 timestamp);
+
+        void onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction);
+
 		/** @deprecated */
 		static void chatFromLogFile(LLLogChat::ELogLineType type, const LLSD& msg, void* userdata);
 
@@ -112,6 +124,10 @@ class LLIMModel :  public LLSingleton<LLIMModel>
 		uuid_vec_t mInitialTargetIDs;
 		std::string mHistoryFileName;
 
+        // Saved messages from the last minute of history read from the local group chat cache file
+        std::string mLastHistoryCacheDateTime;
+        chat_message_list_t mLastHistoryCacheMsgs;
+
 		// connection to voice channel state change signal
 		boost::signals2::connection mVoiceChannelStateChangeConnection;
 
@@ -121,7 +137,7 @@ class LLIMModel :  public LLSingleton<LLIMModel>
 		// does include all incoming messages
 		S32 mNumUnread;
 
-		std::list<LLSD> mMsgs;
+        chat_message_list_t mMsgs;
 
 		LLVoiceChannel* mVoiceChannel;
 		LLIMSpeakerMgr* mSpeakers;
@@ -208,29 +224,43 @@ class LLIMModel :  public LLSingleton<LLIMModel>
 	 * and also saved into a file if log2file is specified.
 	 * It sends new message signal for each added message.
 	 */
-	bool addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& other_participant_id, const std::string& utf8_text, bool log2file = true, bool is_region_msg = false);
+	void addMessage(const LLUUID& session_id,
+                    const std::string& from,
+                    const LLUUID& other_participant_id,
+                    const std::string& utf8_text,
+                    bool log2file = true,
+                    bool is_region_msg = false,
+                    U32 time_stamp = 0);
+
+    void processAddingMessage(const LLUUID& session_id,
+                    const std::string& from,
+                    const LLUUID& from_id,
+                    const std::string& utf8_text,
+                    bool log2file,
+                    bool is_region_msg,
+                    U32 time_stamp);
 
 	/**
 	 * Similar to addMessage(...) above but won't send a signal about a new message added
 	 */
-	LLIMModel::LLIMSession* addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, 
-		const std::string& utf8_text, bool log2file = true, bool is_region_msg = false);
+	LLIMModel::LLIMSession* addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
+		const std::string& utf8_text, bool log2file = true, bool is_region_msg = false, U32 timestamp = 0);
 
 	/**
 	 * Add a system message to an IM Model
 	 */
-	bool proccessOnlineOfflineNotification(const LLUUID& session_id, const std::string& utf8_text);
+	void proccessOnlineOfflineNotification(const LLUUID& session_id, const std::string& utf8_text);
 
 	/**
-	 * Get a session's name. 
-	 * For a P2P chat - it's an avatar's name, 
+	 * Get a session's name.
+	 * For a P2P chat - it's an avatar's name,
 	 * For a group chat - it's a group's name
 	 * For an incoming ad-hoc chat - is received from the server and is in a from of "<Avatar's name> Conference"
 	 *	It is updated in LLIMModel::LLIMSession's constructor to localize the "Conference".
 	 */
 	const std::string getName(const LLUUID& session_id) const;
 
-	/** 
+	/**
 	 * Get number of unread messages in a session with session_id
 	 * Returns -1 if the session with session_id doesn't exist
 	 */
@@ -282,7 +312,7 @@ class LLIMModel :  public LLSingleton<LLIMModel>
 	bool logToFile(const std::string& file_name, const std::string& from, const LLUUID& from_id, const std::string& utf8_text);
 
 private:
-	
+
 	/**
 	 * Populate supplied std::list with messages starting from index specified by start_index without
 	 * emitting no unread messages signal.
@@ -292,7 +322,7 @@ class LLIMModel :  public LLSingleton<LLIMModel>
 	/**
 	 * Add message to a list of message associated with session specified by session_id
 	 */
-	bool addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, bool is_region_msg = false);
+	bool addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, bool is_region_msg, U32 timestamp);
 
 };
 
@@ -334,7 +364,8 @@ class LLIMMgr : public LLSingleton<LLIMMgr>
 					U32 parent_estate_id = 0,
 					const LLUUID& region_id = LLUUID::null,
 					const LLVector3& position = LLVector3::zero,
-					bool is_region_msg = false);
+                    bool is_region_msg = false,
+                    U32 timestamp = 0);
 
 	void addSystemMessage(const LLUUID& session_id, const std::string& message_name, const LLSD& args);
 
diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp
index af1c93f38379d2b38bc62dab4966c17f1589bdc1..67240ac7e7ff0b1bafcf14646cf4b3c92a8a4cec 100644
--- a/indra/newview/llinventoryfunctions.cpp
+++ b/indra/newview/llinventoryfunctions.cpp
@@ -529,7 +529,11 @@ BOOL get_is_item_worn(const LLUUID& id)
 	const LLViewerInventoryItem* item = gInventory.getItem(id);
 	if (!item)
 		return FALSE;
-
+    
+    if (item->getIsLinkType() && !gInventory.getItem(item->getLinkedUUID()))
+    {
+        return FALSE;
+    }
 	// Consider the item as worn if it has links in COF.
 	if (LLAppearanceMgr::instance().isLinkedInCOF(id))
 	{
@@ -787,7 +791,7 @@ void show_item_original(const LLUUID& item_uuid)
         LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel();
         if (main_inventory)
         {
-            main_inventory->resetFilters();
+            main_inventory->resetAllItemsFilters();
         }
         reset_inventory_filter();
 
@@ -795,6 +799,7 @@ void show_item_original(const LLUUID& item_uuid)
         {
             LLFloaterReg::toggleInstanceOrBringToFront("inventory");
         }
+        sidepanel_inventory->showInventoryPanel();
 
         const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX);
         if (gInventory.isObjectDescendentOf(gInventory.getLinkedItemID(item_uuid), inbox_id))
@@ -2650,7 +2655,12 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root
     }
     else
     {
-        std::copy(selected_uuid_set.begin(), selected_uuid_set.end(), std::back_inserter(ids));
+        for (std::set<LLFolderViewItem*>::iterator it = selected_items.begin(), end_it = selected_items.end();
+            it != end_it;
+            ++it)
+        {
+            ids.push_back(static_cast<LLFolderViewModelItemInventory*>((*it)->getViewModelItem())->getUUID());
+        }
     }
 
     // Check for actions that get handled in bulk
@@ -2711,7 +2721,7 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root
     }
     else if ("ungroup_folder_items" == action)
     {
-        if (selected_uuid_set.size() == 1)
+        if (ids.size() == 1)
         {
             LLInventoryCategory* inv_cat = gInventory.getCategory(*ids.begin());
             if (!inv_cat || LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType()))
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp
index d712abec34c88c6381d67c0187eaebea9f3a2945..0bbf201dc6375bca227934c24877741da4f79509 100644
--- a/indra/newview/llinventorymodel.cpp
+++ b/indra/newview/llinventorymodel.cpp
@@ -1636,6 +1636,9 @@ void LLInventoryModel::deleteObject(const LLUUID& id, bool fix_broken_links, boo
 		LL_WARNS(LOG_INV) << "Deleting non-existent object [ id: " << id << " ] " << LL_ENDL;
 		return;
 	}
+
+    //collect the links before removing the item from mItemMap
+    LLInventoryModel::item_array_t links = collectLinksTo(id);
 	
 	LL_DEBUGS(LOG_INV) << "Deleting inventory object " << id << LL_ENDL;
 	mLastItem = NULL;
@@ -1693,7 +1696,7 @@ void LLInventoryModel::deleteObject(const LLUUID& id, bool fix_broken_links, boo
 	// update is getting broken link info separately.
 	if (fix_broken_links && !is_link_type)
 	{
-		updateLinkedObjectsFromPurge(id);
+        rebuildLinkItems(links);
 	}
 	obj = nullptr; // delete obj
 	if (do_notify_observers)
@@ -1702,26 +1705,25 @@ void LLInventoryModel::deleteObject(const LLUUID& id, bool fix_broken_links, boo
 	}
 }
 
-void LLInventoryModel::updateLinkedObjectsFromPurge(const LLUUID &baseobj_id)
+void LLInventoryModel::rebuildLinkItems(LLInventoryModel::item_array_t& items)
 {
-	LLInventoryModel::item_array_t item_array = collectLinksTo(baseobj_id);
-
-	// REBUILD is expensive, so clear the current change list first else
-	// everything else on the changelist will also get rebuilt.
-	if (item_array.size() > 0)
-	{
-		notifyObservers();
-		for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin();
-			iter != item_array.end();
-			iter++)
-		{
-			const LLViewerInventoryItem *linked_item = (*iter);
-			const LLUUID &item_id = linked_item->getUUID();
-			if (item_id == baseobj_id) continue;
-			addChangedMask(LLInventoryObserver::REBUILD, item_id);
-		}
-		notifyObservers();
-	}
+    // REBUILD is expensive, so clear the current change list first else
+    // everything else on the changelist will also get rebuilt.
+    if (items.size() > 0)
+    {
+        notifyObservers();
+        for (LLInventoryModel::item_array_t::const_iterator iter = items.begin();
+            iter != items.end();
+            iter++)
+        {
+            const LLViewerInventoryItem *linked_item = (*iter);
+            if (linked_item)
+            {
+                addChangedMask(LLInventoryObserver::REBUILD, linked_item->getUUID());
+            }
+        }
+        notifyObservers();
+    }
 }
 
 // Add/remove an observer. If the observer is destroyed, be sure to
@@ -1951,18 +1953,20 @@ void LLInventoryModel::cache(
 		items,
 		INCLUDE_TRASH,
 		can_cache);
-	std::string inventory_filename = getInvCacheAddres(agent_id);
-	saveToFile(inventory_filename, categories, items);
-	std::string gzip_filename(inventory_filename);
+    // Use temporary file to avoid potential conflicts with other
+    // instances (even a 'read only' instance unzips into a file)
+    std::string temp_file = gDirUtilp->getTempFilename();
+	saveToFile(temp_file, categories, items);
+    std::string gzip_filename = getInvCacheAddres(agent_id);
 	gzip_filename.append(".gz");
-	if(gzip_file(inventory_filename, gzip_filename))
+	if(gzip_file(temp_file, gzip_filename))
 	{
-		LL_DEBUGS(LOG_INV) << "Successfully compressed " << inventory_filename << LL_ENDL;
-		LLFile::remove(inventory_filename);
+		LL_DEBUGS(LOG_INV) << "Successfully compressed " << temp_file << " to " << gzip_filename << LL_ENDL;
+		LLFile::remove(temp_file);
 	}
 	else
 	{
-		LL_WARNS(LOG_INV) << "Unable to compress " << inventory_filename << LL_ENDL;
+		LL_WARNS(LOG_INV) << "Unable to compress " << temp_file << " into " << gzip_filename << LL_ENDL;
 	}
 }
 
@@ -3035,6 +3039,7 @@ bool LLInventoryModel::saveToFile(const std::string& filename,
                 return false;
             }
         }
+        fileXML.flush();
 
         fileXML.close();
 
diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h
index c4133ff9bb050fc0f7cae244c168fe20bc982bb6..685c2c0fe52dcbafb7ee2350e3c416de2a3f2874 100644
--- a/indra/newview/llinventorymodel.h
+++ b/indra/newview/llinventorymodel.h
@@ -445,7 +445,7 @@ class LLInventoryModel
 	void checkTrashOverflow();
 
 protected:
-	void updateLinkedObjectsFromPurge(const LLUUID& baseobj_id);
+    void rebuildLinkItems(LLInventoryModel::item_array_t& items);
 	
 	//--------------------------------------------------------------------
 	// Reorder
diff --git a/indra/newview/llinventorymodelbackgroundfetch.cpp b/indra/newview/llinventorymodelbackgroundfetch.cpp
index 406c8b89d0b48d0667d1167d4a19339282eb630a..4a9b471a47e22559c94f47ae71f84f074421162b 100644
--- a/indra/newview/llinventorymodelbackgroundfetch.cpp
+++ b/indra/newview/llinventorymodelbackgroundfetch.cpp
@@ -363,7 +363,7 @@ void LLInventoryModelBackgroundFetch::bulkFetch()
 	//If there are items in mFetchQueue, we want to check the time since the last bulkFetch was 
 	//sent.  If it exceeds our retry time, go ahead and fire off another batch.  
 	LLViewerRegion * region(gAgent.getRegion());
-	if (! region || gDisconnected)
+	if (! region || gDisconnected || LLApp::isExiting())
 	{
 		return;
 	}
diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp
index c065c76dca247cf81a01acf6c1cbef594668f686..8029486d6f589e7d982b2851f99f096baaebd58b 100644
--- a/indra/newview/llinventorypanel.cpp
+++ b/indra/newview/llinventorypanel.cpp
@@ -1627,6 +1627,7 @@ void LLInventoryPanel::purgeSelectedItems()
     if (inventory_selected.empty()) return;
     LLSD args;
     S32 count = inventory_selected.size();
+    std::vector<LLUUID> selected_items;
     for (std::set<LLFolderViewItem*>::const_iterator it = inventory_selected.begin(), end_it = inventory_selected.end();
         it != end_it;
         ++it)
@@ -1636,27 +1637,23 @@ void LLInventoryPanel::purgeSelectedItems()
         LLInventoryModel::item_array_t items;
         gInventory.collectDescendents(item_id, cats, items, LLInventoryModel::INCLUDE_TRASH);
         count += items.size() + cats.size();
+        selected_items.push_back(item_id);
     }
     args["COUNT"] = count;
-    LLNotificationsUtil::add("PurgeSelectedItems", args, LLSD(), boost::bind(&LLInventoryPanel::callbackPurgeSelectedItems, this, _1, _2));
+    LLNotificationsUtil::add("PurgeSelectedItems", args, LLSD(), boost::bind(callbackPurgeSelectedItems, _1, _2, selected_items));
 }
 
-void LLInventoryPanel::callbackPurgeSelectedItems(const LLSD& notification, const LLSD& response)
+// static
+void LLInventoryPanel::callbackPurgeSelectedItems(const LLSD& notification, const LLSD& response, const std::vector<LLUUID> inventory_selected)
 {
-    if (!mFolderRoot.get()) return;
-
     S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
     if (option == 0)
     {
-        const std::set<LLFolderViewItem*> inventory_selected = mFolderRoot.get()->getSelectionList();
         if (inventory_selected.empty()) return;
 
-        std::set<LLFolderViewItem*>::const_iterator it = inventory_selected.begin();
-        const std::set<LLFolderViewItem*>::const_iterator it_end = inventory_selected.end();
-        for (; it != it_end; ++it)
+        for (auto it : inventory_selected)
         {
-            LLUUID item_id = static_cast<LLFolderViewModelItemInventory*>((*it)->getViewModelItem())->getUUID();
-            remove_inventory_object(item_id, NULL);
+            remove_inventory_object(it, NULL);
         }
     }
 }
diff --git a/indra/newview/llinventorypanel.h b/indra/newview/llinventorypanel.h
index 552c61b915dc69daae071e28f126d37f53d1fd71..2c782a5ea743326fecba219b61d9b07dfafd2133 100644
--- a/indra/newview/llinventorypanel.h
+++ b/indra/newview/llinventorypanel.h
@@ -260,7 +260,7 @@ class LLInventoryPanel : public LLPanel
     // Clean up stuff when the folder root gets deleted
     void clearFolderRoot();
 
-    void callbackPurgeSelectedItems(const LLSD& notification, const LLSD& response);
+    static void callbackPurgeSelectedItems(const LLSD& notification, const LLSD& response, const std::vector<LLUUID> inventory_selected);
 
 protected:
 	void openStartFolderOrMyInventory(); // open the first level of inventory
diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp
index fb9885b454d930c13aba8ab5b93a0d2663519020..ba82ff0b0f685b35df8864ac152ab4a72319537d 100644
--- a/indra/newview/lllogchat.cpp
+++ b/indra/newview/lllogchat.cpp
@@ -62,6 +62,7 @@
 const S32 LOG_RECALL_SIZE = 2048;
 
 const std::string LL_IM_TIME("time");
+const std::string LL_IM_DATE_TIME("datetime");
 const std::string LL_IM_TEXT("message");
 const std::string LL_IM_FROM("from");
 const std::string LL_IM_FROM_ID("from_id");
@@ -133,14 +134,14 @@ void append_to_last_message(std::list<LLSD>& messages, const std::string& line)
 	messages.back()[LL_IM_TEXT] = im_text;
 }
 
-std::string remove_utf8_bom(const char* buf)
+const char* remove_utf8_bom(const char* buf)
 {
-	std::string res(buf);
-	if (res[0] == (char)0xEF && res[1] == (char)0xBB && res[2] == (char)0xBF)
-	{
-		res.erase(0, 3);
+    const char* start = buf;
+	if (start[0] == (char)0xEF && start[1] == (char)0xBB && start[2] == (char)0xBF)
+	{   // If string starts with the magic bytes, return pointer after it.
+        start += 3;
 	}
-	return res;
+	return start;
 }
 
 class LLLogChatTimeScanner: public LLSingleton<LLLogChatTimeScanner>
@@ -315,7 +316,7 @@ std::string LLLogChat::cleanFileName(std::string filename)
 	return filename;
 }
 
-std::string LLLogChat::timestamp(bool withdate)
+std::string LLLogChat::timestamp2LogString(U32 timestamp, bool withdate)
 {
 	std::string timeStr;
 	if (withdate)
@@ -333,7 +334,14 @@ std::string LLLogChat::timestamp(bool withdate)
 	}
 
 	LLSD substitution;
-	substitution["datetime"] = (S32)time_corrected();
+    if (timestamp == 0)
+    {
+        substitution["datetime"] = (S32)time_corrected();
+    }
+    else
+    {   // timestamp is correct utc already
+        substitution["datetime"] = (S32)timestamp;
+    }
 
 	LLStringUtil::format (timeStr, substitution);
 	return timeStr;
@@ -355,7 +363,7 @@ void LLLogChat::saveHistory(const std::string& filename,
 		llassert(tmp_filename.size());
 		return;
 	}
-	
+
 	llofstream file(LLLogChat::makeLogFileName(filename).c_str(), std::ios_base::app);
 	if (!file.is_open())
 	{
@@ -366,7 +374,7 @@ void LLLogChat::saveHistory(const std::string& filename,
 	LLSD item;
 
 	if (gSavedPerAccountSettings.getBOOL("LogTimestamp"))
-		 item["time"] = LLLogChat::timestamp(gSavedPerAccountSettings.getBOOL("LogTimestampDate"));
+		 item["time"] = LLLogChat::timestamp2LogString(0, gSavedPerAccountSettings.getBOOL("LogTimestampDate"));
 
 	item["from_id"]	= from_id;
 	item["message"]	= line;
@@ -374,7 +382,7 @@ void LLLogChat::saveHistory(const std::string& filename,
 	//adding "Second Life:" for all system messages to make chat log history parsing more reliable
 	if (from.empty() && from_id.isNull())
 	{
-		item["from"] = SYSTEM_FROM; 
+		item["from"] = SYSTEM_FROM;
 	}
 	else
 	{
@@ -393,37 +401,60 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& m
 {
 	if (file_name.empty())
 	{
-		LL_WARNS("LLLogChat::loadChatHistory") << "Session name is Empty!" << LL_ENDL;
+		LL_WARNS("LLLogChat::loadChatHistory") << "Local history file name is empty!" << LL_ENDL;
 		return ;
 	}
 
 	bool load_all_history = load_params.has("load_all_history") ? load_params["load_all_history"].asBoolean() : false;
 
-	LLFILE* fptr = LLFile::fopen(LLLogChat::makeLogFileName(file_name), "r");/*Flawfinder: ignore*/
+    // Stat the file to find it and get the last history entry time
+    llstat stat_data;
+
+    std::string log_file_name = LLLogChat::makeLogFileName(file_name);
+    LL_DEBUGS("ChatHistory") << "First attempt to stat chat history file " << log_file_name << LL_ENDL;
+
+    S32 no_stat = LLFile::stat(log_file_name, &stat_data);
+
+    if (no_stat)
+    {
+        if (is_group)
+        {
+            std::string old_name(file_name);
+            old_name.erase(old_name.size() - GROUP_CHAT_SUFFIX.size());     // trim off " (group)"
+            log_file_name = LLLogChat::makeLogFileName(old_name);
+            LL_DEBUGS("ChatHistory") << "Attempting to stat adjusted chat history file " << log_file_name << LL_ENDL;
+            no_stat = LLFile::stat(log_file_name, &stat_data);
+            if (!no_stat)
+            {   // Found it without "(group)", copy to new naming style.  We already have the mod time in stat_data
+                log_file_name = LLLogChat::makeLogFileName(file_name);
+                LL_DEBUGS("ChatHistory") << "Attempt to stat copied history file " << log_file_name << LL_ENDL;
+                LLFile::copy(LLLogChat::makeLogFileName(old_name), log_file_name);
+            }
+        }
+        if (no_stat)
+        {
+            log_file_name = LLLogChat::oldLogFileName(file_name);
+            LL_DEBUGS("ChatHistory") << "Attempt to stat old history file name " << log_file_name << LL_ENDL;
+            no_stat = LLFile::stat(log_file_name, &stat_data);
+            if (no_stat)
+            {
+                LL_DEBUGS("ChatHistory") << "No previous conversation log file found for " << file_name << LL_ENDL;
+                return;						//No previous conversation with this name.
+            }
+        }
+    }
+
+    // If we got here, we managed to stat the file.
+    // Open the file to read
+    LLFILE* fptr = LLFile::fopen(log_file_name, "r");       /*Flawfinder: ignore*/
 	if (!fptr)
-	{
-		if (is_group)
-		{
-			std::string old_name(file_name);
-			old_name.erase(old_name.size() - GROUP_CHAT_SUFFIX.size());
-			fptr = LLFile::fopen(LLLogChat::makeLogFileName(old_name), "r");
-			if (fptr)
-			{
-				fclose(fptr);
-				LLFile::copy(LLLogChat::makeLogFileName(old_name), LLLogChat::makeLogFileName(file_name));
-			}
-			fptr = LLFile::fopen(LLLogChat::makeLogFileName(file_name), "r");
-		}
-		if (!fptr)
-		{
-			fptr = LLFile::fopen(LLLogChat::oldLogFileName(file_name), "r");/*Flawfinder: ignore*/
-			if (!fptr)
-			{
-				return;						//No previous conversation with this name.
-			}
-		}
+	{   // Ok, this is strange but not really tragic in the big picture of things
+        LL_WARNS("ChatHistory") << "Unable to read file " << log_file_name << " after stat was successful" << LL_ENDL;
+        return;
 	}
 
+    S32 save_num_messages = messages.size();
+
 	char buffer[LOG_RECALL_SIZE];		/*Flawfinder: ignore*/
 	char *bptr;
 	S32 len;
@@ -441,6 +472,7 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& m
 	while (fgets(buffer, LOG_RECALL_SIZE, fptr)  && !feof(fptr))
 	{
 		len = strlen(buffer) - 1;		/*Flawfinder: ignore*/
+        // backfill any end of line characters with nulls
 		for (bptr = (buffer + len); (*bptr == '\n' || *bptr == '\r') && bptr>buffer; bptr--)	*bptr='\0';
 
 		if (firstline)
@@ -473,6 +505,10 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& m
 		}
 	}
 	fclose(fptr);
+
+    LL_DEBUGS("ChatHistory") << "Read " << (messages.size() - save_num_messages)
+        << " messages of chat history from " << log_file_name
+        << " file mod time " << (F64)stat_data.st_mtime << LL_ENDL;
 }
 
 bool LLLogChat::historyThreadsFinished(LLUUID session_id)
@@ -837,7 +873,8 @@ bool LLLogChat::isTranscriptFileFound(std::string fullname)
 		{
 			//matching a timestamp
 			boost::match_results<std::string::const_iterator> matches;
-			if (ll_regex_match(remove_utf8_bom(buffer), matches, TIMESTAMP))
+            std::string line(remove_utf8_bom(buffer));
+			if (ll_regex_match(line, matches, TIMESTAMP))
 			{
 				result = true;
 			}
@@ -847,7 +884,7 @@ bool LLLogChat::isTranscriptFileFound(std::string fullname)
 	return result;
 }
 
-//*TODO mark object's names in a special way so that they will be distinguishable form avatar name 
+//*TODO mark object's names in a special way so that they will be distinguishable form avatar name
 //which are more strict by its nature (only firstname and secondname)
 //Example, an object's name can be written like "Object <actual_object's_name>"
 void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const
@@ -865,7 +902,7 @@ void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const
 		ostr << '[' << timestamp << ']' << TWO_SPACES;
 	}
 	
-	//*TODO mark object's names in a special way so that they will be distinguishable form avatar name 
+	//*TODO mark object's names in a special way so that they will be distinguishable from avatar name 
 	//which are more strict by its nature (only firstname and secondname)
 	//Example, an object's name can be written like "Object <actual_object's_name>"
 	if (im[LL_IM_FROM].isDefined())
@@ -928,7 +965,9 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params
 		timestamp.erase(0, 1);
 		timestamp.erase(timestamp.length()-1, 1);
 
-		if (cut_off_todays_date)
+        im[LL_IM_DATE_TIME] = timestamp;    // Retain full date-time for merging chat histories
+
+        if (cut_off_todays_date)
 		{
 			LLLogChatTimeScanner::instance().checkAndCutOffDate(timestamp);
 		}
@@ -936,9 +975,9 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params
 		im[LL_IM_TIME] = timestamp;
 	}
 	else
-	{
-		//timestamp is optional
-		im[LL_IM_TIME] = "";
+	{   //timestamp is optional
+        im[LL_IM_DATE_TIME] = "";
+        im[LL_IM_TIME] = "";
 	}
 
 	bool has_stuff = matches[IDX_STUFF].matched;
diff --git a/indra/newview/lllogchat.h b/indra/newview/lllogchat.h
index c4b61ee716b56e1832e3a0e373a84c30ca559ca7..5dce8ab1d25443349737b0cac405ba0c3dccaeda 100644
--- a/indra/newview/lllogchat.h
+++ b/indra/newview/lllogchat.h
@@ -92,7 +92,7 @@ class LLLogChat : public LLSingleton<LLLogChat>
 		LOG_END
 	};
 
-	static std::string timestamp(bool withdate = false);
+	static std::string timestamp2LogString(U32 timestamp, bool withdate);
 	static std::string makeLogFileName(std::string(filename));
 	static void renameLogFile(const std::string& old_filename, const std::string& new_filename);
 	/**
@@ -201,6 +201,7 @@ extern const std::string GROUP_CHAT_SUFFIX;
 
 // LLSD map lookup constants
 extern const std::string LL_IM_TIME; //("time");
+extern const std::string LL_IM_DATE_TIME; //("datetime");
 extern const std::string LL_IM_TEXT; //("message");
 extern const std::string LL_IM_FROM; //("from");
 extern const std::string LL_IM_FROM_ID; //("from_id");
diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp
index bf00d77dea955d897329eb9da79fffcbc09ea4d8..a7a7ed1b70b89ec154752b1b69bec1655752a36b 100644
--- a/indra/newview/llmutelist.cpp
+++ b/indra/newview/llmutelist.cpp
@@ -192,7 +192,7 @@ void LLMuteList::cleanupSingleton()
     LLAvatarNameCache::getInstance()->setAccountNameChangedCallback(NULL);
 }
 
-BOOL LLMuteList::isLinden(const std::string& name) const
+bool LLMuteList::isLinden(const std::string& name)
 {
 	std::string username = boost::replace_all_copy(name, ".", " ");
 	typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
@@ -200,9 +200,9 @@ BOOL LLMuteList::isLinden(const std::string& name) const
 	tokenizer tokens(username, sep);
 	tokenizer::iterator token_iter = tokens.begin();
 	
-	if (token_iter == tokens.end()) return FALSE;
+	if (token_iter == tokens.end()) return false;
 	token_iter++;
-	if (token_iter == tokens.end()) return FALSE;
+	if (token_iter == tokens.end()) return false;
 	
 	std::string last_name = *token_iter;
 	LLStringUtil::toLower(last_name);
diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h
index 0d426fbd48438459ba10e43158810531be336233..2c45014321d5e4cfb6ce3f4c9bd4f003322a71af 100644
--- a/indra/newview/llmutelist.h
+++ b/indra/newview/llmutelist.h
@@ -104,7 +104,7 @@ class LLMuteList : public LLSingleton<LLMuteList>
 	// Alternate (convenience) form for places we don't need to pass the name, but do need flags
 	BOOL isMuted(const LLUUID& id, U32 flags) const { return isMuted(id, LLStringUtil::null, flags); };
 	
-	BOOL isLinden(const std::string& name) const;
+	static bool isLinden(const std::string& name);
 	
 	BOOL isLoaded() const { return mIsLoaded; }
 
diff --git a/indra/newview/llnotificationhandlerutil.cpp b/indra/newview/llnotificationhandlerutil.cpp
index 39a0b9b50ed9f538367480befc5b217e13885ad9..85adfaab559f701c6df8e8d07138798a2eb14222 100644
--- a/indra/newview/llnotificationhandlerutil.cpp
+++ b/indra/newview/llnotificationhandlerutil.cpp
@@ -275,7 +275,7 @@ void LLHandlerUtil::addNotifPanelToIM(const LLNotificationPtr& notification)
 	LLSD offer;
 	offer["notification_id"] = notification->getID();
 	offer["from"] = SYSTEM_FROM;
-	offer["time"] = LLLogChat::timestamp(false);
+	offer["time"] = LLLogChat::timestamp2LogString(0, false);   // Use current time
 	offer["index"] = (LLSD::Integer)session->mMsgs.size();
 	session->mMsgs.push_front(offer);
 
diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp
index 9df3a8e31aaf4bd7428f5123e333c0cc4c4640c3..b14fdbf38e8601a0ac9318d8ced512c5a477ab9b 100644
--- a/indra/newview/llpanellogin.cpp
+++ b/indra/newview/llpanellogin.cpp
@@ -1103,6 +1103,18 @@ void LLPanelLogin::onRememberPasswordCheck(void*)
     if (sInstance)
     {
         gSavedSettings.setBOOL("UpdateRememberPasswordSetting", TRUE);
+
+        LLPointer<LLCredential> cred;
+        bool remember_user, remember_password;
+        getFields(cred, remember_user, remember_password);
+
+        std::string grid(LLGridManager::getInstance()->getGridId());
+        std::string user_id(cred->userID());
+        if (!remember_password)
+        {
+            gSecAPIHandler->removeFromProtectedMap("mfa_hash", grid, user_id);
+            gSecAPIHandler->syncProtectedMap();
+        }
     }
 }
 
diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp
index 81acb1c8b4d7c47d71c876016416eb68462ffa15..744d49ff5798aec4f4017a1b10c96476e72a76d2 100644
--- a/indra/newview/llpanelmaininventory.cpp
+++ b/indra/newview/llpanelmaininventory.cpp
@@ -410,6 +410,18 @@ void LLPanelMainInventory::resetFilters()
 	setFilterTextFromFilter();
 }
 
+void LLPanelMainInventory::resetAllItemsFilters()
+{
+    LLFloaterInventoryFinder *finder = getFinder();
+    getAllItemsPanel()->getFilter().resetDefault();
+    if (finder)
+    {
+        finder->updateElementsFromFilter();
+    }
+
+    setFilterTextFromFilter();
+}
+
 void LLPanelMainInventory::setSortBy(const LLSD& userdata)
 {
 	U32 sort_order_mask = getActivePanel()->getSortOrder();
diff --git a/indra/newview/llpanelmaininventory.h b/indra/newview/llpanelmaininventory.h
index 257bce930ccaba8cdbb98e4a6187c83c5e1cc57f..7aae5a0b3cadb790b7bed640673fb6312cc608c7 100644
--- a/indra/newview/llpanelmaininventory.h
+++ b/indra/newview/llpanelmaininventory.h
@@ -96,6 +96,7 @@ class LLPanelMainInventory : public LLPanel, LLInventoryObserver
 	void toggleFindOptions();
 
     void resetFilters();
+    void resetAllItemsFilters();
 
 protected:
 	//
diff --git a/indra/newview/llpanelmediasettingsgeneral.cpp b/indra/newview/llpanelmediasettingsgeneral.cpp
index 416857bd305f2b22567a03381c30065d14c2a65f..8380394f2cc2c4b2f5d8bd0b459d142d31974932 100644
--- a/indra/newview/llpanelmediasettingsgeneral.cpp
+++ b/indra/newview/llpanelmediasettingsgeneral.cpp
@@ -39,6 +39,7 @@
 #include "llagent.h"
 #include "llviewerwindow.h"
 #include "llviewermedia.h"
+#include "llvovolume.h"
 #include "llsdutil.h"
 #include "llselectmgr.h"
 #include "llbutton.h"
@@ -452,10 +453,17 @@ bool LLPanelMediaSettingsGeneral::navigateHomeSelectedFace(bool only_if_current_
 					{
 						viewer_media_t media_impl =
 							LLViewerMedia::getInstance()->getMediaImplFromTextureID(object->getTE(face)->getMediaData()->getMediaID());
-						if(media_impl)
-						{
+                        if (media_impl)
+                        {
                             media_impl->setPriority(LLPluginClassMedia::PRIORITY_NORMAL);
                             media_impl->navigateHome();
+
+                            if (!only_if_current_is_empty)
+                            {
+                                LLSD media_data;
+                                media_data[LLMediaEntry::CURRENT_URL_KEY] = std::string();
+                                object->getTE(face)->mergeIntoMediaData(media_data);
+                            }
 							return true;
 						}
 					}
@@ -471,6 +479,23 @@ bool LLPanelMediaSettingsGeneral::navigateHomeSelectedFace(bool only_if_current_
 	LLObjectSelectionHandle selected_objects =LLSelectMgr::getInstance()->getSelection();
 	selected_objects->getSelectedTEValue( &functor_navigate_media, all_face_media_navigated );
 	
+    if (all_face_media_navigated)
+    {
+        struct functor_sync_to_server : public LLSelectedObjectFunctor
+        {
+            virtual bool apply(LLViewerObject* object)
+            {
+                LLVOVolume *volume = dynamic_cast<LLVOVolume*>(object);
+                if (volume)
+                {
+                    volume->sendMediaDataUpdate();
+                }
+                return true;
+            }
+        } sendfunc;
+        selected_objects->applyToObjects(&sendfunc);
+    }
+
 	// Note: we don't update the 'current URL' field until the media data itself changes
 
 	return all_face_media_navigated;
diff --git a/indra/newview/lltoolfocus.cpp b/indra/newview/lltoolfocus.cpp
index 07f46c5fbe07e08669b25992412af8ab4d970162..4e94895a3ebac2889e9bfcf7908054377d535874 100644
--- a/indra/newview/lltoolfocus.cpp
+++ b/indra/newview/lltoolfocus.cpp
@@ -75,6 +75,7 @@ LLToolCamera::LLToolCamera()
 	mOutsideSlopX(FALSE),
 	mOutsideSlopY(FALSE),
 	mValidClickPoint(FALSE),
+    mClickPickPending(false),
 	mValidSelection(FALSE),
 	mMouseSteering(FALSE),
 	mMouseUpX(0),
@@ -127,6 +128,11 @@ BOOL LLToolCamera::handleMouseDown(S32 x, S32 y, MASK mask)
 
 	mValidClickPoint = FALSE;
 
+    // Sometimes Windows issues down and up events near simultaneously
+    // without giving async pick a chance to trigged
+    // Ex: mouse from numlock emulation
+    mClickPickPending = true;
+
 	// If mouse capture gets ripped away, claim we moused up
 	// at the point we moused down. JC
 	mMouseUpX = x;
@@ -142,13 +148,15 @@ BOOL LLToolCamera::handleMouseDown(S32 x, S32 y, MASK mask)
 
 void LLToolCamera::pickCallback(const LLPickInfo& pick_info)
 {
-	if (!LLToolCamera::getInstance()->hasMouseCapture())
+    LLToolCamera* camera = LLToolCamera::getInstance();
+	if (!camera->mClickPickPending)
 	{
 		return;
 	}
+    camera->mClickPickPending = false;
 
-	LLToolCamera::getInstance()->mMouseDownX = pick_info.mMousePt.mX;
-	LLToolCamera::getInstance()->mMouseDownY = pick_info.mMousePt.mY;
+    camera->mMouseDownX = pick_info.mMousePt.mX;
+    camera->mMouseDownY = pick_info.mMousePt.mY;
 
 	gViewerWindow->moveCursorToCenter();
 
@@ -158,7 +166,7 @@ void LLToolCamera::pickCallback(const LLPickInfo& pick_info)
 	// Check for hit the sky, or some other invalid point
 	if (!hit_obj && pick_info.mPosGlobal.isExactlyZero())
 	{
-		LLToolCamera::getInstance()->mValidClickPoint = FALSE;
+        camera->mValidClickPoint = FALSE;
 		return;
 	}
 
@@ -168,7 +176,7 @@ void LLToolCamera::pickCallback(const LLPickInfo& pick_info)
 		LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection();
 		if (!selection->getObjectCount() || selection->getSelectType() != SELECT_TYPE_HUD)
 		{
-			LLToolCamera::getInstance()->mValidClickPoint = FALSE;
+            camera->mValidClickPoint = FALSE;
 			return;
 		}
 	}
@@ -192,7 +200,7 @@ void LLToolCamera::pickCallback(const LLPickInfo& pick_info)
 
 		if( !good_customize_avatar_hit )
 		{
-			LLToolCamera::getInstance()->mValidClickPoint = FALSE;
+            camera->mValidClickPoint = FALSE;
 			return;
 		}
 
@@ -237,7 +245,7 @@ void LLToolCamera::pickCallback(const LLPickInfo& pick_info)
 
 	}
 
-	LLToolCamera::getInstance()->mValidClickPoint = TRUE;
+    camera->mValidClickPoint = TRUE;
 
 	if( CAMERA_MODE_CUSTOMIZE_AVATAR == gAgentCamera.getCameraMode() )
 	{
@@ -284,32 +292,36 @@ BOOL LLToolCamera::handleMouseUp(S32 x, S32 y, MASK mask)
 
 	if (hasMouseCapture())
 	{
-		if (mValidClickPoint)
-		{
-			if( CAMERA_MODE_CUSTOMIZE_AVATAR == gAgentCamera.getCameraMode() )
-			{
-				LLCoordGL mouse_pos;
-				LLVector3 focus_pos = gAgent.getPosAgentFromGlobal(gAgentCamera.getFocusGlobal());
-				BOOL success = LLViewerCamera::getInstance()->projectPosAgentToScreen(focus_pos, mouse_pos);
-				if (success)
-				{
-					LLUI::getInstance()->setMousePositionScreen(mouse_pos.mX, mouse_pos.mY);
-				}
-			}
-			else if (mMouseSteering)
-			{
-				LLUI::getInstance()->setMousePositionScreen(mMouseDownX, mMouseDownY);
-			}
-			else
-			{
-				gViewerWindow->moveCursorToCenter();
-			}
-		}
-		else
-		{
-			// not a valid zoomable object
-			LLUI::getInstance()->setMousePositionScreen(mMouseDownX, mMouseDownY);
-		}
+        // Do not move camera if we haven't gotten a pick
+        if (!mClickPickPending)
+        {
+            if (mValidClickPoint)
+            {
+                if (CAMERA_MODE_CUSTOMIZE_AVATAR == gAgentCamera.getCameraMode())
+                {
+                    LLCoordGL mouse_pos;
+                    LLVector3 focus_pos = gAgent.getPosAgentFromGlobal(gAgentCamera.getFocusGlobal());
+                    BOOL success = LLViewerCamera::getInstance()->projectPosAgentToScreen(focus_pos, mouse_pos);
+                    if (success)
+                    {
+                        LLUI::getInstance()->setMousePositionScreen(mouse_pos.mX, mouse_pos.mY);
+                    }
+                }
+                else if (mMouseSteering)
+                {
+                    LLUI::getInstance()->setMousePositionScreen(mMouseDownX, mMouseDownY);
+                }
+                else
+                {
+                    gViewerWindow->moveCursorToCenter();
+                }
+            }
+            else
+            {
+                // not a valid zoomable object
+                LLUI::getInstance()->setMousePositionScreen(mMouseDownX, mMouseDownY);
+            }
+        }
 
 		// calls releaseMouse() internally
 		setMouseCapture(FALSE);
diff --git a/indra/newview/lltoolfocus.h b/indra/newview/lltoolfocus.h
index cfc235b6c2a411fba999d95e0de805f7fb13b532..ef71f9230a701b209c7cb3db64b9fa93f151bfeb 100644
--- a/indra/newview/lltoolfocus.h
+++ b/indra/newview/lltoolfocus.h
@@ -49,6 +49,7 @@ class LLToolCamera
 
 	virtual LLTool*	getOverrideTool(MASK mask) { return NULL; }
 
+    void setClickPickPending() { mClickPickPending = true; }
 	static void pickCallback(const LLPickInfo& pick_info);
 	BOOL mouseSteerMode() { return mMouseSteering; }
 
@@ -65,6 +66,7 @@ class LLToolCamera
 	BOOL	mOutsideSlopX;
 	BOOL	mOutsideSlopY;
 	BOOL	mValidClickPoint;
+    bool	mClickPickPending;
 	BOOL	mValidSelection;
 	BOOL	mMouseSteering;
 	S32		mMouseUpX;	// needed for releaseMouse()
diff --git a/indra/newview/lltoolpie.cpp b/indra/newview/lltoolpie.cpp
index 5fb83bf08e490a4761aeda3f368cb43811291bd1..58011bbde2d9c981a040be9deeaf629bc4e040d5 100644
--- a/indra/newview/lltoolpie.cpp
+++ b/indra/newview/lltoolpie.cpp
@@ -443,6 +443,7 @@ BOOL LLToolPie::handleLeftClickPick()
 		LLToolMgr::getInstance()->setTransientTool(LLToolCamera::getInstance());
 		gViewerWindow->hideCursor();
 		LLToolCamera::getInstance()->setMouseCapture(TRUE);
+        LLToolCamera::getInstance()->setClickPickPending();
 		LLToolCamera::getInstance()->pickCallback(mPick);
 		gAgentCamera.setFocusOnAvatar(TRUE, TRUE);
 
diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp
index 4b9f322dfa1e5a327271e7d588120ff216c61f4f..34d3ed8bb10a4956c624ed7f7e9cebaf298c49de 100644
--- a/indra/newview/lltranslate.cpp
+++ b/indra/newview/lltranslate.cpp
@@ -546,6 +546,18 @@ void LLBingTranslationHandler::verifyKey(const std::string &key, LLTranslate::Ke
 }
 
 //=========================================================================
+LLTranslate::LLTranslate():
+	mCharsSeen(0),
+	mCharsSent(0),
+	mFailureCount(0),
+	mSuccessCount(0)
+{
+}
+
+LLTranslate::~LLTranslate()
+{
+}
+
 /*static*/
 void LLTranslate::translateMessage(const std::string &from_lang, const std::string &to_lang,
     const std::string &mesg, TranslationSuccess_fn success, TranslationFailure_fn failure)
@@ -634,6 +646,43 @@ bool LLTranslate::isTranslationConfigured()
 	return getPreferredHandler().isConfigured();
 }
 
+void LLTranslate::logCharsSeen(size_t count)
+{
+	mCharsSeen += count;
+}
+
+void LLTranslate::logCharsSent(size_t count)
+{
+	mCharsSent += count;
+}
+
+void LLTranslate::logSuccess(S32 count)
+{
+	mSuccessCount += count;
+}
+
+void LLTranslate::logFailure(S32 count)
+{
+	mFailureCount += count;
+}
+
+LLSD LLTranslate::asLLSD() const
+{
+	LLSD res;
+	bool on = gSavedSettings.getBOOL("TranslateChat");
+	res["on"] = on;
+	res["chars_seen"] = (S32) mCharsSeen;
+	if (on)
+	{
+		res["chars_sent"] = (S32) mCharsSent;
+		res["success_count"] = mSuccessCount;
+		res["failure_count"] = mFailureCount;
+		res["language"] = getTranslateLanguage();  
+		res["service"] = gSavedSettings.getString("TranslationService");
+	}
+	return res;
+}
+
 // static
 LLTranslationAPIHandler& LLTranslate::getPreferredHandler()
 {
diff --git a/indra/newview/lltranslate.h b/indra/newview/lltranslate.h
index e0722fbd83f92dce9b41c58012e19875893d6621..58707e2d3620e1c0f8b59d24be16f2f7a546b9b3 100644
--- a/indra/newview/lltranslate.h
+++ b/indra/newview/lltranslate.h
@@ -30,6 +30,8 @@
 #include "llbufferstream.h"
 #include <boost/function.hpp>
 
+#include "llsingleton.h"
+
 namespace Json
 {
     class Value;
@@ -48,8 +50,10 @@ class LLTranslationAPIHandler;
  *
  * API keys for translation are taken from saved settings.
  */
-class LLTranslate
+class LLTranslate: public LLSingleton<LLTranslate>
 {
+	LLSINGLETON(LLTranslate);
+	~LLTranslate();
 	LOG_CLASS(LLTranslate);
 
 public :
@@ -94,9 +98,19 @@ public :
     static std::string addNoTranslateTags(std::string mesg);
     static std::string removeNoTranslateTags(std::string mesg);
 
+	void logCharsSeen(size_t count);
+	void logCharsSent(size_t count);
+	void logSuccess(S32 count);
+	void logFailure(S32 count);
+	LLSD asLLSD() const;
 private:
 	static LLTranslationAPIHandler& getPreferredHandler();
 	static LLTranslationAPIHandler& getHandler(EService service);
+
+	size_t mCharsSeen;
+	size_t mCharsSent;
+	S32 mFailureCount;
+	S32 mSuccessCount;
 };
 
 #endif
diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp
index 232b52a3f9fb1a62ce72f54d8f881f9f19fdccda..13491114b9b6ee0a802ba2a72ead69eaecbdb35b 100644
--- a/indra/newview/llviewerassetupload.cpp
+++ b/indra/newview/llviewerassetupload.cpp
@@ -704,7 +704,7 @@ LLUUID LLBufferedAssetUploadInfo::finishUpload(LLSD &result)
 LLScriptAssetUpload::LLScriptAssetUpload(LLUUID itemId, std::string buffer, invnUploadFinish_f finish):
     LLBufferedAssetUploadInfo(itemId, LLAssetType::AT_LSL_TEXT, buffer, finish),
     mExerienceId(),
-    mTargetType(LSL2),
+    mTargetType(MONO),
     mIsRunning(false)
 {
 }
@@ -725,7 +725,7 @@ LLSD LLScriptAssetUpload::generatePostBody()
     if (getTaskId().isNull())
     {
         body["item_id"] = getItemId();
-        body["target"] = "lsl2";
+        body["target"] = "mono";
     }
     else
     {
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index d97ed61e115f323ca093576d0e5ffe026b9071b6..c1a9b6be80fe7581819995e9343ed8cf08d175ae 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -1907,7 +1907,7 @@ bool LLOfferInfo::inventory_offer_callback(const LLSD& notification, const LLSD&
 				log_message = LLTrans::getString("InvOfferDecline", log_message_args);
 			}
 			chat.mText = log_message;
-			if( LLMuteList::getInstance()->isMuted(mFromID ) && ! LLMuteList::getInstance()->isLinden(mFromName) )  // muting for SL-42269
+			if( LLMuteList::getInstance()->isMuted(mFromID ) && ! LLMuteList::isLinden(mFromName) )  // muting for SL-42269
 			{
 				chat.mMuted = TRUE;
 				accept_to_trash = false; // will send decline message
@@ -2426,6 +2426,7 @@ void translateSuccess(LLChat chat, LLSD toastArgs, std::string originalMsg, std:
         chat.mText += " (" + LLTranslate::removeNoTranslateTags(translation) + ")";
     }
 
+	LLTranslate::instance().logSuccess(1);
     LLNotificationsUI::LLNotificationManager::instance().onChat(chat, toastArgs);
 }
 
@@ -2435,6 +2436,7 @@ void translateFailure(LLChat chat, LLSD toastArgs, int status, const std::string
     LLStringUtil::replaceString(msg, "\n", " "); // we want one-line error messages
     chat.mText += " (" + msg + ")";
 
+	LLTranslate::instance().logFailure(1);
     LLNotificationsUI::LLNotificationManager::instance().onChat(chat, toastArgs);
 }
 
@@ -2512,7 +2514,7 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data)
 		LLMute::flagTextChat) 
 		|| LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagTextChat);
 	is_linden = chat.mSourceType != CHAT_SOURCE_OBJECT &&
-		LLMuteList::getInstance()->isLinden(from_name);
+		LLMuteList::isLinden(from_name);
 
 	if (is_muted && (chat.mSourceType == CHAT_SOURCE_OBJECT))
 	{
@@ -2669,6 +2671,7 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data)
 		LLSD args;
 		chat.mOwnerID = owner_id;
 
+		LLTranslate::instance().logCharsSeen(mesg.size());
 		if (gSavedSettings.getBOOL("TranslateChat") && chat.mSourceType != CHAT_SOURCE_SYSTEM)
 		{
 			if (chat.mChatStyle == CHAT_STYLE_IRC)
@@ -2678,6 +2681,7 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data)
 			const std::string from_lang = ""; // leave empty to trigger autodetect
 			const std::string to_lang = LLTranslate::getTranslateLanguage();
 
+			LLTranslate::instance().logCharsSent(mesg.size());
             LLTranslate::translateMessage(from_lang, to_lang, mesg,
                 boost::bind(&translateSuccess, chat, args, mesg, from_lang, _1, _2),
                 boost::bind(&translateFailure, chat, args, _1, _2));
@@ -5819,8 +5823,12 @@ void process_script_question(LLMessageSystem *msg, void **user_data)
 			{
 				count++;
 				known_questions |= script_perm.permbit;
-				// check whether permission question should cause special caution dialog
-				caution |= (script_perm.caution);
+
+                if (!LLMuteList::isLinden(owner_name))
+                {
+                    // check whether permission question should cause special caution dialog
+                    caution |= (script_perm.caution);
+                }
 
 				if (("ScriptTakeMoney" == script_perm.question) && has_not_only_debit)
 					continue;
@@ -6346,7 +6354,7 @@ bool teleport_request_callback(const LLSD& notification, const LLSD& response)
 	LLAvatarName av_name;
 	LLAvatarNameCache::get(from_id, &av_name);
 
-	if(LLMuteList::getInstance()->isMuted(from_id) && !LLMuteList::getInstance()->isLinden(av_name.getUserName()))
+	if(LLMuteList::getInstance()->isMuted(from_id) && !LLMuteList::isLinden(av_name.getUserName()))
 	{
 		return false;
 	}
diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp
index e930b581117a05aacfe0bbdcf128ee58c578cd29..efc4ded79ef086d5c287d19bf67255f17f79b625 100644
--- a/indra/newview/llviewerobjectlist.cpp
+++ b/indra/newview/llviewerobjectlist.cpp
@@ -815,7 +815,10 @@ void LLViewerObjectList::updateApparentAngles(LLAgent &agent)
 	{
 		virtual bool apply(LLViewerObject* objectp)
 		{
-			objectp->boostTexturePriority();
+            if (objectp)
+            {
+                objectp->boostTexturePriority();
+            }
 			return true;
 		}
 	} func;
diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp
index 0045dcb547ffbca9bdc297fc11ad039bea6beee3..519166af6378f632653bb20a3263a4fe6e3bbe9b 100644
--- a/indra/newview/llviewerstats.cpp
+++ b/indra/newview/llviewerstats.cpp
@@ -65,6 +65,7 @@
 #include "llvoicevivox.h"
 #include "llinventorymodel.h"
 #include "lluiusage.h"
+#include "lltranslate.h"
 
 namespace LLStatViewer
 {
@@ -591,6 +592,7 @@ void send_viewer_stats(bool include_preferences)
 	agent["meters_traveled"] = gAgent.getDistanceTraveled();
 	agent["regions_visited"] = gAgent.getRegionsVisited();
 	agent["mem_use"] = LLMemory::getCurrentRSS() / 1024.0;
+	agent["translation"] = LLTranslate::instance().asLLSD();
 
 	LLSD &system = body["system"];
 	
@@ -600,6 +602,7 @@ void send_viewer_stats(bool include_preferences)
     system["cpu_sse"] = gSysCPU.getSSEVersions();
 	system["address_size"] = ADDRESS_SIZE;
 	system["os_bitness"] = LLOSInfo::instance().getOSBitness();
+	system["hardware_concurrency"] = (LLSD::Integer) std::thread::hardware_concurrency();
 	unsigned char MACAddress[MAC_ADDRESS_BYTES];
 	LLUUID::getNodeID(MACAddress);
 	std::string macAddressString = llformat("%02x-%02x-%02x-%02x-%02x-%02x",
@@ -622,6 +625,7 @@ void send_viewer_stats(bool include_preferences)
 
 	gGLManager.asLLSD(system["gl"]);
 
+
 	S32 shader_level = 0;
 	if (LLPipeline::sRenderDeferred)
 	{
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index e3ac56d0d399d4d817adcb540a1a616bf7a6ab9b..8a11c5cf8f38ecf3a29b896851844e6d0af24556 100644
--- a/indra/newview/llviewertexture.cpp
+++ b/indra/newview/llviewertexture.cpp
@@ -1118,7 +1118,7 @@ void LLViewerFetchedTexture::init(bool firstinit)
 	mLoadedCallbackDesiredDiscardLevel = S8_MAX;
 	mPauseLoadedCallBacks = FALSE;
 
-	mNeedsCreateTexture = FALSE;
+	mNeedsCreateTexture = false;
 	
 	mIsRawImageValid = FALSE;
 	mRawDiscardLevel = INVALID_DISCARD_LEVEL;
@@ -1400,12 +1400,12 @@ void LLViewerFetchedTexture::addToCreateTexture()
 	{
 		//just update some variables, not to create a real GL texture.
 		createGLTexture(mRawDiscardLevel, mRawImage, 0, FALSE);
-		mNeedsCreateTexture = FALSE;
+		mNeedsCreateTexture = false;
 		destroyRawImage();
 	}
 	else if(!force_update && getDiscardLevel() > -1 && getDiscardLevel() <= mRawDiscardLevel)
 	{
-		mNeedsCreateTexture = FALSE;
+		mNeedsCreateTexture = false;
 		destroyRawImage();
 	}
 	else
@@ -1441,7 +1441,7 @@ void LLViewerFetchedTexture::addToCreateTexture()
 						mRawDiscardLevel += i;
 						if(mRawDiscardLevel >= getDiscardLevel() && getDiscardLevel() > 0)
 						{
-							mNeedsCreateTexture = FALSE;
+							mNeedsCreateTexture = false;
 							destroyRawImage();
 							return;
 						}
@@ -1473,7 +1473,7 @@ BOOL LLViewerFetchedTexture::preCreateTexture(S32 usename/*= 0*/)
         destroyRawImage();
         return FALSE;
     }
-    mNeedsCreateTexture = FALSE;
+    mNeedsCreateTexture = false;
 
     if (mRawImage.isNull())
     {
@@ -1609,14 +1609,14 @@ void LLViewerFetchedTexture::postCreateTexture()
         destroyRawImage();
     }
 
-    mNeedsCreateTexture = FALSE;
+    mNeedsCreateTexture = false;
 }
 
 void LLViewerFetchedTexture::scheduleCreateTexture()
 {
     if (!mNeedsCreateTexture)
     {
-        mNeedsCreateTexture = TRUE;
+        mNeedsCreateTexture = true;
         if (preCreateTexture())
         {
 #if LL_IMAGEGL_THREAD_CHECK
@@ -1630,7 +1630,7 @@ void LLViewerFetchedTexture::scheduleCreateTexture()
                 memcpy(data_copy, data, size);
             }
 #endif
-            mNeedsCreateTexture = TRUE;
+            mNeedsCreateTexture = true;
             auto mainq = LLImageGLThread::sEnabled ? mMainQueue.lock() : nullptr;
             if (mainq)
             {
diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h
index b953d7006b26603b993028d63e35406a14523775..2f5e0d01dfd494a4a8198332ac1c7c9fa80f9dc3 100644
--- a/indra/newview/llviewertexture.h
+++ b/indra/newview/llviewertexture.h
@@ -27,6 +27,7 @@
 #ifndef LL_LLVIEWERTEXTURE_H					
 #define LL_LLVIEWERTEXTURE_H
 
+#include "llatomic.h"
 #include "llgltexture.h"
 #include "lltimer.h"
 #include "llframetimer.h"
@@ -528,7 +529,9 @@ class LLViewerFetchedTexture : public LLViewerTexture
 	LLFrameTimer mStopFetchingTimer;	// Time since mDecodePriority == 0.f.
 
 	BOOL  mInImageList;				// TRUE if image is in list (in which case don't reset priority!)
-	BOOL  mNeedsCreateTexture;	
+	// This needs to be atomic, since it is written both in the main thread
+	// and in the GL image worker thread... HB
+	LLAtomicBool  mNeedsCreateTexture;	
 
 	BOOL   mForSculpt ; //a flag if the texture is used as sculpt data.
 	BOOL   mIsFetched ; //is loaded from remote or from cache, not generated locally.
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 772abfc6a17954baa4663216e12f607b1ad51745..305c489cc8a427b68d59f64eb043a1c84388f698 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -3547,14 +3547,15 @@ void LLVOAvatar::idleUpdateNameTagText(bool new_name)
 
 void LLVOAvatar::addNameTagLine(const std::string& line, const LLColor4& color, S32 style, const LLFontGL* font, const bool use_ellipses)
 {
+    // extra width (NAMETAG_MAX_WIDTH) is for names only, not for chat
 	llassert(mNameText);
 	if (mVisibleChat)
 	{
-		mNameText->addLabel(line);
+		mNameText->addLabel(line, LLHUDNameTag::NAMETAG_MAX_WIDTH);
 	}
 	else
 	{
-		mNameText->addLine(line, color, (LLFontGL::StyleFlags)style, font, use_ellipses);
+		mNameText->addLine(line, color, (LLFontGL::StyleFlags)style, font, use_ellipses, LLHUDNameTag::NAMETAG_MAX_WIDTH);
 	}
     mNameIsSet |= !line.empty();
 }
diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp
index 8f0938d01efda29dbfd6296d2211c42bf94a97b5..c73f96da2d31a4fccea06d294a7b0049f83ef5bc 100644
--- a/indra/newview/llvoicevivox.cpp
+++ b/indra/newview/llvoicevivox.cpp
@@ -4525,7 +4525,7 @@ void LLVivoxVoiceClient::messageEvent(
 		{
 			bool is_do_not_disturb = gAgent.isDoNotDisturb();
 			bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat);
-			bool is_linden = LLMuteList::getInstance()->isLinden(session->mName);
+			bool is_linden = LLMuteList::isLinden(session->mName);
 			LLChat chat;
 
 			chat.mMuted = is_muted && !is_linden;
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 51d7082b40855602f086b4e886519caa52d69eae..bbff6c889fe1ad2062ae737bbbb2fc7f14758732 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -1778,20 +1778,17 @@ void LLPipeline::unlinkDrawable(LLDrawable *drawable)
 void LLPipeline::removeMutedAVsLights(LLVOAvatar* muted_avatar)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE;
-    light_set_t::iterator iter = gPipeline.mNearbyLights.begin();
-
-    while (iter != gPipeline.mNearbyLights.end())
-    {
-        if (iter->drawable->getVObj()->isAttachment() && iter->drawable->getVObj()->getAvatar() == muted_avatar)
-        {
-            gPipeline.mLights.erase(iter->drawable);
-            iter = gPipeline.mNearbyLights.erase(iter);
-        }
-        else
-        {
-            iter++;
-        }
-    }
+	for (light_set_t::iterator iter = gPipeline.mNearbyLights.begin();
+		 iter != gPipeline.mNearbyLights.end(); iter++)
+	{
+        const LLViewerObject *vobj = iter->drawable->getVObj();
+        if (vobj && vobj->getAvatar()
+            && vobj->isAttachment() && vobj->getAvatar() == muted_avatar)
+		{
+			gPipeline.mLights.erase(iter->drawable);
+			gPipeline.mNearbyLights.erase(iter);
+		}
+	}
 }
 
 U32 LLPipeline::addObject(LLViewerObject *vobj)
diff --git a/indra/newview/skins/default/xui/de/floater_tools.xml b/indra/newview/skins/default/xui/de/floater_tools.xml
index a4dfde66bcccd4d942b72d4e9c830eb4ae443cf7..f6208e11a5d956a9e3f0983fa4addba481c7f09a 100644
--- a/indra/newview/skins/default/xui/de/floater_tools.xml
+++ b/indra/newview/skins/default/xui/de/floater_tools.xml
@@ -40,7 +40,7 @@
 		Klicken und ziehen, um Land auszuwählen
 	</floater.string>
 	<floater.string name="status_selectcount">
-		[OBJ_COUNT] Objekte ausgewählt, Auswirkung auf Land [LAND_IMPACT]
+		[OBJ_COUNT] Objekte ausgewählt, Auswirkung auf Land [LAND_IMPACT] [secondlife:///app/openfloater/object_weights ?]
 	</floater.string>
 	<floater.string name="status_remaining_capacity">
 		Verbleibende Kapazität [LAND_CAPACITY].
diff --git a/indra/newview/skins/default/xui/en/floater_display_name.xml b/indra/newview/skins/default/xui/en/floater_display_name.xml
index 3c8f4158605579d383dfa14fdcad8c1e0fc97ecb..f3431da858d78c15e9ccdf10845dde815941c8cf 100644
--- a/indra/newview/skins/default/xui/en/floater_display_name.xml
+++ b/indra/newview/skins/default/xui/en/floater_display_name.xml
@@ -56,7 +56,7 @@
       max_length_chars="31"
       height="20"
       top_pad="5"
-      left="50" />
+      left_delta="0" />
 	<text
        top_pad="15"
        left="25"
@@ -72,23 +72,33 @@
       max_length_chars="31"
       height="20"
       top_pad="5"
-      left="50" />
+      left_delta="0" />
+    <button
+     label="Reset"
+     layout="topleft"
+     font="SansSerif"
+     width="120"
+     height="23"
+     top_pad="40"
+     left_delta="0"
+     name="reset_btn"
+     tool_tip="Use Username as a Display Name" />
     <button
      height="23"
      label="Save"
      layout="topleft"
      font="SansSerif"
-     left="35"
+     left_pad="35"
      name="save_btn"
      tool_tip="Save your new Display Name" 
-     top_pad="40"
+     top_delta="0"
      width="120" />
     <button
      height="23"
      label="Cancel"
      font="SansSerif"
      layout="topleft"
-     left_pad="125"
+     left_pad="5"
      name="cancel_btn"
      width="120" />
 </floater>
diff --git a/indra/newview/skins/default/xui/en/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/en/floater_inventory_item_properties.xml
index 45e16c59ae3bef4be672e9861ee9eb6efc3f38c8..850e1be372db8639e780f47916c9d7780d693d10 100644
--- a/indra/newview/skins/default/xui/en/floater_inventory_item_properties.xml
+++ b/indra/newview/skins/default/xui/en/floater_inventory_item_properties.xml
@@ -323,7 +323,6 @@
         follows="left|top"
         decimal_digits="0"
         increment="1"
-        control_name="Edit Cost"
         name="Edit Cost"
         label="Price:"
         label_width="100"
diff --git a/indra/newview/skins/default/xui/en/floater_tools.xml b/indra/newview/skins/default/xui/en/floater_tools.xml
index ade79b8884af31f4b5b0ec4e2b664d0c2051bdda..d9b0ac006085185145d23f1e999fe745ae1cd019 100644
--- a/indra/newview/skins/default/xui/en/floater_tools.xml
+++ b/indra/newview/skins/default/xui/en/floater_tools.xml
@@ -1140,7 +1140,6 @@ even though the user gets a free copy.
       decimal_digits="0"
       increment="1"
       left_pad="0"
-      control_name="Edit Cost"
       name="Edit Cost"
       label="L$"
       label_width="15"
diff --git a/indra/newview/skins/default/xui/en/menu_participant_view.xml b/indra/newview/skins/default/xui/en/menu_participant_view.xml
index 7ea87ee05c442542a9bcc79117d6b710e832c664..b9750284cdf29e61b30b00ada3d99bdc4c788bb4 100644
--- a/indra/newview/skins/default/xui/en/menu_participant_view.xml
+++ b/indra/newview/skins/default/xui/en/menu_participant_view.xml
@@ -90,7 +90,7 @@
          parameter="conversation_log" />
     </menu_item_check>
     <menu_item_separator layout="topleft" />
-    <menu_item_check name="Translate_chat" label="Translate Nearby chat">
+    <menu_item_check name="Translate_chat" label="Translate chat">
         <menu_item_check.on_click
          function="IMFloaterContainer.Action" 
          parameter="Translating.Toggle" />
diff --git a/indra/newview/skins/default/xui/en/panel_media_settings_general.xml b/indra/newview/skins/default/xui/en/panel_media_settings_general.xml
index 2316beeb36c8cef72fe69dbd28852a079081a0b1..4c566dc60a13ce971b51c2f840c132512b713ddc 100644
--- a/indra/newview/skins/default/xui/en/panel_media_settings_general.xml
+++ b/indra/newview/skins/default/xui/en/panel_media_settings_general.xml
@@ -92,10 +92,7 @@
    label="Reset"
    left_delta="233" 
    name="current_url_reset_btn" 
-   width="110" > 
-   <button.commit_callback
-	     function="Media.ResetCurrentUrl"/>
-  </button>
+   width="110"/>
   <check_box 
    bottom_delta="-25" 
    enabled="true" 
diff --git a/indra/newview/skins/default/xui/en/panel_region_estate.xml b/indra/newview/skins/default/xui/en/panel_region_estate.xml
index 9b9c870c49f384ebd8a41ef55652b567df02232a..33e99a46aecf4a62d341ead9666955bb21e76679 100644
--- a/indra/newview/skins/default/xui/en/panel_region_estate.xml
+++ b/indra/newview/skins/default/xui/en/panel_region_estate.xml
@@ -82,7 +82,7 @@
     <view_border
      bevel_style="none"
      follows="top|left"
-     height="185"
+     height="205"
      layout="topleft"
      left="10"
      top_pad="5"
@@ -125,6 +125,16 @@
      tool_tip="Residents must have payment information on file to access this estate.  See the [SUPPORT_SITE] for more information."
      top_pad="2"
      width="278" />
+    <check_box
+     follows="top|left"
+     height="18"
+     label="Must not be a scripted agent"
+     layout="topleft"
+     left_delta="0"
+     name="limit_bots"
+     tool_tip="Residents must not be a scripted agents (bots) to access this estate.  See the [SUPPORT_SITE] for more information."
+     top_pad="2"
+     width="278" />
     <check_box
      height="18"
      label="Parcel owners can be more restrictive"
diff --git a/indra/newview/skins/default/xui/en/sidepanel_item_info.xml b/indra/newview/skins/default/xui/en/sidepanel_item_info.xml
index 9a68479d05c143907a5ef0d73ed09f28016ad986..35d14251c75ff85682cb7e15f1fe5bd5525a3405 100644
--- a/indra/newview/skins/default/xui/en/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/en/sidepanel_item_info.xml
@@ -450,7 +450,6 @@
         follows="left|top"
         decimal_digits="0"
         increment="1"
-        control_name="Edit Cost"
         name="Edit Cost"
         label="Price: L$"
         label_width="75"
@@ -461,8 +460,72 @@
         max_val="999999999"
         top_pad="10"
         tool_tip="Object cost." />
-    </panel>
-
+      <text
+        type="string"
+        length="1"
+        follows="left|top"
+        height="10"
+        layout="topleft"
+        left="10"
+        name="BaseMaskDebug"
+        text_color="White"
+        top_pad="30"
+        width="130">
+        B:
+      </text>
+      <text
+        type="string"
+        length="1"
+        follows="left|top"
+        height="10"
+        layout="topleft"
+        left_delta="60"
+        name="OwnerMaskDebug"
+        text_color="White"
+        top_delta="0"
+        width="270">
+        O:
+      </text>
+      <text
+        type="string"
+        length="1"
+        follows="left|top"
+        height="10"
+        layout="topleft"
+        left_delta="60"
+        name="GroupMaskDebug"
+        text_color="White"
+        top_delta="0"
+        width="210">
+        G:
+      </text>
+      <text
+        type="string"
+        length="1"
+        follows="left|top"
+        height="10"
+        layout="topleft"
+        left_delta="60"
+        name="EveryoneMaskDebug"
+        text_color="White"
+        top_delta="0"
+        width="150">
+        E:
+      </text>
+      <text
+       type="string"
+       length="1"
+       follows="left|top"
+       height="10"
+       layout="topleft"
+       left_delta="60"
+       name="NextMaskDebug"
+       text_color="White"
+       top_delta="0"
+       width="90">
+        N:
+      </text>
+    </panel>   
   </scroll_container>
   <panel
     height="30"
diff --git a/indra/newview/skins/default/xui/en/sidepanel_task_info.xml b/indra/newview/skins/default/xui/en/sidepanel_task_info.xml
index 1c9d750aa638cd4b8cc2f480157248b46f22eb07..0b32215964bbb0a548b303b01802b1057692fd1f 100644
--- a/indra/newview/skins/default/xui/en/sidepanel_task_info.xml
+++ b/indra/newview/skins/default/xui/en/sidepanel_task_info.xml
@@ -454,7 +454,6 @@
         increment="1"
         top_pad="10"
         left="120"
-        control_name="Edit Cost"
         name="Edit Cost"
         label="Price: L$"
         label_width="73"				
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index 96b4bda40c621ad595ee6946b0e036e18154c052..01f5b513c7afa896750c622886d039d582f4f0c6 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -126,8 +126,8 @@ http://secondlife.com/download
 
 For more information, see our FAQ below:
 http://secondlife.com/viewer-access-faq</string>
-	<string name="LoginFailed">Grid emergency login failure.
-If you feel this is an error, please contact support@secondlife.com.</string>
+	<string name="LoginFailed">"Login process did not complete due to system issues. Try again in a few minutes.
+If you feel this is an error, contact Support at https://support.secondlife.com"</string>
 	<string name="LoginIntermediateOptionalUpdateAvailable">Optional viewer update available: [VERSION]</string>
 	<string name="LoginFailedRequiredUpdate">Required viewer update: [VERSION]</string>
 	<string name="LoginFailedAlreadyLoggedIn">This agent is already logged in.
diff --git a/indra/newview/skins/default/xui/es/floater_tools.xml b/indra/newview/skins/default/xui/es/floater_tools.xml
index 6fce98472d1d5715a76f457dfdd3256e86c2180b..ffa85a2f04221e19cadf85cfddd415502784251b 100644
--- a/indra/newview/skins/default/xui/es/floater_tools.xml
+++ b/indra/newview/skins/default/xui/es/floater_tools.xml
@@ -25,7 +25,7 @@
 		Pulsa y arrastra para seleccionar el terreno.
 	</floater.string>
 	<floater.string name="status_selectcount">
-		[OBJ_COUNT] objetos seleccionados, impacto en el terreno [LAND_IMPACT]
+		[OBJ_COUNT] objetos seleccionados, impacto en el terreno [LAND_IMPACT] [secondlife:///app/openfloater/object_weights ?]
 	</floater.string>
 	<floater.string name="status_remaining_capacity">
 		Capacidad restante [LAND_CAPACITY].
diff --git a/indra/newview/skins/default/xui/fr/floater_tools.xml b/indra/newview/skins/default/xui/fr/floater_tools.xml
index 9597d38dcab206587c333bbc9541d552daea738d..c161f3f5306de68d1a20d0a607a2278abea1e613 100644
--- a/indra/newview/skins/default/xui/fr/floater_tools.xml
+++ b/indra/newview/skins/default/xui/fr/floater_tools.xml
@@ -40,7 +40,7 @@
 		Cliquez et faites glisser pour sélectionner le terrain.
 	</floater.string>
 	<floater.string name="status_selectcount">
-		[OBJ_COUNT] objets sélectionnés, impact sur le terrain [LAND_IMPACT]
+		[OBJ_COUNT] objets sélectionnés, impact sur le terrain [LAND_IMPACT] [secondlife:///app/openfloater/object_weights ?]
 	</floater.string>
 	<floater.string name="status_remaining_capacity">
 		Capacité restante [LAND_CAPACITY].
diff --git a/indra/newview/skins/default/xui/it/floater_tools.xml b/indra/newview/skins/default/xui/it/floater_tools.xml
index a21ae9a485ad110fac397d7888d49c694463c902..f98a2da277cbec7800ecd769f473537064e078f1 100644
--- a/indra/newview/skins/default/xui/it/floater_tools.xml
+++ b/indra/newview/skins/default/xui/it/floater_tools.xml
@@ -40,7 +40,7 @@
 		Clicca e trascina per selezionare il terreno
 	</floater.string>
 	<floater.string name="status_selectcount">
-		[OBJ_COUNT] oggetti selezionati, impatto terreno [LAND_IMPACT]
+		[OBJ_COUNT] oggetti selezionati, impatto terreno [LAND_IMPACT] [secondlife:///app/openfloater/object_weights ?]
 	</floater.string>
 	<floater.string name="status_remaining_capacity">
 		Capacità restante [LAND_CAPACITY].
diff --git a/indra/newview/skins/default/xui/ja/floater_tools.xml b/indra/newview/skins/default/xui/ja/floater_tools.xml
index aec0dbdb555fa9cb77d5564eeba240b5ce7b9bbf..13f766698ebdd9746fa8f8c23dde2a150b6b851f 100644
--- a/indra/newview/skins/default/xui/ja/floater_tools.xml
+++ b/indra/newview/skins/default/xui/ja/floater_tools.xml
@@ -40,7 +40,7 @@
 		土地をクリックし、ドラッグして選択
 	</floater.string>
 	<floater.string name="status_selectcount">
-		選択されているオブジェクトは [OBJ_COUNT] 個、土地の負荷は [LAND_IMPACT]
+		選択されているオブジェクトは [OBJ_COUNT] 個、土地の負荷は [LAND_IMPACT] [secondlife:///app/openfloater/object_weights 詳細]
 	</floater.string>
 	<floater.string name="status_remaining_capacity">
 		残りの許容数 [LAND_CAPACITY]。
diff --git a/indra/newview/skins/default/xui/pl/floater_tools.xml b/indra/newview/skins/default/xui/pl/floater_tools.xml
index 5e2ed4a3510b46a42b5e662bf0b06ef00ef1628e..8932a86fd1c8588eadc0c576252e8411e1da7864 100644
--- a/indra/newview/skins/default/xui/pl/floater_tools.xml
+++ b/indra/newview/skins/default/xui/pl/floater_tools.xml
@@ -40,7 +40,7 @@
 		Kliknij i przeciągnij, aby zaznaczyć teren
 	</floater.string>
 	<floater.string name="status_selectcount">
-		[OBJ_COUNT] zaznaczonych obiektów, wpływ na strefę: [LAND_IMPACT]
+		[OBJ_COUNT] zaznaczonych obiektów, wpływ na strefę: [LAND_IMPACT] [secondlife:///app/openfloater/object_weights ?]
 	</floater.string>
 	<floater.string name="status_remaining_capacity">
 		Pojemność pozostała: [LAND_CAPACITY].
diff --git a/indra/newview/skins/default/xui/pt/floater_tools.xml b/indra/newview/skins/default/xui/pt/floater_tools.xml
index 0882f485a6e652b0911328b5b1dc385430b29b51..c0eab171c8bbdd39928e44f9cc88e64f93a9316f 100644
--- a/indra/newview/skins/default/xui/pt/floater_tools.xml
+++ b/indra/newview/skins/default/xui/pt/floater_tools.xml
@@ -40,7 +40,7 @@
 		Clicar e arrastar para selecionar a terra
 	</floater.string>
 	<floater.string name="status_selectcount">
-		[OBJ_COUNT] objetos selecionados, impacto no terreno [LAND_IMPACT]
+		[OBJ_COUNT] objetos selecionados, impacto no terreno [LAND_IMPACT] [secondlife:///app/openfloater/object_weights ?]
 	</floater.string>
 	<floater.string name="status_remaining_capacity">
 		Capacidade restante [LAND_CAPACITY].
diff --git a/indra/newview/skins/default/xui/ru/floater_tools.xml b/indra/newview/skins/default/xui/ru/floater_tools.xml
index 82ee3c49ae112976ff71d58d94935659a4b0c69c..44f54aabb6dfd00aea2739b67076b5d2fbe15072 100644
--- a/indra/newview/skins/default/xui/ru/floater_tools.xml
+++ b/indra/newview/skins/default/xui/ru/floater_tools.xml
@@ -40,7 +40,7 @@
 		Щелкните и перетащите для выделения земли
 	</floater.string>
 	<floater.string name="status_selectcount">
-		Выбрано объектов: [OBJ_COUNT], влияние на землю [LAND_IMPACT]
+		Выбрано объектов: [OBJ_COUNT], влияние на землю [LAND_IMPACT] [secondlife:///app/openfloater/object_weights ?]
 	</floater.string>
 	<floater.string name="status_remaining_capacity">
 		Остаток емкости [LAND_CAPACITY].
diff --git a/indra/newview/skins/default/xui/tr/floater_tools.xml b/indra/newview/skins/default/xui/tr/floater_tools.xml
index d6b9a4a533f79d4cb91b938908ec906dbfaa41c8..d48a617e388f01483405090a9b4b2e281f7cf630 100644
--- a/indra/newview/skins/default/xui/tr/floater_tools.xml
+++ b/indra/newview/skins/default/xui/tr/floater_tools.xml
@@ -40,7 +40,7 @@
 		Araziyi seçmek için tıklayın ve sürükleyin
 	</floater.string>
 	<floater.string name="status_selectcount">
-		[OBJ_COUNT] nesne seçili, [LAND_IMPACT] arazi etkisi
+		[OBJ_COUNT] nesne seçili, [LAND_IMPACT] arazi etkisi [secondlife:///app/openfloater/object_weights Ek bilgi]
 	</floater.string>
 	<floater.string name="status_remaining_capacity">
 		Kalan kapasite [LAND_CAPACITY].
diff --git a/indra/newview/skins/default/xui/zh/floater_tools.xml b/indra/newview/skins/default/xui/zh/floater_tools.xml
index 539c7454f1506453509da5697584e2a3bdceabb0..f83b058ce18b32196f875077a662324397e64d7e 100644
--- a/indra/newview/skins/default/xui/zh/floater_tools.xml
+++ b/indra/newview/skins/default/xui/zh/floater_tools.xml
@@ -40,7 +40,7 @@
 		按住並拖曳,可以選取土地
 	</floater.string>
 	<floater.string name="status_selectcount">
-		選取了 [OBJ_COUNT] 個物件,土地衝擊量 [LAND_IMPACT]
+		選取了 [OBJ_COUNT] 個物件,土地衝擊量 [LAND_IMPACT] [secondlife:///app/openfloater/object_weights 詳情]
 	</floater.string>
 	<floater.string name="status_remaining_capacity">
 		剩餘容納量 [LAND_CAPACITY]。