From cfd17f3322ec9c8efb120faa23adb83846272193 Mon Sep 17 00:00:00 2001
From: Tess Chu <tess@lindenlab.com>
Date: Tue, 2 Oct 2007 22:38:38 +0000
Subject: [PATCH] svn merge -r 70819:70853
 svn+ssh://svn/svn/linden/branches/urldispatcher-for-merge

---
 indra/llui/lltexteditor.cpp        |   3 +-
 indra/llui/lltexteditor.h          |  10 +-
 indra/newview/llcommandhandler.cpp |  80 ++++++++
 indra/newview/llcommandhandler.h   |  62 ++++++
 indra/newview/llfloatermap.cpp     |  44 -----
 indra/newview/llfloatermap.h       |   2 -
 indra/newview/llpanellogin.cpp     |   1 +
 indra/newview/llstartup.cpp        |  69 ++++---
 indra/newview/llstartup.h          |   8 +
 indra/newview/llurldispatcher.cpp  | 303 +++++++++++++++++++++++++++++
 indra/newview/llurldispatcher.h    |  32 +++
 indra/newview/llviewermenu.cpp     |   8 +-
 indra/newview/llviewerwindow.cpp   |  17 +-
 13 files changed, 548 insertions(+), 91 deletions(-)
 create mode 100644 indra/newview/llcommandhandler.cpp
 create mode 100644 indra/newview/llcommandhandler.h
 create mode 100644 indra/newview/llurldispatcher.cpp
 create mode 100644 indra/newview/llurldispatcher.h

diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp
index 1ec62f6927..daf3438d3c 100644
--- a/indra/llui/lltexteditor.cpp
+++ b/indra/llui/lltexteditor.cpp
@@ -58,7 +58,8 @@ const S32	SPACES_PER_TAB = 4;
 
 LLColor4 LLTextEditor::mLinkColor = LLColor4::blue;
 void (* LLTextEditor::mURLcallback)(const char*)              = NULL;
-BOOL (* LLTextEditor::mSecondlifeURLcallback)(LLString)   = NULL;
+bool (* LLTextEditor::mSecondlifeURLcallback)(const std::string&)   = NULL;
+bool (* LLTextEditor::mSecondlifeURLcallbackRightClick)(const std::string&)   = NULL;
 
 ///////////////////////////////////////////////////////////////////
 //virtuals
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index e19e799033..ea2e6b3efe 100644
--- a/indra/llui/lltexteditor.h
+++ b/indra/llui/lltexteditor.h
@@ -194,9 +194,10 @@ public:
 
 	// Callbacks
 	static void		setLinkColor(LLColor4 color) { mLinkColor = color; }
-	static void		setURLCallbacks(	void (*callback1) (const char* url), 
-										BOOL (*callback2) (LLString url)      ) 
-										{ mURLcallback = callback1; mSecondlifeURLcallback = callback2;}
+	static void		setURLCallbacks(void (*callback1) (const char* url), 
+									bool (*callback2) (const std::string& url),      
+									bool (*callback3) (const std::string& url)	) 
+									{ mURLcallback = callback1; mSecondlifeURLcallback = callback2; mSecondlifeURLcallbackRightClick = callback3;}
 
 	void			setOnScrollEndCallback(void (*callback)(void*), void* userdata);
 
@@ -318,7 +319,8 @@ public:
 	LLKeywords		mKeywords;
 	static LLColor4 mLinkColor;
 	static void			(*mURLcallback) (const char* url);
-	static BOOL			(*mSecondlifeURLcallback) (LLString url);
+	static bool			(*mSecondlifeURLcallback) (const std::string& url);
+	static bool			(*mSecondlifeURLcallbackRightClick) (const std::string& url);
 protected:
 	LLWString		mWText;
 	mutable LLString mUTF8Text;
