Merge "Update chrome_scrolls column names to be more accurate." into main
diff --git a/docs/analysis/debug-tracks.md b/docs/analysis/debug-tracks.md
new file mode 100644
index 0000000..e3c973c
--- /dev/null
+++ b/docs/analysis/debug-tracks.md
@@ -0,0 +1,33 @@
+# Debug Tracks
+
+Debug Tracks are a way to display tabular results from running a PerfettoSQL
+query as a new so-called "debug" track. Specifically, if the resultant table can
+be visualised in a slice format (for example, the
+[`slice`](sql-tables.autogen#slice) table), a debug track can be created from
+it.
+
+For a result table to be able to be visualised in a slice format, it should
+include:
+
+1.  A name (the name of the slice) column.
+1.  A non-null timestamp (the timestamp, in nanoseconds, at the start of the
+    slice) column.
+1.  (Optionally) a duration (the duration, in nanoseconds, of the slice) column.
+
+To create a new debug track:
+
+1.  Run a SQL query, and ensure its results are `slice`-like (as described
+    above).
+    ![Query for debug track](/docs/images/debug-tracks/debug-tracks-query.png)
+1.  Navigate to the Timeline view, and click on "Show debug track" to set up a
+    new debug track. Note that the names of the columns in the result table do
+    not necessarily have to be `name`, `ts`, or `dur`. Columns which
+    *semantically* match but have a different name can be selected from the
+    drop-down selectors.
+    ![Create a new debug track](/docs/images/debug-tracks/debug-tracks-create.png)
+1.  The debug track is visible as a pinned track near the top of the Timeline
+    view with slices from the table from which the track was created (note that
+    slices with no/zero duration will be displayed as instant events). The debug
+    track may be manually unpinned and then it should appear on the top of other
+    unpinned tracks.
+    ![Resultant debug track](/docs/images/debug-tracks/debug-tracks-result.png)
diff --git a/docs/images/debug-tracks/debug-tracks-create.png b/docs/images/debug-tracks/debug-tracks-create.png
new file mode 100644
index 0000000..c98b49b
--- /dev/null
+++ b/docs/images/debug-tracks/debug-tracks-create.png
Binary files differ
diff --git a/docs/images/debug-tracks/debug-tracks-query.png b/docs/images/debug-tracks/debug-tracks-query.png
new file mode 100644
index 0000000..99d2029
--- /dev/null
+++ b/docs/images/debug-tracks/debug-tracks-query.png
Binary files differ
diff --git a/docs/images/debug-tracks/debug-tracks-result.png b/docs/images/debug-tracks/debug-tracks-result.png
new file mode 100644
index 0000000..51d6fd2
--- /dev/null
+++ b/docs/images/debug-tracks/debug-tracks-result.png
Binary files differ
diff --git a/docs/toc.md b/docs/toc.md
index eca4ee7..d7c34e3 100644
--- a/docs/toc.md
+++ b/docs/toc.md
@@ -58,6 +58,7 @@
   * [Deep linking to Perfetto UI](visualization/deep-linking-to-perfetto-ui.md)
   * [Perfetto UI release process](visualization/perfetto-ui-release-process.md)
   * [Pivot tables](analysis/pivot-tables.md)
+  * [Debug tracks](analysis/debug-tracks.md)
 
 * [Core concepts](#)
   * [Trace configuration](concepts/config.md)
diff --git a/include/perfetto/protozero/message.h b/include/perfetto/protozero/message.h
index 069bc34..efcdf9f 100644
--- a/include/perfetto/protozero/message.h
+++ b/include/perfetto/protozero/message.h
@@ -181,6 +181,8 @@
   // The caller needs to guarantee that the appended data is properly
   // proto-encoded and each field has a proto preamble.
   void AppendRawProtoBytes(const void* data, size_t size) {
+    if (nested_message_)
+      EndNestedMessage();
     const uint8_t* src = reinterpret_cast<const uint8_t*>(data);
     WriteToStream(src, src + size);
   }
diff --git a/perfetto.rc b/perfetto.rc
index c635ac2..2118172 100644
--- a/perfetto.rc
+++ b/perfetto.rc
@@ -20,7 +20,6 @@
     user nobody
     group nobody
     task_profiles ServiceCapacityLow
-    priority -10
 
 service traced_probes /system/bin/traced_probes
     class late_start
@@ -35,7 +34,6 @@
     onrestart exec_background - nobody shell -- /system/bin/traced_probes --cleanup-after-crash
     file /dev/kmsg w
     capabilities DAC_READ_SEARCH
-    priority -10
 
 on property:persist.device_config.global_settings.sys_traced=1
     setprop persist.traced.enable 1
diff --git a/protos/perfetto/config/android/surfaceflinger_layers_config.proto b/protos/perfetto/config/android/surfaceflinger_layers_config.proto
index e5ffc0f..0b8d108 100644
--- a/protos/perfetto/config/android/surfaceflinger_layers_config.proto
+++ b/protos/perfetto/config/android/surfaceflinger_layers_config.proto
@@ -27,7 +27,6 @@
     // occurs.
     MODE_ACTIVE = 1;
 
-    // Default mode (applied by SurfaceFlinger if no mode is specified).
     // Generate layers snapshots from the transactions kept in the
     // SurfaceFlinger's internal ring buffer.
     // The layers snapshots generation occurs when this data source is flushed.
@@ -35,6 +34,11 @@
 
     // Trace a single layers snapshot.
     MODE_DUMP = 3;
+
+    // Default mode (applied by SurfaceFlinger if no mode is specified).
+    // Same as MODE_GENERATED, but triggers the layers snapshots generation only when a bugreport
+    // is taken.
+    MODE_GENERATED_BUGREPORT_ONLY = 4;
   }
   optional Mode mode = 1;
 
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 99b62de..bf19bbb 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -548,7 +548,6 @@
     // occurs.
     MODE_ACTIVE = 1;
 
-    // Default mode (applied by SurfaceFlinger if no mode is specified).
     // Generate layers snapshots from the transactions kept in the
     // SurfaceFlinger's internal ring buffer.
     // The layers snapshots generation occurs when this data source is flushed.
@@ -556,6 +555,11 @@
 
     // Trace a single layers snapshot.
     MODE_DUMP = 3;
+
+    // Default mode (applied by SurfaceFlinger if no mode is specified).
+    // Same as MODE_GENERATED, but triggers the layers snapshots generation only when a bugreport
+    // is taken.
+    MODE_GENERATED_BUGREPORT_ONLY = 4;
   }
   optional Mode mode = 1;
 
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 75043d0..e0ee8d3 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -548,7 +548,6 @@
     // occurs.
     MODE_ACTIVE = 1;
 
-    // Default mode (applied by SurfaceFlinger if no mode is specified).
     // Generate layers snapshots from the transactions kept in the
     // SurfaceFlinger's internal ring buffer.
     // The layers snapshots generation occurs when this data source is flushed.
@@ -556,6 +555,11 @@
 
     // Trace a single layers snapshot.
     MODE_DUMP = 3;
+
+    // Default mode (applied by SurfaceFlinger if no mode is specified).
+    // Same as MODE_GENERATED, but triggers the layers snapshots generation only when a bugreport
+    // is taken.
+    MODE_GENERATED_BUGREPORT_ONLY = 4;
   }
   optional Mode mode = 1;
 
diff --git a/python/generators/sql_processing/docs_parse.py b/python/generators/sql_processing/docs_parse.py
index 079a600..8f17b1b 100644
--- a/python/generators/sql_processing/docs_parse.py
+++ b/python/generators/sql_processing/docs_parse.py
@@ -230,14 +230,14 @@
     self._validate_only_contains_annotations(doc.annotations, {'@arg', '@ret'})
 
     ret_type, ret_desc = self._parse_ret(doc.annotations, ret)
