Always use mojo_shell in over-http mode

--origin tells mojo_shell to map all mojo: urls
to to a new base-url instead of the build directory.

This makes skydb's mojo_shell *always* use the network
loading path, which is what Android mojo_shell does
and is more like how mojo_shell will eventually be
used.

I also fixed the disk-space leak in the
dynamic_application_loader's NetworkLoader path
by having it immediately unlink the /tmp copy
of the library after dlopen.

In order to keep pprof working I had to teach the
dynamic_application_loader to write out a map of
/tmp/file -> url mappings so that we can fix
the pprof file afterwords.

This will "break" skydb --gdb on linux in exactly
as much as it is already broken on Android, but
I'm working on a build-id based solution for both
so that gdb knows how to find symbols for
non-existant randomly named /tmp libraries.

R=abarth@chromium.org, viettrungluu@chromium.org
BUG=450696

Review URL: https://codereview.chromium.org/829183005
diff --git a/shell/dynamic_application_loader.cc b/shell/dynamic_application_loader.cc
index e350b40..3744845 100644
--- a/shell/dynamic_application_loader.cc
+++ b/shell/dynamic_application_loader.cc
@@ -13,6 +13,7 @@
 #include "base/memory/scoped_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/message_loop/message_loop.h"
+#include "base/process/process.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
@@ -109,7 +110,7 @@
            base::Bind(&Loader::RunLibrary, weak_ptr_factory_.GetWeakPtr()));
   }
 
-  void ReportComplete() { loader_complete_callback_.Run(this); }
+  virtual void ReportComplete() { loader_complete_callback_.Run(this); }
 
  private:
   bool PeekContentHandler(std::string* mojo_shebang,
@@ -281,6 +282,28 @@
     return response_.Pass();
   }
 
+  static void RecordCacheToURLMapping(const base::FilePath& path,
+                                      const GURL& url) {
+    // This is used to extract symbols on android.
+    // TODO(eseidel): All users of this log should move to using the map file.
+    LOG(INFO) << "Caching mojo app " << url << " at " << path.value();
+
+    base::FilePath temp_dir;
+    base::GetTempDir(&temp_dir);
+    base::ProcessId pid = base::Process::Current().pid();
+    std::string map_name = base::StringPrintf("mojo_shell.%d.maps", pid);
+    base::FilePath map_path = temp_dir.Append(map_name);
+
+    // TODO(eseidel): Paths or URLs with spaces will need quoting.
+    std::string map_entry =
+        base::StringPrintf("%s %s\n", path.value().data(), url.spec().data());
+    // TODO(eseidel): AppendToFile is missing O_CREAT, crbug.com/450696
+    if (!PathExists(map_path))
+      base::WriteFile(map_path, map_entry.data(), map_entry.length());
+    else
+      base::AppendToFile(map_path, map_entry.data(), map_entry.length());
+  }
+
   void AsPath(
       base::TaskRunner* task_runner,
       base::Callback<void(const base::FilePath&, bool)> callback) override {
@@ -289,11 +312,14 @@
           FROM_HERE, base::Bind(callback, path_, base::PathExists(path_)));
       return;
     }
+    // We don't use the created file, just want the directory and random name.
     base::CreateTemporaryFile(&path_);
-    // This is used to extract symbols on android.
-    LOG(INFO) << "Caching mojo app " << url_ << " at " << path_.value();
+    base::DeleteFile(path_, false);
+    path_ = path_.AddExtension(".mojo");  // Make libraries easy to spot.
     common::CopyToFile(response_->body.Pass(), path_, task_runner,
                        base::Bind(callback, path_));
+
+    RecordCacheToURLMapping(path_, url_);
   }
 
   std::string MimeType() override {
@@ -341,6 +367,14 @@
     Load();
   }
 
+  void ReportComplete() override {
+    Loader::ReportComplete();
+    // As soon as we've loaded the library we can delete the cache file.
+    // Tools can read the mojo_shell.PID.maps file to find the original library.
+    if (!path_.empty())
+      DeleteFile(path_, false);
+  }
+
   const GURL url_;
   URLLoaderPtr url_loader_;
   URLResponsePtr response_;
diff --git a/sky/tools/debugger/prompt/prompt.cc b/sky/tools/debugger/prompt/prompt.cc
index 23f1c5d..f5340c4 100644
--- a/sky/tools/debugger/prompt/prompt.cc
+++ b/sky/tools/debugger/prompt/prompt.cc
@@ -102,12 +102,16 @@
 
   void OnWebSocketRequest(
       int connection_id, const net::HttpServerRequestInfo& info) override {
-    web_server_->Send500(connection_id, "http only");
+    Error(connection_id, "OnWebSocketRequest not implemented");
   }
 
   void OnWebSocketMessage(
       int connection_id, const std::string& data) override {
-    web_server_->Send500(connection_id, "http only");
+    Error(connection_id, "OnWebSocketMessage not implemented");
+  }
+
+  void Error(int connection_id, std::string message) {
+    web_server_->Send500(connection_id, message);
   }
 
   void Respond(int connection_id, std::string response) {
@@ -177,11 +181,21 @@
   }
 
   void StartProfiling(int connection_id) {
+#if !defined(NDEBUG) || !defined(ENABLE_PROFILING)
+    Error(connection_id,
+          "Profiling requires is_debug=false and enable_profiling=true");
+    return;
+#else
     base::debug::StartProfiling("sky_viewer.pprof");
     Respond(connection_id, "Starting profiling (stop with 'stop_profiling')");
+#endif
   }
 
   void StopProfiling(int connection_id) {
+    if (!base::debug::BeingProfiled()) {
+      Error(connection_id, "Profiling not started");
+      return;
+    }
     base::debug::StopProfiling();
     Respond(connection_id, "Stopped profiling");
   }