diff --git a/indra/newview/llcommandhandler.cpp b/indra/newview/llcommandhandler.cpp
new file mode 100644
index 0000000000..47192c99f8
--- /dev/null
+++ b/indra/newview/llcommandhandler.cpp
@@ -0,0 +1,80 @@
+/**
+ * @file llcommandhandler.cpp
+ * @brief Central registry for text-driven "commands", most of
+ * which manipulate user interface.  For example, the command
+ * "agent (uuid) about" will open the UI for an avatar's profile.
+ *
+ * Copyright (c) 2007-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+#include "llviewerprecompiledheaders.h"
+
+#include "llcommandhandler.h"
+
+// system includes
+#include <boost/tokenizer.hpp>
+
+//---------------------------------------------------------------------------
+// Underlying registry for command handlers, not directly accessible.
+//---------------------------------------------------------------------------
+
+class LLCommandHandlerRegistry
+{
+public:
+	static LLCommandHandlerRegistry& instance();
+	void add(const char* cmd, LLCommandHandler* handler);
+	bool dispatch(const std::string& cmd, const std::vector<std::string>& params);
+
+private:
+	std::map<std::string, LLCommandHandler*> mMap;
+};
+
+// static 
+LLCommandHandlerRegistry& LLCommandHandlerRegistry::instance()
+{
+	// Force this to be initialized on first call, because we're going
+	// to be adding items to the std::map before main() and we can't
+	// rely on a global being initialized in the right order.
+	static LLCommandHandlerRegistry instance;
+	return instance;
+}
+
+void LLCommandHandlerRegistry::add(const char* cmd, LLCommandHandler* handler)
+{
+	mMap[cmd] = handler;
+}
+
+bool LLCommandHandlerRegistry::dispatch(const std::string& cmd,
+										const std::vector<std::string>& params)
+{
+	std::map<std::string, LLCommandHandler*>::iterator it = mMap.find(cmd);
+	if (it == mMap.end()) return false;
+	LLCommandHandler* handler = it->second;
+	if (!handler) return false;
+	return handler->handle(params);
+}
+
+//---------------------------------------------------------------------------
+// Automatic registration of commands, runs before main()
+//---------------------------------------------------------------------------
+
+LLCommandHandler::LLCommandHandler(const char* cmd)
+{
+	LLCommandHandlerRegistry::instance().add(cmd, this);
+}
+
+LLCommandHandler::~LLCommandHandler()
+{
+	// Don't care about unregistering these, all the handlers
+	// should be static objects.
+}
+
+//---------------------------------------------------------------------------
+// Public interface
+//---------------------------------------------------------------------------
+
+// static
+bool LLCommandDispatcher::dispatch(const std::string& cmd, const std::vector<std::string>& params)
+{
+	return LLCommandHandlerRegistry::instance().dispatch(cmd, params);
+}
diff --git a/indra/newview/llcommandhandler.h b/indra/newview/llcommandhandler.h
new file mode 100644
index 0000000000..65ce6c633b
--- /dev/null
+++ b/indra/newview/llcommandhandler.h
@@ -0,0 +1,62 @@
+/**
+ * @file llcommandhandler.h
+ * @brief Central registry for text-driven "commands", most of
+ * which manipulate user interface.  For example, the command
+ * "agent (uuid) about" will open the UI for an avatar's profile.
+ *
+ * Copyright (c) 2007-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+#ifndef LLCOMMANDHANDLER_H
+#define LLCOMMANDHANDLER_H
+
+/* To implement a command "foo" that takes one parameter,
+   a UUID, do this:
+
+class LLFooHandler : public LLCommandHandler
+{
+public:
+    // Inform the system you handle commands starting
+	// with "foo"
+	LLFooHandler() : LLCommandHandler("foo") { }
+
+    // Your code here
+	bool handle(const std::vector<std::string>& tokens)
+	{
+		if (tokens.size() < 1) return false;
+		LLUUID id( tokens[0] );
+		return doFoo(id);
+	}
+};
+
+// Creating the object registers with the dispatcher.
+LLFooHandler gFooHandler;
+*/
+
+class LLCommandHandler
+{
+public:
+	LLCommandHandler(const char* command);
+		// Automatically registers object to get called when 
+		// command is executed.
+		
+	virtual ~LLCommandHandler();
+
+	virtual bool handle(const std::vector<std::string>& params) = 0;
+		// Execute the command with a provided (possibly empty)
+		// list of parameters.
+		// Return true if you did something, false if the parameters
+		// are invalid or on error.
+};
+
+
+class LLCommandDispatcher
+{
+public:
+	static bool dispatch(const std::string& cmd, const std::vector<std::string>& params);
+		// Execute a command registered via the above mechanism,
+		// passing string parameters.
+		// Returns true if command was found and executed correctly.
+};
+
+#endif
diff --git a/indra/newview/llfloatermap.cpp b/indra/newview/llfloatermap.cpp
index 242f5b7991..d53929272c 100644
--- a/indra/newview/llfloatermap.cpp
+++ b/indra/newview/llfloatermap.cpp
@@ -197,47 +197,3 @@ void LLFloaterMap::toggle(void*)
 		}
 	}
 }
