diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp
index c300fe55d9388f46b99e9346dda5bba9e7279b82..87669574c2543860de653344ef14484115fcb17e 100644
--- a/indra/llui/llui.cpp
+++ b/indra/llui/llui.cpp
@@ -61,6 +61,8 @@
 // for XUIParse
 #include "llquaternion.h"
 #include <boost/tokenizer.hpp>
+#include <boost/algorithm/string/find_iterator.hpp>
+#include <boost/algorithm/string/finder.hpp>
 
 //
 // Globals
@@ -2020,6 +2022,53 @@ void LLUI::positionViewNearMouse(LLView* view, S32 spawn_x, S32 spawn_y)
 	view->translateIntoRectWithExclusion( virtual_window_rect, mouse_rect, FALSE );
 }
 
+LLView* LLUI::resolvePath(LLView* context, const std::string& path)
+{
+	// Nothing about resolvePath() should require non-const LLView*. If caller
+	// wants non-const, call the const flavor and then cast away const-ness.
+	return const_cast<LLView*>(resolvePath(const_cast<const LLView*>(context), path));
+}
+
+const LLView* LLUI::resolvePath(const LLView* context, const std::string& path)
+{
+	// Create an iterator over slash-separated parts of 'path'. Dereferencing
+	// this iterator returns an iterator_range over the substring. Unlike
+	// LLStringUtil::getTokens(), this split_iterator doesn't combine adjacent
+	// delimiters: leading/trailing slash produces an empty substring, double
+	// slash produces an empty substring. That's what we need.
+	boost::split_iterator<std::string::const_iterator> ti(path, boost::first_finder("/")), tend;
+
+	if (ti == tend)
+	{
+		// 'path' is completely empty, no navigation
+		return context;
+	}
+
+	// leading / means "start at root"
+	if (ti->empty())
+	{
+		context = getRootView();
+		++ti;
+	}
+
+	bool recurse = false;
+	for (; ti != tend && context; ++ti)
+	{
+		if (ti->empty()) 
+		{
+			recurse = true;
+		}
+		else
+		{
+			std::string part(ti->begin(), ti->end());
+			context = context->findChildView(part, recurse);
+			recurse = false;
+		}
+	}
+
+	return context;
+}
+
 
 // LLLocalClipRect and LLScreenClipRect moved to lllocalcliprect.h/cpp
 
diff --git a/indra/llui/llui.h b/indra/llui/llui.h
index 62d10df8b28852db94925ffdffa401f9c12c7d46..50cb9e663200f91c5b50ed2fc8f559c273c3b236 100644
--- a/indra/llui/llui.h
+++ b/indra/llui/llui.h
@@ -185,6 +185,33 @@ class LLUI
 	//helper functions (should probably move free standing rendering helper functions here)
 	static LLView* getRootView() { return sRootView; }
 	static void setRootView(LLView* view) { sRootView = view; }
+	/**
+	 * Walk the LLView tree to resolve a path
+	 * Paths can be discovered using Develop > XUI > Show XUI Paths
+	 *
+	 * A leading "/" indicates the root of the tree is the starting
+	 * position of the search, (otherwise the context node is used)
+	 *
+	 * Adjacent "//" mean that the next level of the search is done
+	 * recursively ("descendant" rather than "child").
+	 *
+	 * Return values: If no match is found, NULL is returned,
+	 * otherwise the matching LLView* is returned.
+	 *
+	 * Examples:
+	 *
+	 * "/" -> return the root view
+	 * "/foo" -> find "foo" as a direct child of the root
+	 * "foo" -> find "foo" as a direct child of the context node
+	 * "//foo" -> find the first "foo" child anywhere in the tree
+	 * "/foo/bar" -> find "foo" as direct child of the root, and
+	 *      "bar" as a direct child of "foo"
+	 * "//foo//bar/baz" -> find the first "foo" anywhere in the
+	 *      tree, the first "bar" anywhere under it, and "baz"
+	 *      as a direct child of that
+	 */
+	static const LLView* resolvePath(const LLView* context, const std::string& path);
+	static LLView* resolvePath(LLView* context, const std::string& path);
 	static std::string locateSkin(const std::string& filename);
 	static void setMousePositionScreen(S32 x, S32 y);
 	static void getMousePositionScreen(S32 *x, S32 *y);
diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp
index 4d6eac4958e25c95a46f6129a416fb859d0d6acf..6b2cd71d406ac4a493eb6de9dd66a6b9d2b2d2b5 100644
--- a/indra/newview/lluilistener.cpp
+++ b/indra/newview/lluilistener.cpp
@@ -34,9 +34,11 @@
 // std headers
 // external library headers
 // other Linden headers
+#include "llui.h" // getRootView(), resolvePath()
 #include "lluictrl.h"
 #include "llerror.h"
 
+
 LLUIListener::LLUIListener():
     LLEventAPI("UI",
                "LLUICtrl::CommitCallbackRegistry listener.\n"
@@ -47,6 +49,12 @@ LLUIListener::LLUIListener():
         "as if from a user gesture on a menu -- or a button click.",
         &LLUIListener::call,
         LLSD().with("function", LLSD()));
+
+    add("getValue",
+        "For the UI control identified by the path in [\"path\"], return the control's\n"
+        "current value as [\"value\"] reply.",
+        &LLUIListener::getValue,
+        LLSDMap("path", LLSD())("reply", LLSD()));
 }
 
 void LLUIListener::call(const LLSD& event) const
@@ -71,3 +79,23 @@ void LLUIListener::call(const LLSD& event) const
         (*func)(NULL, event["parameter"]);
     }
 }
+
+void LLUIListener::getValue(const LLSD&event) const
+{
+    LLSD reply = LLSD::emptyMap();
+
+    const LLView* root = LLUI::getRootView();
+    const LLView* view = LLUI::resolvePath(root, event["path"].asString());
+    const LLUICtrl* ctrl(dynamic_cast<const LLUICtrl*>(view));
+
+    if (ctrl) 
+    {
+        reply["value"] = ctrl->getValue();
+    }
+    else
+    {
+        // *TODO: ??? return something indicating failure to resolve
+    }
+    
+    sendReply(reply, event);
+}
diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h
index e7847f01e8a3a4b6345364c63adf9db2c873a0e6..08724024dc84401b6add6c79d2f26bdf6d108ac0 100644
--- a/indra/newview/lluilistener.h
+++ b/indra/newview/lluilistener.h
@@ -41,6 +41,7 @@ class LLUIListener: public LLEventAPI
 
 private:
     void call(const LLSD& event) const;
+    void getValue(const LLSD&event) const;
 };
 
 #endif /* ! defined(LL_LLUILISTENER_H) */