-    name = self._parse_name(upper=True)
+    name = self._parse_name()
 
     if not is_snake_case(name):
       self._error('Function name %s is not snake_case (should be %s) ' %
                   (name, name.casefold()))
 
     return Function(
-        name=self._parse_name(upper=True),
+        name=name,
         desc=self._parse_desc_not_empty(doc.description),
         args=self._parse_args(doc.annotations, args),
         return_type=ret_type,
@@ -273,8 +273,14 @@
 
     self._validate_only_contains_annotations(doc.annotations,
                                              {'@arg', '@column'})
+    name = self._parse_name()
+
+    if not is_snake_case(name):
+      self._error('Function name %s is not snake_case (should be %s) ' %
+                  (name, name.casefold()))
+
     return TableFunction(
-        name=self._parse_name(upper=True),
+        name=name,
         desc=self._parse_desc_not_empty(doc.description),
         cols=self._parse_columns(doc.annotations, columns),
         args=self._parse_args(doc.annotations, args),
diff --git a/python/generators/sql_processing/utils.py b/python/generators/sql_processing/utils.py
index 97d7d8f..c701ceb 100644
--- a/python/generators/sql_processing/utils.py
+++ b/python/generators/sql_processing/utils.py
@@ -130,6 +130,12 @@
                     'Use CREATE PERFETTO FUNCTION instead.\n'
                     f'Offending file: {path}')
 
+    if 'create_view_function' in line.casefold():
+      errors.append(
+          'CREATE_VIEW_FUNCTION is deprecated in trace processor. '
+          'Use CREATE PERFETTO FUNCTION $name RETURNS TABLE instead.\n'
+          f'Offending file: {path}')
+
     if 'import(' in line.casefold():
       errors.append('SELECT IMPORT is deprecated in trace processor. '
                     'Use INCLUDE PERFETTO MODULE instead.\n'
diff --git a/python/test/stdlib_unittest.py b/python/test/stdlib_unittest.py
index d1a90be..479c603 100644
--- a/python/test/stdlib_unittest.py
+++ b/python/test/stdlib_unittest.py
@@ -104,7 +104,7 @@
 {ARGS_STR}
 {COLS_STR}
 SELECT CREATE_VIEW_FUNCTION(
-  'FOO_VIEW_FN({ARGS_SQL_STR})',
+  'foo_view_fn({ARGS_SQL_STR})',
   '{COLS_SQL_STR}',
   '{SQL_STR}'
 );
@@ -112,7 +112,7 @@
     self.assertListEqual(res.errors, [])
 
     fn = res.table_functions[0]
-    self.assertEqual(fn.name, 'FOO_VIEW_FN')
+    self.assertEqual(fn.name, 'foo_view_fn')
     self.assertEqual(fn.desc, 'First line. Second line.')
     self.assertEqual(
         fn.args, {
diff --git a/src/protozero/message_unittest.cc b/src/protozero/message_unittest.cc
index 5b3c09f..985ed5c 100644
--- a/src/protozero/message_unittest.cc
+++ b/src/protozero/message_unittest.cc
@@ -236,6 +236,27 @@
   EXPECT_EQ("42424242", GetNextSerializedBytes(4));
 }
 
+TEST_F(MessageTest, AppendRawProtoBytesFinalizesNestedMessage) {
+  Message* root_msg = NewMessage();
+
+  uint8_t buffer[42];
+  memset(buffer, 0x42, sizeof(buffer));
+
+  FakeChildMessage* nested_msg =
+      root_msg->BeginNestedMessage<FakeChildMessage>(9001 /* field_id */);
+  nested_msg->AppendVarInt(4 /* field_id */, 2);
+  uint8_t* nested_msg_size_field = nested_msg->size_field();
+
+  EXPECT_FALSE(nested_msg->is_finalized());
+  EXPECT_EQ(0u, *nested_msg_size_field);
+
+  root_msg->AppendRawProtoBytes(buffer, sizeof(buffer));
+
+  // Nested message should have been finalized as a side effect of appending
+  // raw bytes.
+  EXPECT_EQ(0x82u, *nested_msg_size_field);
+}
+
 TEST_F(MessageTest, AppendScatteredBytesFinalizesNestedMessage) {
   Message* root_msg = NewMessage();
 
diff --git a/src/trace_processor/metrics/sql/android/android_startup.sql b/src/trace_processor/metrics/sql/android/android_startup.sql
index 5a92db8..d2efb76 100644
--- a/src/trace_processor/metrics/sql/android/android_startup.sql
+++ b/src/trace_processor/metrics/sql/android/android_startup.sql
@@ -43,20 +43,16 @@
 -- Returns the slices for forked processes. Never present in hot starts.
 -- Prefer this over process start_ts, since the process might have
 -- been preforked.
-SELECT CREATE_VIEW_FUNCTION(
-  'ZYGOTE_FORK_FOR_LAUNCH(startup_id INT)',
-  'ts INT, dur INT',
-  '
-    SELECT slice.ts, slice.dur
-    FROM android_startups l
-    JOIN slice ON (
-      l.ts < slice.ts AND
-      slice.ts + slice.dur < l.ts_end AND
-      STR_SPLIT(slice.name, ": ", 1) = l.package
-    )
-    WHERE l.startup_id = $startup_id AND slice.name GLOB "Start proc: *"
-  '
-);
+CREATE PERFETTO FUNCTION zygote_fork_for_launch(startup_id INT)
+RETURNS TABLE(ts INT, dur INT) AS
+SELECT slice.ts, slice.dur
+FROM android_startups l
+JOIN slice ON (
+  l.ts < slice.ts AND
+  slice.ts + slice.dur < l.ts_end AND
+  STR_SPLIT(slice.name, ': ', 1) = l.package
+)
+WHERE l.startup_id = $startup_id AND slice.name GLOB 'Start proc: *';
 
 -- Returns the fully drawn slice proto given a launch id.
 CREATE PERFETTO FUNCTION report_fully_drawn_for_launch(startup_id INT)
@@ -79,17 +75,14 @@
 );
 
 -- Given a launch id and GLOB for a slice name, returns the N longest slice name and duration.
-SELECT CREATE_VIEW_FUNCTION(
-  'GET_LONG_SLICES_FOR_LAUNCH(startup_id INT, slice_name STRING, top_n INT)',
-  'slice_name STRING, slice_dur INT',
-  '
-    SELECT slice_name, slice_dur
-    FROM android_thread_slices_for_all_startups s
-    WHERE s.startup_id = $startup_id AND s.slice_name GLOB $slice_name
-    ORDER BY slice_dur DESC
-    LIMIT $top_n
-  '
-);
+CREATE PERFETTO FUNCTION get_long_slices_for_launch(
+  startup_id INT, slice_name STRING, top_n INT)
+RETURNS TABLE(slice_name STRING, slice_dur INT) AS
+SELECT slice_name, slice_dur
+FROM android_thread_slices_for_all_startups s
+WHERE s.startup_id = $startup_id AND s.slice_name GLOB $slice_name
+ORDER BY slice_dur DESC
+LIMIT $top_n;
 
 -- Define the view
 DROP VIEW IF EXISTS startup_view;
diff --git a/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql b/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql
index 9c891af..0814320 100644
--- a/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql
+++ b/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql
@@ -190,30 +190,30 @@
 -- slice for each of the layers. GROUP BY to deduplicate these rows.
 GROUP BY cuj_id, app_upid, app_vsync, sf_upid, sf_vsync;
 
-SELECT CREATE_VIEW_FUNCTION(
-  'FIND_ANDROID_JANK_CUJ_SF_MAIN_THREAD_SLICE(slice_name_glob STRING)',
-  'cuj_id INT, utid INT, vsync INT, id INT, name STRING, ts LONG, dur LONG, ts_end LONG',
-  '
-  WITH sf_vsync AS (
-    SELECT DISTINCT cuj_id, sf_vsync AS vsync
-    FROM android_jank_cuj_app_to_sf_match)
-  SELECT
-    cuj_id,
-    utid,
-    sf_vsync.vsync,
-    slice.id,
-    slice.name,
-    slice.ts,
-    slice.dur,
-    slice.ts + slice.dur AS ts_end
-  FROM slice
-  JOIN android_jank_cuj_sf_main_thread main_thread USING (track_id)
-  JOIN sf_vsync
-    ON vsync_from_name(slice.name) = sf_vsync.vsync
-  WHERE slice.name GLOB $slice_name_glob AND slice.dur > 0
-  ORDER BY cuj_id, vsync;
-  '
-);
+CREATE PERFETTO FUNCTION find_android_jank_cuj_sf_main_thread_slice(
+  slice_name_glob STRING)
+RETURNS TABLE(
+  cuj_id INT, utid INT, vsync INT, id INT,
+  name STRING, ts LONG, dur LONG, ts_end LONG)
+AS
+WITH sf_vsync AS (
+  SELECT DISTINCT cuj_id, sf_vsync AS vsync
+  FROM android_jank_cuj_app_to_sf_match)
+SELECT
+  cuj_id,
+  utid,
+  sf_vsync.vsync,
+  slice.id,
+  slice.name,
+  slice.ts,
+  slice.dur,
+  slice.ts + slice.dur AS ts_end
+FROM slice
+JOIN android_jank_cuj_sf_main_thread main_thread USING (track_id)
+JOIN sf_vsync
+  ON vsync_from_name(slice.name) = sf_vsync.vsync
+WHERE slice.name GLOB $slice_name_glob AND slice.dur > 0
+ORDER BY cuj_id, vsync;
 
 DROP TABLE IF EXISTS android_jank_cuj_sf_commit_slice;
 CREATE PERFETTO TABLE android_jank_cuj_sf_commit_slice AS
diff --git a/src/trace_processor/metrics/sql/android/jank/relevant_threads.sql b/src/trace_processor/metrics/sql/android/jank/relevant_threads.sql
index b647721..d3fc193 100644
--- a/src/trace_processor/metrics/sql/android/jank/relevant_threads.sql
+++ b/src/trace_processor/metrics/sql/android/jank/relevant_threads.sql
@@ -25,22 +25,18 @@
   -- Some CUJs use a dedicated thread for Choreographer callbacks
   OR (p.main_thread_override = thread.name);
 
-SELECT CREATE_VIEW_FUNCTION(
-  'ANDROID_JANK_CUJ_APP_THREAD(thread_name STRING)',
-  'cuj_id INT, upid INT, utid INT, name STRING, track_id INT',
-  '
-  SELECT
-    cuj_id,
-    cuj.upid,
-    utid,
-    thread.name,
-    thread_track.id AS track_id
-  FROM thread
-  JOIN android_jank_cuj cuj USING (upid)
-  JOIN thread_track USING (utid)
-  WHERE thread.name = $thread_name;
-  '
-);
+CREATE PERFETTO FUNCTION android_jank_cuj_app_thread(thread_name STRING)
+RETURNS TABLE(cuj_id INT, upid INT, utid INT, name STRING, track_id INT) AS
+SELECT
+  cuj_id,
+  cuj.upid,
+  utid,
+  thread.name,
+  thread_track.id AS track_id
+FROM thread
+JOIN android_jank_cuj cuj USING (upid)
+JOIN thread_track USING (utid)
+WHERE thread.name = $thread_name;
 
 DROP TABLE IF EXISTS android_jank_cuj_render_thread;
 CREATE PERFETTO TABLE android_jank_cuj_render_thread AS
@@ -68,17 +64,13 @@
 JOIN thread_track USING (utid)
 WHERE thread.is_main_thread;
 
-SELECT CREATE_VIEW_FUNCTION(
-  'ANDROID_JANK_CUJ_SF_THREAD(thread_name STRING)',
-  'upid INT, utid INT, name STRING, track_id INT',
-  '
-  SELECT upid, utid, thread.name, thread_track.id AS track_id
-  FROM thread
-  JOIN android_jank_cuj_sf_process sf_process USING (upid)
-  JOIN thread_track USING (utid)
-  WHERE thread.name = $thread_name;
-  '
-);
+CREATE PERFETTO FUNCTION android_jank_cuj_sf_thread(thread_name STRING)
+RETURNS TABLE(upid INT, utid INT, name STRING, track_id INT) AS
+SELECT upid, utid, thread.name, thread_track.id AS track_id
+FROM thread
+JOIN android_jank_cuj_sf_process sf_process USING (upid)
+JOIN thread_track USING (utid)
+WHERE thread.name = $thread_name;
 
 DROP TABLE IF EXISTS android_jank_cuj_sf_gpu_completion_thread;
 CREATE PERFETTO TABLE android_jank_cuj_sf_gpu_completion_thread AS
diff --git a/src/trace_processor/metrics/sql/android/p_state.sql b/src/trace_processor/metrics/sql/android/p_state.sql
index 79d2155..b334fcd 100644
--- a/src/trace_processor/metrics/sql/android/p_state.sql
+++ b/src/trace_processor/metrics/sql/android/p_state.sql
@@ -38,35 +38,32 @@
   p_state_cpu_idle_counter PARTITIONED cpu
 );
 
+CREATE PERFETTO FUNCTION p_state_over_interval(
+  start_ns LONG, end_ns LONG)
+RETURNS TABLE(cpu INT, freq_khz INT, idle_value INT, dur_ns INT)
+AS
+WITH sched_freq_idle_windowed AS (
+  SELECT
+    freq_khz,
+    idle_value,
+    cpu,
+    IIF(ts + dur <= $end_ns, ts + dur, $end_ns) - IIF(ts >= $start_ns, ts, $start_ns) AS dur
+  FROM
+    p_state_sched_freq_idle
+  WHERE
+    ts + dur > $start_ns
+    AND ts < $end_ns
+)
 SELECT
-  CREATE_VIEW_FUNCTION(
-    'P_STATE_OVER_INTERVAL(start_ns LONG, end_ns LONG)',
-    'cpu INT, freq_khz INT, idle_value INT, dur_ns INT',
-    '
-    WITH sched_freq_idle_windowed AS (
-      SELECT
-        freq_khz,
-        idle_value,
-        cpu,
-        IIF(ts + dur <= $end_ns, ts + dur, $end_ns) - IIF(ts >= $start_ns, ts, $start_ns) AS dur
-      FROM
-        p_state_sched_freq_idle
-      WHERE
-        ts + dur > $start_ns
-        AND ts < $end_ns
-    )
-    SELECT
-      cast(cpu AS int) AS cpu,
-      cast(freq_khz AS int) AS freq_khz,
-      cast(idle_value AS int) AS idle_value,
-      cast(sum(dur) AS int) AS dur_ns
-    FROM
-      sched_freq_idle_windowed
-    WHERE
-      freq_khz > 0
-    GROUP BY
-      cpu,
-      freq_khz,
-      idle_value
-  '
-  );
+  cast(cpu AS int) AS cpu,
+  cast(freq_khz AS int) AS freq_khz,
+  cast(idle_value AS int) AS idle_value,
+  cast(sum(dur) AS int) AS dur_ns
+FROM
+  sched_freq_idle_windowed
+WHERE
+  freq_khz > 0
+GROUP BY
+  cpu,
+  freq_khz,
+  idle_value;
diff --git a/src/trace_processor/metrics/sql/android/startup/slice_functions.sql b/src/trace_processor/metrics/sql/android/startup/slice_functions.sql
index e003302..7a12590 100644
--- a/src/trace_processor/metrics/sql/android/startup/slice_functions.sql
+++ b/src/trace_processor/metrics/sql/android/startup/slice_functions.sql
@@ -138,17 +138,14 @@
   ELSE $loc
 END || ": " || $status || "/" || $filter_str || "/" || $reason;
 
-SELECT CREATE_VIEW_FUNCTION(
-  'BINDER_TRANSACTION_REPLY_SLICES_FOR_LAUNCH(startup_id INT, threshold DOUBLE)',
-  'name STRING',
-  '
-    SELECT reply.name AS name
-    FROM ANDROID_BINDER_TRANSACTION_SLICES_FOR_STARTUP($startup_id, $threshold) request
-    JOIN following_flow(request.id) arrow
-    JOIN slice reply ON reply.id = arrow.slice_in
-    WHERE reply.dur > $threshold AND request.is_main_thread
-  '
-);
+CREATE PERFETTO FUNCTION binder_transaction_reply_slices_for_launch(
+  startup_id INT, threshold DOUBLE)
+RETURNS TABLE(name STRING) AS
+SELECT reply.name AS name
+FROM android_binder_transaction_slices_for_startup($startup_id, $threshold) request
+JOIN following_flow(request.id) arrow
+JOIN slice reply ON reply.id = arrow.slice_in
+WHERE reply.dur > $threshold AND request.is_main_thread;
 
 -- Given a launch id, return if unlock is running by systemui during the launch.
 CREATE PERFETTO FUNCTION is_unlock_running_during_launch(startup_id LONG)
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_long_tasks.sql b/src/trace_processor/metrics/sql/chrome/chrome_long_tasks.sql
index e80913e..e483965 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_long_tasks.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_long_tasks.sql
@@ -23,30 +23,38 @@
 -- names. For example, LongTaskTracker slices may have associated IPC
 -- metadata, or InterestingTask slices for input may have associated IPC to
 -- determine whether the task is fling/etc.
-SELECT CREATE_VIEW_FUNCTION(
-  'SELECT_LONG_TASK_SLICES(name STRING)',
-  'interface_name STRING, ipc_hash INT, message_type STRING, id INT, task_name STRING',
-  '
-    WITH slices_with_mojo_data AS (
-      SELECT
-          EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.mojo_interface_tag") AS interface_name,
-          EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.ipc_hash") AS ipc_hash,
-          CASE
-            WHEN EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.is_reply") THEN "reply"
-            ELSE "message"
-          END AS message_type,
-          id
-      FROM slice
-      WHERE
-        category GLOB "*scheduler.long_tasks*"
-        AND name = $name
-    )
-    SELECT
-      *,
-      printf("%s %s(hash=%s)", interface_name, message_type, ipc_hash) as task_name
-    FROM slices_with_mojo_data;
-  '
-);
+CREATE PERFETTO FUNCTION select_long_task_slices(name STRING)
+RETURNS TABLE(
+  interface_name STRING,
+  ipc_hash INT,
+  message_type STRING,
+  id INT,
+  task_name STRING)
+AS
+WITH slices_with_mojo_data AS (
+  SELECT
+      EXTRACT_ARG(
+        arg_set_id,
+        "chrome_mojo_event_info.mojo_interface_tag"
+      ) AS interface_name,
+      EXTRACT_ARG(
+        arg_set_id,
+        "chrome_mojo_event_info.ipc_hash"
+      ) AS ipc_hash,
+      CASE
+        WHEN EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.is_reply") THEN "reply"
+        ELSE "message"
+      END AS message_type,
+      id
+  FROM slice
+  WHERE
+    category GLOB "*scheduler.long_tasks*"
+    AND name = $name
+)
+SELECT
+  *,
+  printf("%s %s(hash=%s)", interface_name, message_type, ipc_hash) as task_name
+FROM slices_with_mojo_data;
 
 CREATE PERFETTO FUNCTION is_long_choreographer_task(dur LONG)
 RETURNS BOOL AS
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_tasks_delaying_input_processing_base.sql b/src/trace_processor/metrics/sql/chrome/chrome_tasks_delaying_input_processing_base.sql
index ba96dc3..3cf320c 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_tasks_delaying_input_processing_base.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_tasks_delaying_input_processing_base.sql
@@ -23,23 +23,20 @@
 -- browser interval. This may differ based on whether the scenario is for
 -- topLevel events or LongTask events.
 
-SELECT CREATE_VIEW_FUNCTION(
-  '{{function_prefix}}SELECT_SLOW_BROWSER_TASKS()',
-  'full_name STRING, dur INT, ts INT, id INT, upid INT, thread_dur INT',
-  'SELECT
-    task_table.full_name AS full_name,
-    task_table.dur AS dur,
-    task_table.ts AS ts,
-    task_table.id AS id,
-    task_table.upid AS upid,
-    thread_dur
-  FROM
-    {{task_table_name}} task_table
-  WHERE
-    task_table.dur >= {{duration_causing_jank_ms}} * 1e6
-    AND task_table.thread_name = "CrBrowserMain"
-  '
-);
+CREATE PERFETTO FUNCTION {{function_prefix}}SELECT_SLOW_BROWSER_TASKS()
+RETURNS TABLE(full_name STRING, dur INT, ts INT, id INT, upid INT, thread_dur INT) AS
+SELECT
+  task_table.full_name AS full_name,
+  task_table.dur AS dur,
+  task_table.ts AS ts,
+  task_table.id AS id,
+  task_table.upid AS upid,
+  thread_dur
+FROM
+  {{task_table_name}} task_table
+WHERE
+  task_table.dur >= {{duration_causing_jank_ms}} * 1e6
+  AND task_table.thread_name = "CrBrowserMain";
 
 -- Get the tasks that was running for more than 8ms within windows
 -- that we could have started processing input but did not on the
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql
index 521f2fc..2cfdce0 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql
@@ -211,15 +211,18 @@
 -- @column slice_dur      Duration of the slice.
 -- @column thread_name    Name of the thread with the slice.
 -- @column arg_set_id     Arg set id.
-SELECT CREATE_VIEW_FUNCTION(
-  'ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME(startup_id INT, slice_name STRING)',
-  'slice_name STRING, slice_ts INT, slice_dur INT, thread_name STRING, arg_set_id INT',
-  '
-    SELECT slice_name, slice_ts, slice_dur, thread_name, arg_set_id
-    FROM android_thread_slices_for_all_startups
-    WHERE startup_id = $startup_id AND slice_name GLOB $slice_name
-  '
-);
+CREATE PERFETTO FUNCTION android_slices_for_startup_and_slice_name(
+  startup_id INT, slice_name STRING)
+RETURNS TABLE(
+  slice_name STRING,
+  slice_ts INT,
+  slice_dur INT,
+  thread_name STRING,
+  arg_set_id INT
+) AS
+SELECT slice_name, slice_ts, slice_dur, thread_name, arg_set_id
+FROM android_thread_slices_for_all_startups
+WHERE startup_id = $startup_id AND slice_name GLOB $slice_name;
 
 -- Returns binder transaction slices for a given startup id with duration over threshold.
 --
@@ -231,18 +234,30 @@
 -- @column process        Name of the process with slice.
 -- @column arg_set_id     Arg set id.
 -- @column is_main_thread Whether is main thread.
-SELECT CREATE_VIEW_FUNCTION(
-  'ANDROID_BINDER_TRANSACTION_SLICES_FOR_STARTUP(startup_id INT, threshold DOUBLE)',
-  'id INT, slice_dur INT, thread_name STRING, process STRING, arg_set_id INT, is_main_thread BOOL',
-  '
-    SELECT slice_id as id, slice_dur, thread_name, process.name as process, s.arg_set_id, is_main_thread
-    FROM android_thread_slices_for_all_startups s
-    JOIN process ON (
-      EXTRACT_ARG(s.arg_set_id, "destination process") = process.pid
-    )
-    WHERE startup_id = $startup_id AND slice_name GLOB "binder transaction" AND slice_dur > $threshold
-  '
-);
+CREATE PERFETTO FUNCTION android_binder_transaction_slices_for_startup(
+  startup_id INT, threshold DOUBLE)
+RETURNS TABLE(
+  id INT,
+  slice_dur INT,
+  thread_name STRING,
+  process STRING,
+  arg_set_id INT,
+  is_main_thread BOOL
+) AS
+SELECT
+  slice_id as id,
+  slice_dur,
+  thread_name,
+  process.name as process,
+  s.arg_set_id,
+  is_main_thread
+FROM android_thread_slices_for_all_startups s
+JOIN process ON (
+  EXTRACT_ARG(s.arg_set_id, "destination process") = process.pid
+)
+WHERE startup_id = $startup_id
+  AND slice_name GLOB "binder transaction"
+  AND slice_dur > $threshold;
 
 -- Returns duration of startup for slice name.
 --
@@ -251,11 +266,13 @@
 -- @arg startup_id LONG   Startup id.
 -- @arg slice_name STRING Slice name.
 -- @ret INT               Sum of duration.
-CREATE PERFETTO FUNCTION android_sum_dur_for_startup_and_slice(startup_id LONG, slice_name STRING)
+CREATE PERFETTO FUNCTION android_sum_dur_for_startup_and_slice(
+  startup_id LONG, slice_name STRING)
 RETURNS INT AS
 SELECT SUM(slice_dur)
 FROM android_thread_slices_for_all_startups
-WHERE startup_id = $startup_id AND slice_name GLOB $slice_name;
+WHERE startup_id = $startup_id
+  AND slice_name GLOB $slice_name;
 
 -- Returns duration of startup for slice name on main thread.
 --
@@ -264,8 +281,11 @@
 -- @arg startup_id LONG   Startup id.
 -- @arg slice_name STRING Slice name.
 -- @ret INT               Sum of duration.
-CREATE PERFETTO FUNCTION android_sum_dur_on_main_thread_for_startup_and_slice(startup_id LONG, slice_name STRING)
+CREATE PERFETTO FUNCTION android_sum_dur_on_main_thread_for_startup_and_slice(
+  startup_id LONG, slice_name STRING)
 RETURNS INT AS
 SELECT SUM(slice_dur)
 FROM android_thread_slices_for_all_startups
-WHERE startup_id = $startup_id AND slice_name GLOB $slice_name AND is_main_thread;
+WHERE startup_id = $startup_id
+  AND slice_name GLOB $slice_name
+  AND is_main_thread;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/thread.sql b/src/trace_processor/perfetto_sql/stdlib/android/thread.sql
index 3d6fa46..592b476 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/thread.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/thread.sql
@@ -28,34 +28,36 @@
 -- @column pid                     Process pid creating threads.
 -- @column thread_name_prefix      String prefix of thread names created.
 -- @column max_count_per_sec       Max number of threads created within a time window.
-SELECT CREATE_VIEW_FUNCTION(
-    'ANDROID_THREAD_CREATION_SPAM(min_thread_dur FLOAT, sliding_window_dur FLOAT)',
-    'process_name STRING, pid INT, thread_name_prefix STRING, max_count_per_sec INT',
-    '
-    WITH
-      x AS (
-        SELECT
-          pid,
-          upid,
-          INTERNAL_THREAD_PREFIX(thread.name) AS thread_name_prefix,
-          process.name AS process_name,
-          COUNT(thread.start_ts)
-            OVER (
-              PARTITION BY upid, thread.name
-              ORDER BY thread.start_ts
-              RANGE BETWEEN CURRENT ROW AND CAST($sliding_window_dur AS INT64) FOLLOWING
-            ) AS count
-        FROM thread
-        JOIN process
-          USING (upid)
-        WHERE
-          ($min_thread_dur AND (thread.end_ts - thread.start_ts) <= $min_thread_dur)
-          OR $min_thread_dur IS NULL
-      )
-    SELECT process_name, pid, thread_name_prefix, MAX(count) AS max_count_per_sec
-    FROM x
-    GROUP BY upid, thread_name_prefix
-    HAVING max_count_per_sec > 0
-    ORDER BY count DESC;
-    '
-);
+CREATE PERFETTO FUNCTION android_thread_creation_spam(
+  min_thread_dur FLOAT, sliding_window_dur FLOAT)
+RETURNS TABLE(
+  process_name STRING,
+  pid INT,
+  thread_name_prefix STRING,
+  max_count_per_sec INT
+) AS
+WITH
+x AS (
+  SELECT
+    pid,
+    upid,
+    INTERNAL_THREAD_PREFIX(thread.name) AS thread_name_prefix,
+    process.name AS process_name,
+    COUNT(thread.start_ts)
+      OVER (
+        PARTITION BY upid, thread.name
+        ORDER BY thread.start_ts
+        RANGE BETWEEN CURRENT ROW AND CAST($sliding_window_dur AS INT64) FOLLOWING
+      ) AS count
+  FROM thread
+  JOIN process
+    USING (upid)
+  WHERE
+    ($min_thread_dur AND (thread.end_ts - thread.start_ts) <= $min_thread_dur)
+    OR $min_thread_dur IS NULL
+)
+SELECT process_name, pid, thread_name_prefix, MAX(count) AS max_count_per_sec
+FROM x
+GROUP BY upid, thread_name_prefix
+HAVING max_count_per_sec > 0
+ORDER BY count DESC;
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
index 167bca2..44e6e1e 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
@@ -95,20 +95,22 @@
 -- @column ipc_hash            Hash of the IPC call.
 -- @column message_type        Message type (e.g. reply).
 -- @column id                  The slice ID.
-SELECT CREATE_VIEW_FUNCTION(
-  'CHROME_SELECT_LONG_TASK_SLICES(name STRING)',
-  'interface_name STRING, ipc_hash INT, message_type STRING, id INT',
-  'SELECT
-      EXTRACT_ARG(s.arg_set_id, "chrome_mojo_event_info.mojo_interface_tag") AS interface_name,
-      EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.ipc_hash") AS ipc_hash,
-      CASE
-        WHEN EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.is_reply") THEN "reply"
-        ELSE "message"
-      END AS message_type,
-      s.id
-    FROM slice s
-    WHERE
-      category GLOB "*scheduler.long_tasks*"
-      AND name = $name
-  '
-);
+CREATE PERFETTO FUNCTION chrome_select_long_task_slices(name STRING)
+RETURNS TABLE(
+  interface_name STRING,
+  ipc_hash INT,
+  message_type STRING,
+  id INT
+) AS
+SELECT
+  EXTRACT_ARG(s.arg_set_id, "chrome_mojo_event_info.mojo_interface_tag") AS interface_name,
+  EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.ipc_hash") AS ipc_hash,
+  CASE
+    WHEN EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.is_reply") THEN "reply"
+    ELSE "message"
+  END AS message_type,
+  s.id
+FROM slice s
+WHERE
+  category GLOB "*scheduler.long_tasks*"
+  AND name = $name;
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql
index cd45beb..6890ce7 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql
@@ -43,7 +43,8 @@
 LIMIT 1;
 
 -- Human-readable aliases for a few key navigation tasks.
-CREATE PERFETTO FUNCTION internal_human_readable_navigation_task_name(task_name STRING)
+CREATE PERFETTO FUNCTION internal_human_readable_navigation_task_name(
+  task_name STRING)
 RETURNS STRING AS
 SELECT
   CASE
@@ -310,22 +311,20 @@
 -- @column kind          The type of Java slice.
 -- @column ts            The timestamp of the slice.
 -- @column name          The name of the slice.
-SELECT CREATE_VIEW_FUNCTION(
-  'INTERNAL_SELECT_BEGIN_MAIN_FRAME_JAVA_SLICES(name STRING)',
-  'id INT, kind STRING, ts LONG, dur LONG, name STRING',
-  'SELECT
-      id,
-      "SingleThreadProxy::BeginMainFrame" AS kind,
-      ts,
-      dur,
-      name
-    FROM slice
-    WHERE
-      (name = $name
-        AND internal_get_posted_from(arg_set_id) =
-            "cc/trees/single_thread_proxy.cc:ScheduledActionSendBeginMainFrame")
-  '
-);
+CREATE PERFETTO FUNCTION internal_select_begin_main_frame_java_slices(
+  name STRING)
+RETURNS TABLE(id INT, kind STRING, ts LONG, dur LONG, name STRING) AS
+SELECT
+  id,
+  "SingleThreadProxy::BeginMainFrame" AS kind,
+  ts,
+  dur,
+  name
+FROM slice
+WHERE
+  (name = $name
+    AND internal_get_posted_from(arg_set_id) =
+        "cc/trees/single_thread_proxy.cc:ScheduledActionSendBeginMainFrame");
 
 -- A list of Chrome tasks which were performing operations with Java views,
 -- together with the names of the these views.
@@ -436,17 +435,15 @@
 ORDER by depth, ts
 LIMIT 1;
 
-SELECT CREATE_VIEW_FUNCTION('INTERNAL_DESCENDANT_MOJO_SLICE(slice_id INT)',
-  'task_name STRING',
-  '
-  SELECT
-    printf("%s %s (hash=%d)",
-      mojo.interface_name, mojo.message_type, mojo.ipc_hash) AS task_name
-  FROM slice task
-  JOIN internal_chrome_mojo_slices mojo
-    ON mojo.id = internal_get_descendant_mojo_slice_candidate($slice_id)
-  WHERE task.id = $slice_id
-  ');
+CREATE PERFETTO FUNCTION internal_descendant_mojo_slice(slice_id INT)
+RETURNS TABLE(task_name STRING) AS
+SELECT
+  printf("%s %s (hash=%d)",
+    mojo.interface_name, mojo.message_type, mojo.ipc_hash) AS task_name
+FROM slice task
+JOIN internal_chrome_mojo_slices mojo
+  ON mojo.id = internal_get_descendant_mojo_slice_candidate($slice_id)
+WHERE task.id = $slice_id;
 
 -- A list of "Chrome tasks": top-level execution units (e.g. scheduler tasks /
 -- IPCs / system callbacks) run by Chrome. For a given thread, the tasks
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/args.sql b/src/trace_processor/perfetto_sql/stdlib/common/args.sql
index 7361e93..150fae4 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/args.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/args.sql
@@ -12,20 +12,6 @@
 -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
---
--- Copyright 2023 The Android Open Source Project
---
--- Licensed under the Apache License, Version 2.0 (the "License");
--- you may not use this file except in compliance with the License.
--- You may obtain a copy of the License at
---
---     https://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
 
 -- Returns the formatted value of a given argument.
 -- Similar to EXTRACT_ARG, but instead of returning the raw value, it returns
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/counters.sql b/src/trace_processor/perfetto_sql/stdlib/common/counters.sql
index 91e023d..a7f3157 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/counters.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/counters.sql
@@ -34,9 +34,8 @@
 -- @column track_name          Name of the counter track.
 -- @column track_arg_set_id    Counter track set id.
 -- @column arg_set_id          Counter arg set id.
-SELECT CREATE_VIEW_FUNCTION(
-  'COUNTER_WITH_DUR_FOR_TRACK(counter_track_id INT)',
-  '
+CREATE PERFETTO FUNCTION counter_with_dur_for_track(counter_track_id INT)
+RETURNS TABLE(
     ts LONG,
     dur LONG,
     value DOUBLE,
@@ -44,21 +43,18 @@
     track_name STRING,
     track_arg_set_id INT,
     arg_set_id INT
-  ',
-  '
-    SELECT
-        ts,
-        LEAD(ts, 1, trace_end()) OVER(ORDER BY ts) - ts AS dur,
-        value,
-        track.id AS track_id,
-        track.name AS track_name,
-        track.source_arg_set_id AS track_arg_set_id,
-        counter.arg_set_id AS arg_set_id
-    FROM counter
-    JOIN counter_track track ON track.id = counter.track_id
-    WHERE track.id = $counter_track_id
-  '
-);
+) AS
+SELECT
+  ts,
+  LEAD(ts, 1, trace_end()) OVER(ORDER BY ts) - ts AS dur,
+  value,
+  track.id AS track_id,
+  track.name AS track_name,
+  track.source_arg_set_id AS track_arg_set_id,
+  counter.arg_set_id AS arg_set_id
+FROM counter
+JOIN counter_track track ON track.id = counter.track_id
+WHERE track.id = $counter_track_id;
 
 -- COUNTER_WITH_DUR_FOR_TRACK but in a specified time.
 -- Does calculation over the table ends - creates an artificial counter value at
@@ -75,33 +71,30 @@
 -- @column track_name          Name of the counter track.
 -- @column track_arg_set_id    Counter track set id.
 -- @column arg_set_id          Counter arg set id.
-SELECT CREATE_VIEW_FUNCTION(
-  'COUNTER_FOR_TIME_RANGE(counter_track_id INT, start_ts LONG, end_ts LONG)',
-  '
-    ts LONG,
-    dur LONG,
-    value DOUBLE,
-    track_id INT,
-    track_name STRING,
-    track_arg_set_id INT,
-    arg_set_id INT
-  ',
-  '
-  SELECT
-    IIF(ts < $start_ts, $start_ts, ts) AS ts,
-    IIF(
-      ts < $start_ts,
-      dur - ($start_ts - ts),
-      IIF(ts + dur > $end_ts, $end_ts - ts, dur)) AS dur,
-    value,
-    track_id,
-    track_name,
-    track_arg_set_id,
-    arg_set_id
-  FROM COUNTER_WITH_DUR_FOR_TRACK($counter_track_id)
-  WHERE TRUE
-    AND ts + dur >= $start_ts
-    AND ts < $end_ts
-  ORDER BY ts ASC;
-'
-);
\ No newline at end of file
+CREATE PERFETTO FUNCTION COUNTER_FOR_TIME_RANGE(
+  counter_track_id INT, start_ts LONG, end_ts LONG)
+RETURNS TABLE(
+  ts LONG,
+  dur LONG,
+  value DOUBLE,
+  track_id INT,
+  track_name STRING,
+  track_arg_set_id INT,
+  arg_set_id INT
+) AS
+SELECT
+  IIF(ts < $start_ts, $start_ts, ts) AS ts,
+  IIF(
+    ts < $start_ts,
+    dur - ($start_ts - ts),
+    IIF(ts + dur > $end_ts, $end_ts - ts, dur)) AS dur,
+  value,
+  track_id,
+  track_name,
+  track_arg_set_id,
+  arg_set_id
+FROM counter_with_dur_for_track($counter_track_id)
+WHERE TRUE
+  AND ts + dur >= $start_ts
+  AND ts < $end_ts
+ORDER BY ts ASC;
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql b/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql
index bbba1eb..8b3da9d 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql
@@ -16,15 +16,13 @@
 INCLUDE PERFETTO MODULE common.counters;
 INCLUDE PERFETTO MODULE common.timestamps;
 
-SELECT CREATE_VIEW_FUNCTION(
-    'INTERNAL_NUMBER_GENERATOR(to INT)',
-    'num INT',
-    'WITH NUMS AS
-        (SELECT 1 num UNION SELECT num + 1
-        from NUMS
-        WHERE num < $to)
-    SELECT num FROM NUMS;'
-);
+CREATE PERFETTO FUNCTION internal_number_generator(to INT)
+RETURNS TABLE(num INT) AS
+WITH nums AS
+    (SELECT 1 num UNION SELECT num + 1
+    from NUMS
+    WHERE num < $to)
+SELECT num FROM nums;
 
 --
 -- Get durations for percentile
@@ -46,44 +44,43 @@
 -- @arg end_ts LONG          Timestamp of end of time range.
 -- @column percentile        All of the numbers from 1 to 100.
 -- @column value             Value for the percentile.
-SELECT CREATE_VIEW_FUNCTION(
-    'COUNTER_PERCENTILES_FOR_TIME_RANGE(counter_track_id INT, start_ts LONG, end_ts LONG)',
-    'percentile INT, value DOUBLE',
-    'WITH percentiles_for_value AS (
-        SELECT
-            value,
-            (CAST(SUM(dur) OVER(ORDER BY value ASC) AS DOUBLE) /
-                ($end_ts - MAX($start_ts, earliest_timestamp_for_counter_track($counter_track_id)))) * 100
-            AS percentile_for_value
-        FROM COUNTER_FOR_TIME_RANGE($counter_track_id, $start_ts, $end_ts)
-        ORDER BY value ASC
-    ),
-    with_gaps AS (
-        SELECT
-            CAST(percentile_for_value AS INT) AS percentile,
-            MIN(value) AS value
-        FROM percentiles_for_value
-        GROUP BY percentile
-        ORDER BY percentile ASC)
+CREATE PERFETTO FUNCTION counter_percentiles_for_time_range(
+  counter_track_id INT, start_ts LONG, end_ts LONG)
+RETURNS TABLE(percentile INT, value DOUBLE) AS
+WITH percentiles_for_value AS (
     SELECT
-        num AS percentile,
-        IFNULL(value, MIN(value) OVER (ORDER BY percentile DESC)) AS value
-    FROM INTERNAL_NUMBER_GENERATOR(100) AS nums
-    LEFT JOIN with_gaps ON with_gaps.percentile = nums.num
-    ORDER BY percentile DESC
-    '
-);
+        value,
+        (CAST(SUM(dur) OVER(ORDER BY value ASC) AS DOUBLE) /
+            ($end_ts - MAX($start_ts, earliest_timestamp_for_counter_track($counter_track_id)))) * 100
+        AS percentile_for_value
+    FROM COUNTER_FOR_TIME_RANGE($counter_track_id, $start_ts, $end_ts)
+    ORDER BY value ASC
+),
+with_gaps AS (
+    SELECT
+        CAST(percentile_for_value AS INT) AS percentile,
+        MIN(value) AS value
+    FROM percentiles_for_value
+    GROUP BY percentile
+    ORDER BY percentile ASC)
+SELECT
+    num AS percentile,
+    IFNULL(value, MIN(value) OVER (ORDER BY percentile DESC)) AS value
+FROM INTERNAL_NUMBER_GENERATOR(100) AS nums
+LEFT JOIN with_gaps ON with_gaps.percentile = nums.num
+ORDER BY percentile DESC;
 
 -- All percentiles (range 1-100) for counter track ID.
 --
 -- @arg counter_track_id INT Id of the counter track.
 -- @column percentile        All of the numbers from 1 to 100.
 -- @column value             Value for the percentile.
-SELECT CREATE_VIEW_FUNCTION(
-    'COUNTER_PERCENTILES_FOR_TRACK(counter_track_id INT)',
-    'percentile INT, value DOUBLE',
-    'SELECT * FROM COUNTER_PERCENTILES_FOR_TIME_RANGE($counter_track_id, trace_start(), trace_end());'
-);
+CREATE PERFETTO FUNCTION counter_percentiles_for_track(
+  counter_track_id INT)
+RETURNS TABLE(percentile INT, value DOUBLE) AS
+SELECT *
+FROM counter_percentiles_for_time_range(
+  $counter_track_id, trace_start(), trace_end());
 
 -- Value for specific percentile (range 1-100) for counter track ID in time range.
 --
@@ -98,7 +95,7 @@
                                                           end_ts LONG)
 RETURNS DOUBLE AS
 SELECT value
-FROM COUNTER_PERCENTILES_FOR_TIME_RANGE($counter_track_id, $start_ts, $end_ts)
+FROM counter_percentiles_for_time_range($counter_track_id, $start_ts, $end_ts)
 WHERE percentile = $percentile;
 
 -- Value for specific percentile (range 1-100) for counter track ID.
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql b/src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql
index 2aa0d47..df95849 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql
@@ -66,3 +66,56 @@
     WHEN ($ts1 > $ts2) AND ($ts1 + $dur1 > $ts2 + $dur2) THEN $ts2 + $dur2 - $ts1
     ELSE $dur2
   END;
+
+--
+-- Helpers for defining time durations.
+--
+
+-- Converts a duration in seconds to nanoseconds, which is the default representation
+-- of time durations in trace processor. Provided for consisensy with other functions.
+-- @arg nanos INT  Time duration in seconds.
+-- @ret INT        Time duration in nanoseconds.
+CREATE PERFETTO FUNCTION ns(nanos INT) RETURNS INT AS
+SELECT $nanos;
+
+-- Converts a duration in microseconds to nanoseconds, which is the default
+-- representation of time durations in trace processor.
+-- @arg micros INT  Time duration in microseconds.
+-- @ret INT         Time duration in nanoseconds.
+CREATE PERFETTO FUNCTION us(micros INT) RETURNS INT AS
+SELECT $micros * 1000;
+
+-- Converts a duration in millseconds to nanoseconds, which is the default
+-- representation of time durations in trace processor.
+-- @arg millis INT  Time duration in milliseconds.
+-- @ret INT         Time duration in nanoseconds.
+CREATE PERFETTO FUNCTION ms(millis INT) RETURNS INT AS
+SELECT $millis * 1000 * 1000;
+
+-- Converts a duration in seconds to nanoseconds, which is the default
+-- representation of time durations in trace processor.
+-- @arg seconds INT  Time duration in seconds.
+-- @ret INT          Time duration in nanoseconds.
+CREATE PERFETTO FUNCTION seconds(seconds INT) RETURNS INT AS
+SELECT $seconds * 1000 * 1000 * 1000;
+
+-- Converts a duration in minutes to nanoseconds, which is the default
+-- representation of time durations in trace processor.
+-- @arg minutes INT  Time duration in minutes.
+-- @ret INT          Time duration in nanoseconds.
+CREATE PERFETTO FUNCTION minutes(minutes INT) RETURNS INT AS
+SELECT $minutes * 60 * 1000 * 1000 * 1000;
+
+-- Converts a duration in hours to nanoseconds, which is the default
+-- representation of time durations in trace processor.
+-- @arg hours INT  Time duration in hours.
+-- @ret INT        Time duration in nanoseconds.
+CREATE PERFETTO FUNCTION hours(hours INT) RETURNS INT AS
+SELECT $hours * 60 * 60 * 1000 * 1000 * 1000;
+
+-- Converts a duration in days to nanoseconds, which is the default
+-- representation of time durations in trace processor.
+-- @arg days INT  Time duration in days.
+-- @ret INT       Time duration in nanoseconds.
+CREATE PERFETTO FUNCTION days(days INT) RETURNS INT AS
+SELECT $days * 24 * 60 * 60 * 1000 * 1000 * 1000;
diff --git a/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql b/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
index 5b7bf03..f839818 100644
--- a/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
@@ -347,9 +347,8 @@
 -- @column is_leaf            Whether this span is the leaf in the slice tree.
 -- @column depth              Tree depth from |root_id|
 -- @column root_id            Thread state id used to start the recursion. Helpful for SQL JOINs
-SELECT CREATE_VIEW_FUNCTION(
-'EXPERIMENTAL_THREAD_EXECUTING_SPAN_DESCENDANTS(root_id INT)',
-'
+CREATE PERFETTO FUNCTION experimental_thread_executing_span_descendants(root_id INT)
+RETURNS TABLE(
   parent_id LONG,
   id LONG,
   ts LONG,
@@ -373,8 +372,7 @@
   is_leaf INT,
   depth INT,
   root_id INT
-',
-'
+) AS
 WITH chain AS (
   SELECT
     *,
@@ -390,8 +388,7 @@
   FROM experimental_thread_executing_span_graph graph
   JOIN chain ON chain.id = graph.parent_id
 )
-SELECT * FROM chain
-');
+SELECT * FROM chain;
 
 -- All thread_executing_spans that are ancestors of |leaf_id|.
 --
@@ -426,9 +423,8 @@
 -- @column leaf_blocked_dur        Thread state duration blocked of the |leaf_id|.
 -- @column leaf_blocked_state      Thread state of the |leaf_id|.
 -- @column leaf_blocked_function   Thread state blocked_function of the |leaf_id|.
-SELECT CREATE_VIEW_FUNCTION(
-'EXPERIMENTAL_THREAD_EXECUTING_SPAN_ANCESTORS(leaf_id INT, leaf_utid INT)',
-'
+CREATE PERFETTO FUNCTION experimental_thread_executing_span_ancestors(leaf_id INT, leaf_utid INT)
+RETURNS TABLE(
   parent_id LONG,
   id LONG,
   ts LONG,
@@ -457,8 +453,7 @@
   leaf_blocked_dur LONG,
   leaf_blocked_state STRING,
   leaf_blocked_function STRING
-',
-'
+) AS
 WITH
 chain AS (
   SELECT
@@ -486,8 +481,7 @@
   FROM experimental_thread_executing_span_graph graph
   JOIN chain ON chain.parent_id = graph.id AND chain.ts >= (leaf_ts - leaf_blocked_dur)
 )
-SELECT * FROM chain
-');
+SELECT * FROM chain;
 
 -- Gets the thread_executing_span id a thread_state belongs to. Returns NULL if thread state is
 -- sleeping and not blocked on an interrupt.
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 2f7b62f..b960520 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -89,6 +89,7 @@
 from diff_tests.tables.tests import Tables
 from diff_tests.tables.tests_counters import TablesCounters
 from diff_tests.tables.tests_sched import TablesSched
+from diff_tests.time.tests import Time
 from diff_tests.track_event.tests import TrackEvent
 from diff_tests.translation.tests import Translation
 from diff_tests.ufs.tests import Ufs
@@ -178,6 +179,7 @@
       *Tables(index_path, 'tables', 'Tables').fetch(),
       *TablesCounters(index_path, 'tables', 'TablesCounters').fetch(),
       *TablesSched(index_path, 'tables', 'TablesSched').fetch(),
+      *Time(index_path, 'time', 'Time').fetch(),
       *TrackEvent(index_path, 'track_event', 'TrackEvent').fetch(),
       *Translation(index_path, 'translation', 'Translation').fetch(),
       *WebView(index_path, 'webview', 'WebView').fetch(),
diff --git a/test/trace_processor/diff_tests/time/tests.py b/test/trace_processor/diff_tests/time/tests.py
new file mode 100644
index 0000000..5731169
--- /dev/null
+++ b/test/trace_processor/diff_tests/time/tests.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License a
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric
+from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+from google.protobuf import text_format
+
+
+class Time(TestSuite):
+
+  def test_ns(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT IMPORT('common.timestamps');
+        SELECT ns(4) as result;
+      """,
+        out=Csv("""
+        "result"
+        4
+      """))
+
+  def test_us(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT IMPORT('common.timestamps');
+        SELECT us(4) as result;
+      """,
+        out=Csv("""
+        "result"
+        4000
+      """))
+
+  def test_ms(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT IMPORT('common.timestamps');
+        SELECT ms(4) as result;
+      """,
+        out=Csv("""
+        "result"
+        4000000
+      """))
+
+  def test_seconds(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT IMPORT('common.timestamps');
+        SELECT seconds(4) as result;
+      """,
+        out=Csv("""
+        "result"
+        4000000000
+      """))
+
+  def test_minutes(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT IMPORT('common.timestamps');
+        SELECT minutes(1) as result;
+      """,
+        out=Csv("""
+        "result"
+        60000000000
+      """))
+
+  def test_hours(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT IMPORT('common.timestamps');
+        SELECT hours(1) as result;
+      """,
+        out=Csv("""
+        "result"
+        3600000000000
+      """))
+
+  def test_days(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT IMPORT('common.timestamps');
+        SELECT days(1) as result;
+      """,
+        out=Csv("""
+        "result"
+        86400000000000
+      """))
diff --git a/ui/src/base/dom_utils.ts b/ui/src/base/dom_utils.ts
index e2c8e73..d7c7582 100644
--- a/ui/src/base/dom_utils.ts
+++ b/ui/src/base/dom_utils.ts
@@ -62,3 +62,23 @@
 
   return true;
 }
+
+// Returns the mouse pointer's position relative to |e.currentTarget| for a
+// given |MouseEvent|.
+// Similar to |offsetX|, |offsetY| but for |currentTarget| rather than |target|.
+// If the event has no currentTarget or it is not an element, offsetX & offsetY
+// are returned instead.
+export function currentTargetOffset(e: MouseEvent): {x: number, y: number} {
+  if (e.currentTarget === e.target) {
+    return {x: e.offsetX, y: e.offsetY};
+  }
+
+  if (e.currentTarget && e.currentTarget instanceof Element) {
+    const rect = e.currentTarget.getBoundingClientRect();
+    const offsetX = e.clientX - rect.left;
+    const offsetY = e.clientY - rect.top;
+    return {x: offsetX, y: offsetY};
+  }
+
+  return {x: e.offsetX, y: e.offsetY};
+}
diff --git a/ui/src/frontend/events.ts b/ui/src/frontend/events.ts
deleted file mode 100644
index 88e044b..0000000
--- a/ui/src/frontend/events.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use size file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// layerX and layerY aren't standardized but there is no drop-in replacement
-// (offsetX/offsetY have slightly different semantics) and they are still
-// present in the browsers we care about, so for now we create an extended
-// version of MouseEvent we can use instead.
-// See also:
-// https://github.com/microsoft/TypeScript/issues/35634#issuecomment-564765179
-export interface PerfettoMouseEvent extends MouseEvent {
-  layerX: number;
-  layerY: number;
-}
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index ea32ac1..1904f32 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -14,6 +14,7 @@
 
 import m from 'mithril';
 
+import {currentTargetOffset} from '../base/dom_utils';
 import {Icons} from '../base/semantic_icons';
 import {Time} from '../base/time';
 import {Actions} from '../common/actions';
@@ -28,7 +29,6 @@
   NewBottomTabArgs,
 } from './bottom_tab';
 import {TRACK_SHELL_WIDTH} from './css_constants';
-import {PerfettoMouseEvent} from './events';
 import {globals} from './globals';
 import {
   getMaxMajorTicks,
@@ -60,21 +60,6 @@
 export class NotesPanel extends Panel {
   hoveredX: null|number = null;
 
-  oncreate({dom}: m.CVnodeDOM) {
-    dom.addEventListener('mousemove', (e: Event) => {
-      this.hoveredX = (e as PerfettoMouseEvent).layerX - TRACK_SHELL_WIDTH;
-      raf.scheduleRedraw();
-    }, {passive: true});
-    dom.addEventListener('mouseenter', (e: Event) => {
-      this.hoveredX = (e as PerfettoMouseEvent).layerX - TRACK_SHELL_WIDTH;
-      raf.scheduleRedraw();
-    });
-    dom.addEventListener('mouseout', () => {
-      this.hoveredX = null;
-      globals.dispatch(Actions.setHoveredNoteTimestamp({ts: Time.INVALID}));
-    }, {passive: true});
-  }
-
   view() {
     const allCollapsed = Object.values(globals.state.trackGroups)
                              .every((group) => group.collapsed);
@@ -82,10 +67,24 @@
     return m(
         '.notes-panel',
         {
-          onclick: (e: PerfettoMouseEvent) => {
-            this.onClick(e.layerX - TRACK_SHELL_WIDTH, e.layerY);
+          onclick: (e: MouseEvent) => {
+            const {x, y} = currentTargetOffset(e);
+            this.onClick(x - TRACK_SHELL_WIDTH, y);
             e.stopPropagation();
           },
+          onmousemove: (e: MouseEvent) => {
+            this.hoveredX = currentTargetOffset(e).x - TRACK_SHELL_WIDTH;
+            raf.scheduleRedraw();
+          },
+          mouseenter: (e: MouseEvent) => {
+            this.hoveredX = currentTargetOffset(e).x - TRACK_SHELL_WIDTH;
+            raf.scheduleRedraw();
+          },
+          onmouseout: () => {
+            this.hoveredX = null;
+            globals.dispatch(
+                Actions.setHoveredNoteTimestamp({ts: Time.INVALID}));
+          },
         },
         isTraceLoaded() ?
             [
diff --git a/ui/src/frontend/pan_and_zoom_handler.ts b/ui/src/frontend/pan_and_zoom_handler.ts
index a70baa9..5d2c37a 100644
--- a/ui/src/frontend/pan_and_zoom_handler.ts
+++ b/ui/src/frontend/pan_and_zoom_handler.ts
@@ -13,12 +13,11 @@
 // limitations under the License.
 
 import {Disposable, Trash} from '../base/disposable';
-import {elementIsEditable} from '../base/dom_utils';
+import {currentTargetOffset, elementIsEditable} from '../base/dom_utils';
 import {raf} from '../core/raf_scheduler';
 
 import {Animation} from './animation';
 import {DragGestureHandler} from './drag_gesture_handler';
-import {globals} from './globals';
 import {handleKey} from './keyboard_event_handler';
 
 // When first starting to pan or zoom, move at least this many units.
@@ -110,7 +109,6 @@
   private zoomAnimation = new Animation(this.onZoomAnimationStep.bind(this));
 
   private element: HTMLElement;
-  private contentOffsetX: number;
   private onPanned: (movedPx: number) => void;
   private onZoomed: (zoomPositionPx: number, zoomRatio: number) => void;
   private editSelection: (currentPx: number) => boolean;
@@ -122,7 +120,6 @@
 
   constructor({
     element,
-    contentOffsetX,
     onPanned,
     onZoomed,
     editSelection,
@@ -130,7 +127,6 @@
     endSelection,
   }: {
     element: HTMLElement,
-    contentOffsetX: number,
     onPanned: (movedPx: number) => void,
     onZoomed: (zoomPositionPx: number, zoomRatio: number) => void,
     editSelection: (currentPx: number) => boolean,
@@ -140,7 +136,6 @@
     endSelection: (edit: boolean) => void,
   }) {
     this.element = element;
-    this.contentOffsetX = contentOffsetX;
     this.onPanned = onPanned;
     this.onZoomed = onZoomed;
     this.editSelection = editSelection;
@@ -236,11 +231,8 @@
   }
 
   private onMouseMove(e: MouseEvent) {
-    const pageOffset = globals.state.sidebarVisible && !globals.hideSidebar ?
-        this.contentOffsetX :
-        0;
-    // We can't use layerX here because there are many layers in this element.
-    this.mousePositionX = e.clientX - pageOffset;
+    this.mousePositionX = currentTargetOffset(e).x;
+
     // Only change the cursor when hovering, the DragGestureHandler handles
     // changing the cursor during drag events. This avoids the problem of
     // the cursor flickering between styles if you drag fast and get too
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index b9ff4a1..6fc7615 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -867,8 +867,14 @@
           class: globals.state.sidebarVisible ? 'show-sidebar' : 'hide-sidebar',
           // 150 here matches --sidebar-timing in the css.
           // TODO(hjd): Should link to the CSS variable.
-          ontransitionstart: () => this._redrawWhileAnimating.start(150),
-          ontransitionend: () => this._redrawWhileAnimating.stop(),
+          ontransitionstart: (e: TransitionEvent) => {
+            if (e.target !== e.currentTarget) return;
+            this._redrawWhileAnimating.start(150);
+          },
+          ontransitionend: (e: TransitionEvent) => {
+            if (e.target !== e.currentTarget) return;
+            this._redrawWhileAnimating.stop();
+          },
         },
         shouldShowHiringBanner() ? m(HiringBanner) : null,
         m(
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 4d59dfa..1f333f6 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -15,6 +15,7 @@
 import {hex} from 'color-convert';
 import m from 'mithril';
 
+import {currentTargetOffset} from '../base/dom_utils';
 import {Icons} from '../base/semantic_icons';
 import {duration, Span, time} from '../base/time';
 import {Actions} from '../common/actions';
@@ -25,7 +26,6 @@
 import {TrackLike} from '../public';
 
 import {SELECTION_FILL_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
-import {PerfettoMouseEvent} from './events';
 import {globals} from './globals';
 import {drawGridLines} from './gridline_helper';
 import {Panel, PanelSize} from './panel';
@@ -153,7 +153,7 @@
           globals.state.currentSelection !== null &&
                   globals.state.currentSelection.kind === 'AREA' ?
               m(TrackButton, {
-                action: (e: PerfettoMouseEvent) => {
+                action: (e: MouseEvent) => {
                   globals.dispatch(Actions.toggleTrackSelection(
                       {id: attrs.trackState.id, isTrackGroup: false}));
                   e.stopPropagation();
@@ -231,32 +231,33 @@
     return m(
         '.track-content',
         {
-          onmousemove: (e: PerfettoMouseEvent) => {
-            attrs.track.onMouseMove(
-                {x: e.layerX - TRACK_SHELL_WIDTH, y: e.layerY});
+          onmousemove: (e: MouseEvent) => {
+            attrs.track.onMouseMove(currentTargetOffset(e));
             raf.scheduleRedraw();
           },
           onmouseout: () => {
             attrs.track.onMouseOut();
             raf.scheduleRedraw();
           },
-          onmousedown: (e: PerfettoMouseEvent) => {
-            this.mouseDownX = e.layerX;
-            this.mouseDownY = e.layerY;
+          onmousedown: (e: MouseEvent) => {
+            const {x, y} = currentTargetOffset(e);
+            this.mouseDownX = x;
+            this.mouseDownY = y;
           },
-          onmouseup: (e: PerfettoMouseEvent) => {
+          onmouseup: (e: MouseEvent) => {
             if (this.mouseDownX === undefined ||
                 this.mouseDownY === undefined) {
               return;
             }
-            if (Math.abs(e.layerX - this.mouseDownX) > 1 ||
-                Math.abs(e.layerY - this.mouseDownY) > 1) {
+            const {x, y} = currentTargetOffset(e);
+            if (Math.abs(x - this.mouseDownX) > 1 ||
+                Math.abs(y - this.mouseDownY) > 1) {
               this.selectionOccurred = true;
             }
             this.mouseDownX = undefined;
             this.mouseDownY = undefined;
           },
-          onclick: (e: PerfettoMouseEvent) => {
+          onclick: (e: MouseEvent) => {
             // This click event occurs after any selection mouse up/drag events
             // so we have to look if the mouse moved during this click to know
             // if a selection occurred.
@@ -265,8 +266,7 @@
               return;
             }
             // Returns true if something was selected, so stop propagation.
-            if (attrs.track.onMouseClick(
-                    {x: e.layerX - TRACK_SHELL_WIDTH, y: e.layerY})) {
+            if (attrs.track.onMouseClick(currentTargetOffset(e))) {
               e.stopPropagation();
             }
             raf.scheduleRedraw();
@@ -308,7 +308,7 @@
 }
 
 export interface TrackButtonAttrs {
-  action: (e: PerfettoMouseEvent) => void;
+  action: (e: MouseEvent) => void;
   i: string;
   tooltip: string;
   showButton: boolean;
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 2f3a2c6..0297f81 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -35,8 +35,6 @@
 import {TrackGroupPanel} from './track_group_panel';
 import {TrackPanel} from './track_panel';
 
-const SIDEBAR_WIDTH = 256;
-
 const OVERVIEW_PANEL_FLAG = featureFlags.register({
   id: 'overviewVisible',
   name: 'Overview Panel',
@@ -119,7 +117,6 @@
 
     this.zoomContent = new PanAndZoomHandler({
       element: panZoomEl,
-      contentOffsetX: SIDEBAR_WIDTH,
       onPanned: (pannedPx: number) => {
         const {
           visibleTimeScale,