-
-
-BOOL process_secondlife_url(LLString url)
-{
-	S32 strpos, strpos2;
-
-	LLString slurlID = "slurl.com/secondlife/";
-	strpos = url.find(slurlID);
-	
-	if (strpos < 0)
-	{
-		slurlID="secondlife://";
-		strpos = url.find(slurlID);
-	}
-	
-	if (strpos >= 0) 
-	{
-		LLString simname;
-
-		strpos+=slurlID.length();
-		strpos2=url.find("/",strpos);
-		if (strpos2 < strpos) strpos2=url.length();
-		simname="secondlife://" + url.substr(strpos,url.length() - strpos);
-
-		LLURLSimString::setString( simname );
-		LLURLSimString::parse();
-
-		// if there is a world map
-		if ( gFloaterWorldMap )
-		{
-			// mark where the destination is
-			gFloaterWorldMap->trackURL( LLURLSimString::sInstance.mSimName.c_str(),
-										LLURLSimString::sInstance.mX,
-										LLURLSimString::sInstance.mY,
-										LLURLSimString::sInstance.mZ );
-
-			// display map
-			LLFloaterWorldMap::show( NULL, TRUE );
-		};
-
-		return TRUE;
-	}
-	return FALSE;
-}
diff --git a/indra/newview/llfloatermap.h b/indra/newview/llfloatermap.h
index 5b581c0fe5..8f7629be3f 100644
--- a/indra/newview/llfloatermap.h
+++ b/indra/newview/llfloatermap.h
@@ -46,6 +46,4 @@ protected:
 
 extern LLFloaterMap *gFloaterMap;
 
-BOOL process_secondlife_url(LLString url);
-
 #endif  // LL_LLFLOATERMAP_H
diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp
index 7a05f2bdbf..b730d15aab 100644
--- a/indra/newview/llpanellogin.cpp
+++ b/indra/newview/llpanellogin.cpp
@@ -31,6 +31,7 @@
 #include "lltextbox.h"
 #include "llui.h"
 #include "lluiconstants.h"
+#include "llurlsimstring.h"
 #include "llviewerbuild.h"
 #include "llviewerimagelist.h"
 #include "llviewermenu.h"			// for handle_preferences()
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index fe17664c7d..615f872de8 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -107,6 +107,8 @@
 #include "lltexturefetch.h"
 #include "lltoolmgr.h"
 #include "llui.h"
+#include "llurldispatcher.h"
+#include "llurlsimstring.h"
 #include "llurlwhitelist.h"
 #include "lluserauth.h"
 #include "llviewerassetstorage.h"
