Merge inbound to mozilla-central. a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Mon, 03 Dec 2018 11:30:40 +0200
changeset 508427 01d0813d8203d78613fc33a3e8e32627c1300b50
parent 508413 a73e91a77409762488958bf3b9430a2bec80c59c (current diff)
parent 508426 290a2b092c01cbb0a897bf4036f149aaafec70d0 (diff)
child 508428 9ad82455dcee2bc1d438e46016b8db00e88758a8
child 508446 5e2fad03c885961363aa136290e69df9009f4726
child 508461 f5851346109d87c59d7389d3b8b25f2c02bd7d08
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
01d0813d8203 / 65.0a1 / 20181203093109 / files
nightly linux64
01d0813d8203 / 65.0a1 / 20181203093109 / files
nightly mac
01d0813d8203 / 65.0a1 / 20181203093109 / files
nightly win32
01d0813d8203 / 65.0a1 / 20181203093109 / files
nightly win64
01d0813d8203 / 65.0a1 / 20181203093109 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/devtools/client/debugger/new/dist/debugger.css
+++ b/devtools/client/debugger/new/dist/debugger.css
@@ -2998,17 +2998,17 @@ debug-expression-error {
 }
 
 /* Don't display the highlight color since the debug line
   is already highlighted */
 .new-debug-line-error .CodeMirror-activeline-background {
   display: none;
 }
 