diff --git a/sky/tools/skydb b/sky/tools/skydb
index 3976346..eb13caa 100755
--- a/sky/tools/skydb
+++ b/sky/tools/skydb
@@ -79,12 +79,6 @@
         return os.environ.get('CHROME_REMOTE_DESKTOP_SESSION', False)
 
     def _wrap_for_android(self, shell_args):
-        build_dir_url = SkyServer.url_for_path(
-            self.pids['remote_sky_server_port'],
-            self.pids['sky_server_root'],
-            self.pids['build_dir'])
-        shell_args += ['--origin=%s' % build_dir_url]
-
         # am shell --esa: (someone shoot me now)
         #  [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]
         #  (to embed a comma into a string escape it using "\,")
@@ -106,6 +100,7 @@
             for mime_type in SUPPORTED_MIME_TYPES]
 
         remote_command_port = self.pids.get('remote_sky_command_port', self.pids['sky_command_port'])
+        remote_server_port = self.pids.get('remote_sky_server_port', self.pids['sky_server_port'])
 
         shell_args = [
             '--v=1',
@@ -115,6 +110,13 @@
             'mojo:window_manager',
         ]
 
+        # Map all mojo: urls to http: urls using the --origin command.
+        build_dir_url = SkyServer.url_for_path(
+            remote_server_port,
+            self.pids['sky_server_root'],
+            self.pids['build_dir'])
+        shell_args += ['--origin=%s' % build_dir_url]
+
         # Desktop-only work-around for mojo crashing under chromoting.
         if not is_android and args.use_osmesa:
             shell_args.append(
@@ -322,14 +324,46 @@
             url = args.url_or_path
         self._send_command_to_sky('/load', url)
 
+    def _read_mojo_map(self):
+        # TODO(eseidel): Does not work for android.
+        mojo_map_path = "/tmp/mojo_shell.%d.maps" % self.pids['mojo_shell_pid']
+        with open(mojo_map_path, 'r') as maps_file:
+            lines = maps_file.read().strip().split('\n')
+            return dict(map(lambda line: line.split(' '), lines))
+
     def stop_profiling_command(self, args):
         self._send_command_to_sky('/stop_profiling')
-        # We need to munge the profile to replace foo.mojo with libfoo.so so
-        # that pprof knows this represents an SO.
-        with open("sky_viewer.pprof", "r+") as profile_file:
-            data = profile_file.read()
+        mojo_map = self._read_mojo_map()
+
+        # TODO(eseidel): We should have a helper for resolving urls, etc.
+        remote_server_port = self.pids.get('remote_sky_server_port', self.pids['sky_server_port'])
+        build_dir_url = SkyServer.url_for_path(
+            remote_server_port,
+            self.pids['sky_server_root'],
+            self.pids['build_dir'])
+
+        # Map /tmp cache paths to urls and then to local build_dir paths.
+        def map_to_local_paths(match):
+            path = match.group('mojo_path')
+            url = mojo_map.get(path)
+            if url and url.startswith(build_dir_url):
+                return url.replace(build_dir_url, self.pids['build_dir'])
+            return match.group(0)
+
+        MOJO_PATH_RE = re.compile(r'(?P<mojo_path>\S+\.mojo)')
+        MOJO_NAME_RE = re.compile(r'(?P<mojo_name>\w+)\.mojo')
+
+        with open("sky_viewer.pprof", "rb+") as profile_file:
+            # ISO-8859-1 can represent arbitrary binary while still keeping
+            # ASCII characters in the ASCII range (allowing us to regexp).
+            # http://en.wikipedia.org/wiki/ISO/IEC_8859-1
+            as_string = profile_file.read().decode('iso-8859-1')
+            # Using the mojo_shell.PID.maps file tmp paths to build_dir paths.
+            as_string = MOJO_PATH_RE.sub(map_to_local_paths, as_string)
+            # In release foo.mojo is stripped but libfoo_library.so isn't.
+            as_string = MOJO_NAME_RE.sub(r'lib\1_library.so', as_string)
             profile_file.seek(0)
-            profile_file.write(re.sub(r'(\w+)\.mojo', r'lib\1_library.so', data))
+            profile_file.write(as_string.encode('iso-8859-1'))
             profile_file.truncate()
 
     def _command_base_url(self):
@@ -487,6 +521,9 @@
         logcat.wait()
         stack.wait()
 
+    def pids_command(self, args):
+        print json.dumps(self.pids, indent=1)
+
     def main(self):
         logging.basicConfig(level=logging.WARNING)
         logging.getLogger("requests").setLevel(logging.WARNING)
@@ -514,6 +551,10 @@
             help=('stop sky (as listed in %s)' % PID_FILE_PATH))
         stop_parser.set_defaults(func=self.stop_command)
 
+        pids_parser = subparsers.add_parser('pids',
+            help='dump the current skydb pids file')
+        pids_parser.set_defaults(func=self.pids_command)
+
         logcat_parser = subparsers.add_parser('logcat',
             help=('dump sky-related logs from device'))
         logcat_parser.set_defaults(func=self.logcat_command)