@@ -779,7 +781,7 @@ BOOL idle_startup()
 
 		//For HTML parsing in text boxes.
 		LLTextEditor::setLinkColor( gSavedSettings.getColor4("HTMLLinkColor") );
-		LLTextEditor::setURLCallbacks ( &LLWeb::loadURL, &process_secondlife_url );
+		LLTextEditor::setURLCallbacks ( &LLWeb::loadURL, &LLURLDispatcher::dispatch, &LLURLDispatcher::dispatch   );
 
 		//-------------------------------------------------
 		// Handle startup progress screen
@@ -2254,25 +2256,8 @@ BOOL idle_startup()
 			gAgentPilot.startPlayback();
 		}
 
-		// ok, if we've gotten this far and have a startup URL
-		if (LLURLSimString::sInstance.parse())
-		{
-			// kick off request for landmark to startup URL
-			if(gFloaterWorldMap)
-			{
-				LLVector3 pos = gAgent.getPositionAgent();
-				if( LLURLSimString::sInstance.mSimName != gAgent.getRegion()->getName()
-					|| LLURLSimString::sInstance.mX != llfloor(pos[VX])
-					|| LLURLSimString::sInstance.mY != llfloor(pos[VY]) )
-				{
-					LLFloaterWorldMap::show(NULL, TRUE);
-					gFloaterWorldMap->trackURL(LLURLSimString::sInstance.mSimName,
-											   LLURLSimString::sInstance.mX,
-											   LLURLSimString::sInstance.mY,
-											   LLURLSimString::sInstance.mZ);
-				}
-			}
-		}
+		// If we've got a startup URL, dispatch it
+		LLStartUp::dispatchURL();
 		
 		// Clean up the userauth stuff.
 		if (gUserAuthp)
@@ -3664,13 +3649,6 @@ void release_start_screen()
 	gStartImageGL = NULL;
 }
 
-// static
-bool LLStartUp::canGoFullscreen()
-{
-	return LLStartUp::getStartupState() >= STATE_WORLD_INIT;
-}
-
-
 // static
 void LLStartUp::setStartupState( S32 state )
 {
@@ -3692,3 +3670,40 @@ void reset_login()
 	if ( gFloaterMap )
 		gFloaterMap->setVisible( FALSE );
 }
+
+//---------------------------------------------------------------------------
+
+std::string LLStartUp::sSLURLCommand;
+
+bool LLStartUp::canGoFullscreen()
+{
+	return gStartupState >= STATE_WORLD_INIT;
+}
+
+bool LLStartUp::dispatchURL()
+{
+	// ok, if we've gotten this far and have a startup URL
+	if (!sSLURLCommand.empty())
+	{
+		LLURLDispatcher::dispatch(sSLURLCommand);
+	}
+	else if (LLURLSimString::parse())
+	{
+		// If we started with a location, but we're already
+		// at that location, don't pop dialogs open.
+		LLVector3 pos = gAgent.getPositionAgent();
+		F32 dx = pos.mV[VX] - (F32)LLURLSimString::sInstance.mX;
+		F32 dy = pos.mV[VY] - (F32)LLURLSimString::sInstance.mY;
+		const F32 SLOP = 2.f;	// meters
+
+		if( LLURLSimString::sInstance.mSimName != gAgent.getRegion()->getName()
+			|| (dx*dx > SLOP*SLOP)
+			|| (dy*dy > SLOP*SLOP) )
+		{
+			std::string url = LLURLSimString::getURL();
+			LLURLDispatcher::dispatch(url);
+		}
+		return true;
+	}
+	return false;
+}
diff --git a/indra/newview/llstartup.h b/indra/newview/llstartup.h
index 9e1ff3c151..88ed0bdd24 100644
--- a/indra/newview/llstartup.h
+++ b/indra/newview/llstartup.h
@@ -64,6 +64,14 @@ public:
 	static void	setStartupState( S32 state );
 	static S32	getStartupState()				{ return gStartupState;		};
 