-.highlight-line .CodeMirror-line {
+.debugger:not(.can-rewind) .highlight-line .CodeMirror-line {
   animation: fade-highlight-out 1.5s normal forwards;
 }
 
 @keyframes fade-highlight-out {
   0% {
     background-color: var(--theme-highlight-gray);
   }
   100% {
--- a/devtools/client/debugger/new/src/actions/pause/mapFrames.js
+++ b/devtools/client/debugger/new/src/actions/pause/mapFrames.js
@@ -1,24 +1,44 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 
-import { getFrames, getSymbols, getSource } from "../../selectors";
+import {
+  getFrames,
+  getSymbols,
+  getSource,
+  getSelectedFrame
+} from "../../selectors";
+
 import assert from "../../utils/assert";
 import { findClosestFunction } from "../../utils/ast";
 
 import type { Frame } from "../../types";
 import type { State } from "../../reducers/types";
 import type { ThunkArgs } from "../types";
+import { features } from "../../utils/prefs";
 
 import { isGeneratedId } from "devtools-source-map";
 
+function getSelectedFrameId(state, frames) {
+  if (!features.originalBlackbox) {
+    const selectedFrame = getSelectedFrame(state);
+    return selectedFrame && selectedFrame.id;
+  }
+
+  const selectedFrame =  frames.find(frame =>
+    !getSource(state, frame.location.sourceId).isBlackBoxed
+  )
+
+  return selectedFrame && selectedFrame.id
+}
+
 export function updateFrameLocation(frame: Frame, sourceMaps: any) {
   if (frame.isOriginal) {
     return Promise.resolve(frame);
   }
   return sourceMaps.getOriginalLocation(frame.location).then(loc => ({
     ...frame,
     location: loc,
     generatedLocation: frame.generatedLocation || frame.location
@@ -144,14 +164,16 @@ export function mapFrames() {
     if (!frames) {
       return;
     }
 
     let mappedFrames = await updateFrameLocations(frames, sourceMaps);
     mappedFrames = await expandFrames(mappedFrames, sourceMaps, getState);
     mappedFrames = mapDisplayNames(mappedFrames, getState);
 
+    const selectedFrameId = getSelectedFrameId(getState(), mappedFrames)
     dispatch({
       type: "MAP_FRAMES",
-      frames: mappedFrames
+      frames: mappedFrames,
+      selectedFrameId
     });
   };
 }
--- a/devtools/client/debugger/new/src/actions/sources/blackbox.js
+++ b/devtools/client/debugger/new/src/actions/sources/blackbox.js
@@ -4,29 +4,38 @@
 
 // @flow
 
 /**
  * Redux actions for the sources state
  * @module actions/sources
  */
 
+import { isOriginalId } from "devtools-source-map";
 import { recordEvent } from "../../utils/telemetry";
+import { features } from "../../utils/prefs";
 
 import { PROMISE } from "../utils/middleware/promise";
 import type { Source } from "../../types";
 import type { ThunkArgs } from "../types";
 
 export function toggleBlackBox(source: Source) {
   return async ({ dispatch, getState, client, sourceMaps }: ThunkArgs) => {
     const { isBlackBoxed, id } = source;
 
     if (!isBlackBoxed) {
       recordEvent("blackbox");
     }
 
+    let promise;
+    if (features.originalBlackbox && isOriginalId(id)) {
+      promise = Promise.resolve({isBlackBoxed: !isBlackBoxed})
+    } else {
+      promise = client.blackBox(id, isBlackBoxed)
+    }
+
     return dispatch({
       type: "BLACKBOX",
       source,
-      [PROMISE]: client.blackBox(id, isBlackBoxed)
+      [PROMISE]: promise
     });
   };
 }
--- a/devtools/client/debugger/new/src/components/App.js
+++ b/devtools/client/debugger/new/src/components/App.js
@@ -1,28 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 import React, { Component } from "react";
 import { connect } from "react-redux";
 import PropTypes from "prop-types";
+import classnames from "classnames";
 
 import { prefs, features } from "../utils/prefs";
 import actions from "../actions";
 import A11yIntention from "./A11yIntention";
 import { ShortcutsModal } from "./ShortcutsModal";
 
 import {
   getSelectedSource,
   getPaneCollapse,
   getActiveSearch,
   getQuickOpenEnabled,
-  getOrientation
+  getOrientation,
+  getCanRewind
 } from "../selectors";
 
 import type { OrientationType } from "../reducers/types";
 import type { Source } from "../types";
 
 import { KeyShortcuts } from "devtools-modules";
 import Services from "devtools-services";
 const shortcuts = new KeyShortcuts({ window });
@@ -308,19 +310,19 @@ class App extends Component<Props, State
         additionalClass={additionalClass}
         enabled={this.state.shortcutsModalEnabled}
         handleClose={() => this.toggleShortcutsModal()}
       />
     );
   }
 
   render() {
-    const { quickOpenEnabled } = this.props;
+    const { quickOpenEnabled, canRewind } = this.props;
     return (
-      <div className="debugger">
+      <div className={classnames("debugger", { "can-rewind": canRewind })}>
         <A11yIntention>
           {this.renderLayout()}
           {quickOpenEnabled === true && (
             <QuickOpenModal
               shortcutsModalEnabled={this.state.shortcutsModalEnabled}
               toggleShortcutsModal={() => this.toggleShortcutsModal()}
             />
           )}
@@ -332,16 +334,17 @@ class App extends Component<Props, State
 }
 
 App.childContextTypes = {
   shortcuts: PropTypes.object,
   l10n: PropTypes.object
 };
 
 const mapStateToProps = state => ({
+  canRewind: getCanRewind(state),
   selectedSource: getSelectedSource(state),
   startPanelCollapsed: getPaneCollapse(state, "start"),
   endPanelCollapsed: getPaneCollapse(state, "end"),
   activeSearch: getActiveSearch(state),
   quickOpenEnabled: getQuickOpenEnabled(state),
   orientation: getOrientation(state)
 });
 
--- a/devtools/client/debugger/new/src/components/Editor/EditorMenu.js
+++ b/devtools/client/debugger/new/src/components/Editor/EditorMenu.js
@@ -9,17 +9,17 @@ import { isOriginalId } from "devtools-s
 
 import { copyToTheClipboard } from "../../utils/clipboard";
 import { findFunctionText } from "../../utils/function";
 import { findClosestFunction } from "../../utils/ast";
 import {
   getSourceLocationFromMouseEvent,
   toSourceLine
 } from "../../utils/editor";
-import { isPretty, getRawSourceURL } from "../../utils/source";
+import { isPretty, getRawSourceURL, shouldBlackbox } from "../../utils/source";
 import {
   getContextMenu,
   getPrettySource,
   getSelectedLocation,
   getSelectedSource,
   getSymbols
 } from "../../selectors";
 
@@ -157,18 +157,17 @@ function getMenuItems(
     disabled: !selectedSource.url,
     click: () => showSource(sourceId)
   };
 
   const blackBoxMenuItem = {
     id: "node-menu-blackbox",
     label: toggleBlackBoxLabel,
     accesskey: blackboxKey,
-    disabled:
-      isOriginal || isPrettyPrinted || hasSourceMap || !selectedSource.url,
+    disabled: !shouldBlackbox(selectedSource),
     click: () => toggleBlackBox(selectedSource)
   };
 
   const watchExpressionItem = {
     id: "node-menu-add-watch-expression",
     label: watchExpressionLabel,
     accesskey: watchExpressionKey,
     click: () => addExpression(editor.codeMirror.getSelection())
--- a/devtools/client/debugger/new/src/components/Editor/Footer.js
+++ b/devtools/client/debugger/new/src/components/Editor/Footer.js
@@ -14,17 +14,18 @@ import {
   getPaneCollapse
 } from "../../selectors";
 
 import {
   isPretty,
   isLoaded,
   getFilename,
   isOriginal,
-  isLoading
+  isLoading,
+  shouldBlackbox
 } from "../../utils/source";
 import { getGeneratedSource } from "../../reducers/sources";
 import { shouldShowFooter, shouldShowPrettyPrint } from "../../utils/editor";
 
 import { PaneToggleButton } from "../shared/Button";
 
 import type { Source } from "../../types";
 
@@ -102,17 +103,17 @@ class SourceFooter extends PureComponent
       </button>
     );
   }
 
   blackBoxButton() {
     const { selectedSource, toggleBlackBox } = this.props;
     const sourceLoaded = selectedSource && isLoaded(selectedSource);
 
-    if (!sourceLoaded || selectedSource.isPrettyPrinted) {
+    if (!shouldBlackbox(selectedSource)) {
       return;
     }
 
     const blackboxed = selectedSource.isBlackBoxed;
 
     const tooltip = L10N.getStr("sourceFooter.blackbox");
     const type = "black-box";
 
--- a/devtools/client/debugger/new/src/reducers/pause.js
+++ b/devtools/client/debugger/new/src/reducers/pause.js
@@ -113,17 +113,18 @@ function update(
         frames,
         frameScopes: { ...emptyPauseState.frameScopes },
         loadedObjects: objectMap,
         why
       };
     }
 
     case "MAP_FRAMES": {
-      return { ...state, frames: action.frames };
+      const { selectedFrameId, frames } = action;
+      return { ...state, frames, selectedFrameId };
     }
 
     case "ADD_EXTRA": {
       return { ...state, extra: action.extra };
     }
 
     case "ADD_SCOPES": {
       const { frame, status, value } = action;
--- a/devtools/client/debugger/new/src/utils/prefs.js
+++ b/devtools/client/debugger/new/src/utils/prefs.js
@@ -56,16 +56,17 @@ if (isDevelopment()) {
   pref("devtools.debugger.features.column-breakpoints", false);
   pref("devtools.debugger.features.pause-points", true);
   pref("devtools.debugger.features.skip-pausing", true);
   pref("devtools.debugger.features.component-pane", false);
   pref("devtools.debugger.features.autocomplete-expressions", false);
   pref("devtools.debugger.features.map-expression-bindings", true);
   pref("devtools.debugger.features.map-await-expression", true);
   pref("devtools.debugger.features.xhr-breakpoints", true);
+  pref("devtools.debugger.features.origial-blackbox", false);
 }
 
 export const prefs = new PrefsHelper("devtools", {
   logging: ["Bool", "debugger.alphabetize-outline"],
   alphabetizeOutline: ["Bool", "debugger.alphabetize-outline"],
   autoPrettyPrint: ["Bool", "debugger.auto-pretty-print"],
   clientSourceMapsEnabled: ["Bool", "source-map.client-service.enabled"],
   pauseOnExceptions: ["Bool", "debugger.pause-on-exceptions"],
@@ -108,17 +109,18 @@ export const features = new PrefsHelper(
   outline: ["Bool", "outline"],
   codeFolding: ["Bool", "code-folding"],
   pausePoints: ["Bool", "pause-points"],
   skipPausing: ["Bool", "skip-pausing"],
   autocompleteExpression: ["Bool", "autocomplete-expressions"],
   mapExpressionBindings: ["Bool", "map-expression-bindings"],
   mapAwaitExpression: ["Bool", "map-await-expression"],
   componentPane: ["Bool", "component-pane"],
-  xhrBreakpoints: ["Bool", "xhr-breakpoints"]
+  xhrBreakpoints: ["Bool", "xhr-breakpoints"],
+  originalBlackbox: ["Bool", "origial-blackbox"],
 });
 
 export const asyncStore = asyncStoreHelper("debugger", {
   pendingBreakpoints: ["pending-breakpoints", {}],
   tabs: ["tabs", []],
   xhrBreakpoints: ["xhr-breakpoints", []]
 });
 
--- a/devtools/client/debugger/new/src/utils/source.js
+++ b/devtools/client/debugger/new/src/utils/source.js
@@ -14,17 +14,17 @@ import { getUnicodeUrl } from "devtools-
 
 import { endTruncateStr } from "./utils";
 import { truncateMiddleText } from "../utils/text";
 import { parse as parseURL } from "../utils/url";
 import { renderWasmText } from "./wasm";
 import { toEditorPosition } from "./editor";
 export { isMinified } from "./isMinified";
 import { getURL, getFileExtension } from "./sources-tree";
-import { prefs } from "./prefs";
+import { prefs, features } from "./prefs";
 
 import type { Source, SourceLocation, JsSource } from "../types";
 import type { SourceMetaDataType } from "../reducers/ast";
 import type { SymbolDeclarations } from "../workers/parser";
 
 type transformUrlCallback = string => string;
 
 export const sourceTypes = {
@@ -50,16 +50,32 @@ function trimUrlQuery(url: string): stri
     q1 != -1 ? q1 : length,
     q2 != -1 ? q2 : length,
     q3 != -1 ? q3 : length
   );
 
   return url.slice(0, q);
 }
 
+export function shouldBlackbox(source: ?Source) {
+  if (!source) {
+    return false;
+  }
+
+  if (!isLoaded(source) || !source.url) {
+    return false;
+  }
+
+  if (isOriginalId(source.id) && !features.originalBlackbox) {
+    return false;
+  }
+
+  return true;
+}
+
 export function shouldPrettyPrint(source: Source) {
   if (
     !source ||
     isPretty(source) ||
     !isJavaScript(source) ||
     isOriginal(source) ||
     (prefs.clientSourceMapsEnabled && source.sourceMapURL)
   ) {
--- a/devtools/client/preferences/debugger.js
+++ b/devtools/client/preferences/debugger.js
@@ -62,8 +62,9 @@ pref("devtools.debugger.features.code-fo
 pref("devtools.debugger.features.outline", true);
 pref("devtools.debugger.features.pause-points", true);
 pref("devtools.debugger.features.component-pane", false);
 pref("devtools.debugger.features.async-stepping", true);
 pref("devtools.debugger.features.skip-pausing", true);
 pref("devtools.debugger.features.autocomplete-expressions", false);
 pref("devtools.debugger.features.map-expression-bindings", true);
 pref("devtools.debugger.features.xhr-breakpoints", true);
+pref("devtools.debugger.features.origial-blackbox", false);
\ No newline at end of file
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -478,31 +478,32 @@
 
 .webreplay-player .progressBar {
   position: relative;
   width: 100%;
   height: 20px;
   background: #fff;
   margin: 4px 10px 4px 0;
   border: 1px solid #bfc9d2;
+  overflow: hidden;
 }
 
 .webreplay-player .progress {
   position: absolute;
   width: 100%;
   height: 100%;
   background: var(--progress-playing-background);
   transition-duration: var(--progress-bar-transition);
 }
 
 .webreplay-player #overlay:not(.recording) .progress::after {
   background: var(--purple-50);
   width: 1px;
   height: 100%;
-  right: 0;
+  right: -0.5px;
   opacity: 0.4;
   display: block;
   content: "";
   position: absolute;
 }
 
 .webreplay-player .recording .progress {
   background: var(--progress-recording-background);
@@ -624,8 +625,59 @@
 .webreplay-player .progress-line.end {
   opacity: 0.3;
 }
 
 .webreplay-player .recording .progress-line {
   background: #d0021b;
   opacity: 0.3;
 }
+
+.webreplay-player .tick {
+  position: absolute;
+  height: 100%;
+}
+
+.webreplay-player .tick::before,
+.webreplay-player .tick::after {
+  height: 1.5px;
+  width: 1px;
+  right: 0;
+  position: absolute;
+  content: "";
+  display: block;
+}
+
+.webreplay-player .recording .tick::before,
+.webreplay-player .recording .tick::after {
+  background: #d0021b;
+}
+
+.webreplay-player .tick.future::before,
+.webreplay-player .tick.future::after {
+  background: #bfc9d2;
+}
+
+.webreplay-player .tick::before,
+.webreplay-player .tick::after {
+  background: var(--blue-50);
+}
+
+.webreplay-player .tick::after {
+  bottom: 0;
+}
+
+.webreplay-player .tick::before {
+  top: 0;
+}
+
+
+.webreplay-player #overlay:hover .tick {
+  opacity: 1;
+}
+
+.webreplay-player #overlay .tick {
+  opacity: 0.5;
+}
+
+.webreplay-player #overlay .tick:hover ~ .tick {
+ opacity: 0.5; 
+}
\ No newline at end of file
--- a/devtools/client/webreplay/components/WebReplayPlayer.js
+++ b/devtools/client/webreplay/components/WebReplayPlayer.js
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { Component } = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
-const {sortBy} = require("devtools/client/shared/vendor/lodash");
+const { sortBy, range } = require("devtools/client/shared/vendor/lodash");
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper(
   "devtools/client/locales/toolbox.properties"
 );
 const getFormatStr = (key, a) => L10N.getFormatStr(`toolbox.replay.${key}`, a);
 
 const { div } = dom;
@@ -99,18 +99,21 @@ class WebReplayPlayer extends Component 
     this.state = {
       executionPoint: null,
       recordingEndpoint: null,
       seeking: false,
       recording: true,
       paused: false,
       messages: [],
       highlightedMessage: null,
+      start: 0,
+      end: 1,
     };
-    this.overlayWidth = 0;
+    this.overlayWidth = 1;
+    this.onClickProgressBar = this.onClickProgressBar.bind(this);
   }
 
   componentDidMount() {
     this.overlayWidth = this.updateOverlayWidth();
     this.threadClient.addListener("paused", this.onPaused.bind(this));
     this.threadClient.addListener("resumed", this.onResumed.bind(this));
     this.threadClient.addListener("progress", this.onProgress.bind(this));
 
@@ -154,16 +157,29 @@ class WebReplayPlayer extends Component 
   isPaused() {
     return this.state.paused;
   }
 
   isSeeking() {
     return this.state.seeking;
   }
 
+  getTickSize() {
+    const {start, end} = this.state;
+    const minSize = 10;
+
+    if (!start && !end) {
+      return minSize;
+    }
+
+    const maxSize = this.overlayWidth / 10;
+    const ratio = end - start;
+    return ((1 - ratio) * maxSize) + minSize;
+  }
+
   onPaused(_, packet) {
     if (packet && packet.recordingEndpoint) {
       const { executionPoint, recordingEndpoint } = packet;
       const closestMessage = getClosestMessage(this.state.messages, executionPoint);
 
       this.setState({
         executionPoint,
         recordingEndpoint,
@@ -199,35 +215,60 @@ class WebReplayPlayer extends Component 
 
     this.setState(newState);
   }
 
   onConsoleUpdate(consoleState) {
     const {
       messages: { visibleMessages, messagesById },
     } = consoleState;
-    const messages = visibleMessages.map(id => messagesById.get(id));
 
     if (visibleMessages != this.state.visibleMessages) {
+      const messages = sortBy(
+        visibleMessages.map(id => messagesById.get(id)),
+        message => getMessageProgress(message)
+      );
+
       this.setState({ messages, visibleMessages });
     }
   }
 
   onConsoleMessageHover(type, message) {
     if (type == "mouseleave") {
       return this.setState({ highlightedMessage: null });
     }
 
     if (type == "mouseenter") {
       return this.setState({ highlightedMessage: message.id });
     }
 
     return null;
   }
 
+  onClickProgressBar(e) {
+    if (!e.altKey) {
+      return;
+    }
+
+    const {start, end} = this.state;
+
+    const direction = e.shiftKey ? "end" : "start";
+    const { left, width } = e.currentTarget.getBoundingClientRect();
+    const clickLeft = e.clientX;
+
+    const clickPosition = (clickLeft - left) / width;
+    const position = ((end - start) * clickPosition) + start;
+
+    this.setTimelinePosition({ position, direction });
+  }
+
+  setTimelinePosition({ position, direction }) {
+    this.setState({[direction]: position});
+  }
+
   scrollToMessage() {
     const {closestMessage} = this.state;
 
     if (!closestMessage) {
       return;
     }
 
     const consoleOutput = this.console.hud.ui.outputNode;
@@ -353,38 +394,74 @@ class WebReplayPlayer extends Component 
         img: "next",
         onClick: () => this.next(),
       }),
     ];
   }
 
   updateOverlayWidth() {
     const el = ReactDOM.findDOMNode(this).querySelector(".progressBar");
-    return el.clientWidth;
+    return el ? el.clientWidth : 1;
   }
 
   // calculate pixel distance from two points
   getDistanceFrom(to, from) {
     const toPercent = this.getPercent(to);
     const fromPercent = this.getPercent(from);
 
     return ((toPercent - fromPercent) * this.overlayWidth) / 100;
   }
 
   getOffset(point) {
     const percent = this.getPercent(point);
     return (percent * this.overlayWidth) / 100;
   }
 
+  getPercent(executionPoint) {
+    const {recordingEndpoint} = this.state;
+
+    if (!recordingEndpoint) {
+      return 100;
+    }
+
+    if (!executionPoint) {
+      return 0;
+    }
+
+    const ratio = executionPoint.progress / recordingEndpoint.progress;
+    return ratio * 100;
+  }
+
+  getVisiblePercent(executionPoint) {
+    const {start, end} = this.state;
+
+    const position = this.getPercent(executionPoint) / 100;
+
+    if (position < start || position > end) {
+      return -1;
+    }
+
+    return ((position - start) / (end - start)) * 100;
+  }
+
+  getVisibleOffset(point) {
+    const percent = this.getVisiblePercent(point);
+    return (percent * this.overlayWidth) / 100;
+  }
+
   renderMessage(message, index) {
     const { messages, executionPoint, highlightedMessage } = this.state;
 
-    const offset = this.getOffset(message.executionPoint);
+    const offset = this.getVisibleOffset(message.executionPoint);
     const previousMessage = messages[index - 1];
 
+    if (offset < 0) {
+      return null;
+    }
+
     // Check to see if two messages overlay each other on the timeline
     const isOverlayed =
       previousMessage &&
       this.getDistanceFrom(
         message.executionPoint,
         previousMessage.executionPoint
       ) < markerWidth;
 
@@ -397,71 +474,91 @@ class WebReplayPlayer extends Component 
 
     return dom.a({
       className: classname("message", {
         overlayed: isOverlayed,
         future: isFuture,
         highlighted: isHighlighted,
       }),
       style: {
-        left: `${offset - markerWidth / 2}px`,
+        left: `${Math.max(offset - markerWidth / 2, 0)}px`,
         zIndex: `${index + 100}`,
       },
       title: getFormatStr("jumpMessage", index + 1),
-      onClick: () => this.seek(message.executionPoint),
+      onClick: (e) => {
+        e.preventDefault();
+        e.stopPropagation();
+        this.seek(message.executionPoint);
+      },
     });
   }
 
   renderMessages() {
     const messages = this.state.messages;
-    return messages.map((message, index) => this.renderMessage(message, index));
+    return messages
+      .map((message, index) => this.renderMessage(message, index));
+  }
+
+  renderTicks() {
+    const tickSize = this.getTickSize();
+    const ticks =  Math.round((this.overlayWidth) / tickSize);
+    return range(ticks).map((value, index) => this.renderTick(index));
   }
 
-  getPercent(executionPoint) {
-    if (!this.state.recordingEndpoint) {
-      return 100;
-    }
+  renderTick(index) {
+    const { executionPoint } = this.state;
+    const tickSize = this.getTickSize();
+    const offset = Math.round(this.getOffset(executionPoint));
+    const position = index * tickSize;
+    const isFuture = position > offset;
 
-    if (!executionPoint) {
-      return 0;
-    }
-
-    const ratio =
-      executionPoint.progress / this.state.recordingEndpoint.progress;
-    return ratio * 100;
+    return dom.span({
+      className: classname("tick", {
+        future: isFuture,
+      }),
+      style: {
+        left: `${position}px`,
+        width: `${tickSize}px`,
+      },
+    });
   }
 
   render() {
-    const percent = this.getPercent(this.state.executionPoint);
+    const percent = this.getVisiblePercent(this.state.executionPoint);
     const recording = this.isRecording();
     return div(
       { className: "webreplay-player" },
       div(
         {
           id: "overlay",
           className: classname("", { recording: recording, paused: !recording }),
         },
         div(
           { className: "overlay-container " },
           div({ className: "commands" }, ...this.renderCommands()),
           div(
-            { className: "progressBar" },
+            {
+              className: "progressBar",
+              onClick: this.onClickProgressBar,
+              onDoubleClick: () => this.setState({ start: 0, end: 1 }),
+            },
             div({
               className: "progress",
               style: { width: `${percent}%` },
             }),
             div({
               className: "progress-line",
               style: { width: `${percent}%` },
             }),
             div({
               className: "progress-line end",
               style: { left: `${percent}%`, width: `${100 - percent}%` },
             }),
-            ...this.renderMessages()
+            ...this.renderMessages(),
+            ...this.renderTicks()
           )
         )
       )
     );
   }
 }
 
 module.exports = WebReplayPlayer;
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -320,17 +320,17 @@ random-if(gtkWidget) == objectBoundingBo
 
 == opacity-and-gradient-01.svg pass.svg
 skip-if(d2d) fuzzy-if(cocoaWidget,0-1,0-99974) fuzzy-if(skiaContent,0-1,0-200000) == opacity-and-gradient-02.svg opacity-and-gradient-02-ref.svg
 == opacity-and-pattern-01.svg pass.svg
 fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||skiaContent,0-1,0-10000) == opacity-and-transform-01.svg opacity-and-transform-01-ref.svg
 
 fuzzy-if(Android,0-8,0-200) == outer-svg-border-and-padding-01.svg outer-svg-border-and-padding-01-ref.svg
 
-fuzzy-if(skiaContent,0-7,0-175) fuzzy-if(webrender,54-54,124-124) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(gtkWidget) skip-if(cocoaWidget&&isDebugBuild) == outline.html outline-ref.html # Bug 1392106, Bug 1503525
+fuzzy(0-16,0-193) fuzzy-if(skiaContent,0-7,0-175) fuzzy-if(webrender,54-54,124-124) == outline.html outline-ref.html # Bug 1392106, Bug 1503525
 
 == overflow-on-outer-svg-01.svg overflow-on-outer-svg-01-ref.svg
 == overflow-on-outer-svg-02a.xhtml overflow-on-outer-svg-02-ref.xhtml
 == overflow-on-outer-svg-02b.xhtml overflow-on-outer-svg-02-ref.xhtml
 == overflow-on-outer-svg-02c.xhtml overflow-on-outer-svg-02-ref.xhtml
 == overflow-on-outer-svg-02d.xhtml overflow-on-outer-svg-02-ref.xhtml
 == overflow-on-outer-svg-03a.xhtml overflow-on-outer-svg-03-ref.xhtml
 == overflow-on-outer-svg-03b.xhtml overflow-on-outer-svg-03-ref.xhtml
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -202,17 +202,16 @@ skip-if = toolkit == 'android' #bug 5366
 [test_css_supports.html]
 [test_css_supports_variables.html]
 [test_custom_content_inheritance.html]
 [test_default_bidi_css.html]
 [test_default_computed_style.html]
 [test_descriptor_storage.html]
 [test_descriptor_syntax_errors.html]
 [test_dont_use_document_colors.html]
-skip-if = (os == 'win' && asan) # Bug 1458365
 [test_dynamic_change_causing_reflow.html]
 [test_exposed_prop_accessors.html]
 [test_extra_inherit_initial.html]
 [test_flexbox_child_display_values.xhtml]
 [test_flexbox_flex_grow_and_shrink.html]
 [test_flexbox_flex_shorthand.html]
 [test_flexbox_focus_order.html]
 [test_flexbox_layout.html]