+	static bool dispatchURL();
+		// if we have a SLURL or sim string ("Ahern/123/45") that started
+		// the viewer, dispatch it
+
+	static std::string sSLURLCommand;
+		// *HACK: On startup, if we were passed a secondlife://app/do/foo
+		// command URL, store it for later processing.
+
 protected:
 	static S32 gStartupState;			// Do not set directly, use LLStartup::setStartupState
 };
diff --git a/indra/newview/llurldispatcher.cpp b/indra/newview/llurldispatcher.cpp
new file mode 100644
index 0000000000..4145ec305c
--- /dev/null
+++ b/indra/newview/llurldispatcher.cpp
@@ -0,0 +1,303 @@
+/**
+ * @file llurldispatcher.cpp
+ * @brief Central registry for all URL handlers
+ *
+ * Copyright (c) 2007-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+#include "llviewerprecompiledheaders.h"
+
+#include "llurldispatcher.h"
+
+// viewer includes
+#include "llagent.h"			// teleportViaLocation()
+#include "llcommandhandler.h"
+// *FIX: code in merge sl-search-8
+//#include "llfloaterurldisplay.h"
+#include "llfloaterdirectory.h"
+#include "llfloaterhtmlhelp.h"
+#include "llfloaterworldmap.h"
+#include "llpanellogin.h"
+#include "llstartup.h"			// gStartupState
+#include "llurlsimstring.h"
+#include "llviewerwindow.h"		// alertXml()
+#include "llworldmap.h"
+
+// library includes
+#include "llsd.h"
+
+// system includes
+#include <boost/tokenizer.hpp>
+
+const std::string SLURL_SL_HELP_PREFIX		= "secondlife://app.";
+const std::string SLURL_SL_PREFIX			= "sl://";
+const std::string SLURL_SECONDLIFE_PREFIX	= "secondlife://";
+const std::string SLURL_SLURL_PREFIX		= "http://slurl.com/secondlife/";
+
+const std::string SLURL_APP_TOKEN = "app/";
+
+class LLURLDispatcherImpl
+{
+public:
+	static bool isSLURL(const std::string& url);
+
+	static bool isSLURLCommand(const std::string& url);
+
+	static bool dispatch(const std::string& url);
+		// returns true if handled
+
+	static bool dispatchRightClick(const std::string& url);
+
+private:
+	static bool dispatchCore(const std::string& url, bool right_mouse);
+		// handles both left and right click
+
+	static bool dispatchHelp(const std::string& url, BOOL right_mouse);
+		// Handles sl://app.floater.html.help by showing Help floater.
+		// Returns true if handled.
+
+	static bool dispatchApp(const std::string& url, BOOL right_mouse);
+		// Handles secondlife://app/agent/<agent_id>/about and similar
+		// by showing panel in Search floater.
+		// Returns true if handled.
+
+	static bool dispatchRegion(const std::string& url, BOOL right_mouse);
+		// handles secondlife://Ahern/123/45/67/
+		// Returns true if handled.
+
+// *FIX: code in merge sl-search-8
+//	static void regionHandleCallback(U64 handle, const std::string& url,
+//		const LLUUID& snapshot_id, bool teleport);
+//		// Called by LLWorldMap when a region name has been resolved to a
+//		// location in-world, used by places-panel display.
+
+	static bool matchPrefix(const std::string& url, const std::string& prefix);
+	
+	static std::string stripProtocol(const std::string& url);
+
+	friend class LLTeleportHandler;
+};
+
+// static
+bool LLURLDispatcherImpl::isSLURL(const std::string& url)
+{
+	if (matchPrefix(url, SLURL_SL_HELP_PREFIX)) return true;
+	if (matchPrefix(url, SLURL_SL_PREFIX)) return true;
+	if (matchPrefix(url, SLURL_SECONDLIFE_PREFIX)) return true;
+	if (matchPrefix(url, SLURL_SLURL_PREFIX)) return true;
+	return false;
+}
+
+// static
+bool LLURLDispatcherImpl::isSLURLCommand(const std::string& url)
+{
+	if (matchPrefix(url, SLURL_SL_PREFIX + SLURL_APP_TOKEN)
+		|| matchPrefix(url, SLURL_SECONDLIFE_PREFIX + SLURL_APP_TOKEN)
+		|| matchPrefix(url, SLURL_SLURL_PREFIX + SLURL_APP_TOKEN) )
+	{
+		return true;
+	}
+	return false;
+}
+
+// static
+bool LLURLDispatcherImpl::dispatchCore(const std::string& url, bool right_mouse)
+{
+	if (url.empty()) return false;
+	if (dispatchHelp(url, right_mouse)) return true;
+	if (dispatchApp(url, right_mouse)) return true;
+	if (dispatchRegion(url, right_mouse)) return true;
+	return false;
+}
+
+// static
+bool LLURLDispatcherImpl::dispatch(const std::string& url)
+{
+	llinfos << "url: " << url << llendl;
+	return dispatchCore(url, false);	// not right click
+}
+
+// static
+bool LLURLDispatcherImpl::dispatchRightClick(const std::string& url)
+{
+	llinfos << "url: " << url << llendl;
+	return dispatchCore(url, true);	// yes right click
+}
+// static
+bool LLURLDispatcherImpl::dispatchHelp(const std::string& url, BOOL right_mouse)
+{
+	if (matchPrefix(url, SLURL_SL_HELP_PREFIX))
+	{
+		gViewerHtmlHelp.show();
+		return true;
+	}
+	return false;
+}
+
+// static
+bool LLURLDispatcherImpl::dispatchApp(const std::string& url, BOOL right_mouse)
+{
+	if (!isSLURL(url))
+	{
+		return false;
+	}
+	std::string s = stripProtocol(url);
+
+	// At this point, "secondlife://app/foo/bar/baz/" should be left
+	// as: 	"app/foo/bar/baz/"
+	typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+	boost::char_separator<char> sep("/", "", boost::drop_empty_tokens);
+	tokenizer tokens(s, sep);
+	tokenizer::iterator it = tokens.begin();
+	tokenizer::iterator end = tokens.end();
+
+	// Build parameter list suitable for LLDispatcher dispatch
+	if (it == end) return false;
+	if (*it != "app") return false;
+	++it;
+
+	if (it == end) return false;
+	std::string cmd = *it;
+	++it;
+
+	std::vector<std::string> params;
+	for ( ; it != end; ++it)
+	{
+		params.push_back(*it);
+	}
+
+	bool handled = LLCommandDispatcher::dispatch(cmd, params);
+	if (handled) return true;
+
+	// Inform the user we can't handle this
+	std::map<std::string, std::string> args;
+	args["[SLURL]"] = url;
+	gViewerWindow->alertXml("BadURL", args);
+	return false;
+}
+
+// static
+bool LLURLDispatcherImpl::dispatchRegion(const std::string& url, BOOL right_mouse)
+{
+	if (!isSLURL(url))
+	{
+		return false;
+	}
+
+	// Before we're logged in, need to update the startup screen
+	// to tell the user where they are going.
+	if (LLStartUp::getStartupState() < STATE_CLEANUP)
+	{
+		// Parse it and stash in globals, it will be dispatched in
+		// STATE_CLEANUP.
+		LLURLSimString::setString(url);
+		// We're at the login screen, so make sure user can see
+		// the login location box to know where they are going.
+		LLPanelLogin::refreshLocation( true );
+		return true;
+	}
+
+	std::string sim_string = stripProtocol(url);
+	std::string region_name;
+	S32 x = 128;
+	S32 y = 128;
+	S32 z = 0;
+	LLURLSimString::parse(sim_string, &region_name, &x, &y, &z);
+	if (gFloaterWorldMap)
+	{
+		llinfos << "Opening map to " << region_name << llendl;
+		gFloaterWorldMap->trackURL( region_name.c_str(), x, y, z );
+		LLFloaterWorldMap::show(NULL, TRUE);
+	}
+	return true;
+}
+
+// static
+bool LLURLDispatcherImpl::matchPrefix(const std::string& url, const std::string& prefix)
+{
+	std::string test_prefix = url.substr(0, prefix.length());
+	LLString::toLower(test_prefix);
+	return test_prefix == prefix;
+}
+
+// static
+std::string LLURLDispatcherImpl::stripProtocol(const std::string& url)
+{
+	std::string stripped = url;
+	if (matchPrefix(stripped, SLURL_SL_HELP_PREFIX))
+	{
+		stripped.erase(0, SLURL_SL_HELP_PREFIX.length());
+	}
+	else if (matchPrefix(stripped, SLURL_SL_PREFIX))
+	{
+		stripped.erase(0, SLURL_SL_PREFIX.length());
+	}
+	else if (matchPrefix(stripped, SLURL_SECONDLIFE_PREFIX))
+	{
+		stripped.erase(0, SLURL_SECONDLIFE_PREFIX.length());
+	}
+	else if (matchPrefix(stripped, SLURL_SLURL_PREFIX))
+	{
+		stripped.erase(0, SLURL_SLURL_PREFIX.length());
+	}
+	return stripped;
+}
+
+// *FIX: code in merge sl-search-8
+//
+////---------------------------------------------------------------------------
+//// Teleportation links are handled here because they are tightly coupled
+//// to URL parsing and sim-fragment parsing
+//class LLTeleportHandler : public LLCommandHandler
+//{
+//public:
+//	LLTeleportHandler() : LLCommandHandler("teleport") { }
+//	bool handle(const std::vector<std::string>& tokens)
+//	{
+//		// construct a "normal" SLURL, resolve the region to
+//		// a global position, and teleport to it
+//		if (tokens.size() < 1) return false;
+//
+//		// Region names may be %20 escaped.
+//		std::string region_name = LLURLSimString::unescapeRegionName(tokens[0]);
+//
+//		// build secondlife://De%20Haro/123/45/67 for use in callback
+//		std::string url = SLURL_SECONDLIFE_PREFIX;
+//		for (size_t i = 0; i < tokens.size(); ++i)
+//		{
+//			url += tokens[i] + "/";
+//		}
+//		gWorldMap->sendNamedRegionRequest(region_name,
+//			LLURLDispatcherImpl::regionHandleCallback,
+//			url,
+//			true);	// teleport
+//		return true;
+//	}
+//};
+//LLTeleportHandler gTeleportHandler;
+
+//---------------------------------------------------------------------------
+
+// static
+bool LLURLDispatcher::isSLURL(const std::string& url)
+{
+	return LLURLDispatcherImpl::isSLURL(url);
+}
+
+// static
+bool LLURLDispatcher::isSLURLCommand(const std::string& url)
+{
+	return LLURLDispatcherImpl::isSLURLCommand(url);
+}
+
+// static
+bool LLURLDispatcher::dispatch(const std::string& url)
+{
+	return LLURLDispatcherImpl::dispatch(url);
+}
+// static
+bool LLURLDispatcher::dispatchRightClick(const std::string& url)
+{
+	return LLURLDispatcherImpl::dispatchRightClick(url);
+}
+
diff --git a/indra/newview/llurldispatcher.h b/indra/newview/llurldispatcher.h
new file mode 100644
index 0000000000..e701a01be4
--- /dev/null
+++ b/indra/newview/llurldispatcher.h
@@ -0,0 +1,32 @@
+/**
+ * @file llurldispatcher.h
+ * @brief Central registry for all SL URL handlers
+ *
+ * Copyright (c) 2007-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+#ifndef LLURLDISPATCHER_H
+#define LLURLDISPATCHER_H
+
+class LLURLDispatcher
+{
+public:
+	static bool isSLURL(const std::string& url);
+		// Is this any sort of secondlife:// or sl:// URL?
+
+	static bool isSLURLCommand(const std::string& url);
+		// Is this a special secondlife://app/ URL?
+
+	static bool dispatch(const std::string& url);
+		// At startup time and on clicks in internal web browsers,
+		// teleport, open map, or run requested command.
+		// Handles:
+		//   secondlife://RegionName/123/45/67/
+		//   secondlife://app/agent/3d6181b0-6a4b-97ef-18d8-722652995cf1/show
+		//   sl://app/foo/bar
+		// Returns true if someone handled the URL.
+	static bool dispatchRightClick(const std::string& url);
+
+};
+
+#endif
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index c242d855f1..adea8968e5 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -83,7 +83,6 @@
 #include "llfloatergroups.h"
 #include "llfloaterhtml.h"
 #include "llfloaterhtmlhelp.h"
-#include "llfloaterhtmlfind.h"
 #include "llfloaterinspect.h"
 #include "llfloaterlagmeter.h"
 #include "llfloaterland.h"
@@ -340,6 +339,7 @@ void toggle_show_xui_names(void *);
 BOOL check_show_xui_names(void *);
 
 // Debug UI
+void handle_slurl_test(void*);
 void handle_save_to_xml(void*);
 void handle_load_from_xml(void*);
 
@@ -976,6 +976,7 @@ extern BOOL gVectorizePerfTest;
 
 void init_debug_ui_menu(LLMenuGL* menu)
 {
+	menu->append(new LLMenuItemCallGL("SLURL Test", &handle_slurl_test));
 	menu->append(new LLMenuItemCallGL("Editable UI", &edit_ui));
 	menu->append(new LLMenuItemToggleGL("Async Keystrokes", &gHandleKeysAsync));
 	menu->append(new LLMenuItemCallGL( "Dump SelectMgr", &dump_select_mgr));
@@ -7282,6 +7283,11 @@ void handle_load_from_xml(void*)
 	}
 }
 
+void handle_slurl_test(void*)
+{
+	LLFloaterHtml::getInstance()->show("http://user.lindenlab.com/~james/slurl.html", "SLURL Test");
+}
+
 void handle_rebake_textures(void*)
 {
 	LLVOAvatar* avatar = gAgent.getAvatarObject();
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 897831afc0..d4b87ceba0 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -136,6 +136,7 @@
 #include "lltoolview.h"
 #include "llvieweruictrlfactory.h"
 #include "lluploaddialog.h"
+#include "llurldispatcher.h"		// SLURL from other app instance
 #include "llviewercamera.h"
 #include "llviewergesture.h"
 #include "llviewerimagelist.h"
@@ -1394,22 +1395,14 @@ void LLViewerWindow::handleWindowUnblock(LLWindow *window)
 
 void LLViewerWindow::handleDataCopy(LLWindow *window, S32 data_type, void *data)
 {
+	const S32 SLURL_MESSAGE_TYPE = 0;
 	switch (data_type)
 	{
-	case 0:
+	case SLURL_MESSAGE_TYPE:
 		// received URL
-		if (LLURLSimString::unpack_data(data))
+		std::string url = (const char*)data;
+		if (LLURLDispatcher::dispatch(url))
 		{
-			if(gFloaterWorldMap)
-			{
-				gFloaterWorldMap->trackURL(LLURLSimString::sInstance.mSimName,
-										   LLURLSimString::sInstance.mX,
-										   LLURLSimString::sInstance.mY,
-										   LLURLSimString::sInstance.mZ);
-
-				LLFloaterWorldMap::show(NULL, TRUE);
-			}
-
 			// bring window to foreground, as it has just been "launched" from a URL
 			mWindow->bringToFront();
 		}
-- 
GitLab