Bug 1463481 - Update Debugger Frontend v58. r=dwalsh
authorJason Laster <jason.laster.11@gmail.com>
Tue, 22 May 2018 12:24:27 -0400
changeset 419751 f38805d4a3898e56052231f6a14a785094205270
parent 419750 8239a2ac64c54fae1bd2b573d1e7218d1f37fc87
child 419752 0df854d34c01bafb3c88d595f8464ed381b95d03
child 419787 135af0c54dd7dda95f06eaf587b9d52fff5fa677
push id34046
push usercbrindusan@mozilla.com
push dateFri, 25 May 2018 00:04:29 +0000
treeherdermozilla-central@0df854d34c01 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdwalsh
bugs1463481
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1463481 - Update Debugger Frontend v58. r=dwalsh MozReview-Commit-ID: JS9wvG9Qmw6
devtools/client/debugger/new/README.mozilla
devtools/client/debugger/new/dist/debugger.css
devtools/client/debugger/new/dist/vendors.js
devtools/client/debugger/new/src/actions/pause/extra.js
devtools/client/debugger/new/src/components/Editor/Preview/Popup.js
devtools/client/debugger/new/src/components/Editor/Tab.js
devtools/client/debugger/new/src/components/QuickOpenModal.js
devtools/client/debugger/new/src/components/SecondaryPanes/Frames/WhyPaused.js
devtools/client/debugger/new/src/components/shared/SearchInput.js
devtools/client/debugger/new/src/utils/preview.js
devtools/client/debugger/new/src/utils/source.js
devtools/client/debugger/new/src/utils/sources-tree/getURL.js
devtools/client/shared/components/reps/reps.js
devtools/client/shared/source-map/worker.js
--- a/devtools/client/debugger/new/README.mozilla
+++ b/devtools/client/debugger/new/README.mozilla
@@ -1,13 +1,13 @@
 This is the debugger.html project output.
 See https://github.com/devtools-html/debugger.html
 
-Version 57
+Version 58
 
-Comparison: https://github.com/devtools-html/debugger.html/compare/release-56...release-57
+Comparison: https://github.com/devtools-html/debugger.html/compare/release-57...release-58
 
 Packages:
 - babel-plugin-transform-es2015-modules-commonjs @6.26.2
 - babel-preset-react @6.24.1
 - react @16.2.0
 - react-dom @16.2.0
 - webpack @3.12.0
--- a/devtools/client/debugger/new/dist/debugger.css
+++ b/devtools/client/debugger/new/dist/debugger.css
@@ -1128,22 +1128,27 @@ html .toggle-button.end.vertical svg {
 }
 
 .search-field input.empty {
   color: var(--theme-highlight-orange);
 }
 
 .search-field .summary {
   line-height: 27px;
+  text-align: center;
   padding-right: 10px;
   color: var(--theme-body-color-inactive);
+  align-self: center;
+  padding-top: 1px;
+  white-space: nowrap;
 }
 
 .search-field.big .summary {
-  line-height: 40px;
+  padding: 5px 0 5px 0;
+  line-height: 2rem;
 }
 
 .search-field .search-nav-buttons {
   display: flex;
   user-select: none;
 }
 
 .search-field .search-nav-buttons .nav-btn {
--- a/devtools/client/debugger/new/dist/vendors.js
+++ b/devtools/client/debugger/new/dist/vendors.js
@@ -7938,16 +7938,20 @@ class Tree extends Component {
         depth,
         renderItem: this.props.renderItem,
         focused: focused === item,
         expanded: this.props.isExpanded(item),
         isExpandable: this._nodeIsExpandable(item),
         onExpand: this._onExpand,
         onCollapse: this._onCollapse,
         onClick: e => {
+          // We can stop the propagation since click handler on the node can be
+          // created in `renderItem`.
+          e.stopPropagation();
+
           // Since the user just clicked the node, there's no need to check if
           // it should be scrolled into view.
           this._focus(item, { preventAutoScroll: true });
           if (this.props.isExpanded(item)) {
             this.props.onCollapse(item);
           } else {
             this.props.onExpand(item, e.altKey);
           }
--- a/devtools/client/debugger/new/src/actions/pause/extra.js
+++ b/devtools/client/debugger/new/src/actions/pause/extra.js
@@ -55,17 +55,17 @@ async function getImmutableProps(express
 async function getExtraProps(getState, expression, result, evaluate) {
   const props = {};
   const component = (0, _selectors.inComponent)(getState());
 
   if (component) {
     props.react = await getReactProps(evaluate, component);
   }
 
-  if ((0, _preview.isImmutable)(result)) {
+  if ((0, _preview.isImmutablePreview)(result)) {
     props.immutable = await getImmutableProps(expression, evaluate);
   }
 
   return props;
 }
 
 function fetchExtra() {
   return async function ({
--- a/devtools/client/debugger/new/src/components/Editor/Preview/Popup.js
+++ b/devtools/client/debugger/new/src/components/Editor/Preview/Popup.js
@@ -26,16 +26,18 @@ var _Popover = require("../../shared/Pop
 var _Popover2 = _interopRequireDefault(_Popover);
 
 var _PreviewFunction = require("../../shared/PreviewFunction");
 
 var _PreviewFunction2 = _interopRequireDefault(_PreviewFunction);
 
 var _editor = require("../../../utils/editor/index");
 
+var _preview = require("../../../utils/preview");
+
 var _Svg = require("devtools/client/debugger/new/dist/vendors").vendored["Svg"];
 
 var _Svg2 = _interopRequireDefault(_Svg);
 
 var _firefox = require("../../../client/firefox");
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
@@ -130,32 +132,41 @@ class Popup extends _react.Component {
       name: expression,
       path: expression,
       contents: {
         value: rootValue
       }
     });
   }
 
-  getChildren() {
+  getObjectProperties() {
     const {
       popupObjectProperties
     } = this.props;
     const root = this.getRoot();
     const value = getValue(root);
-    const actor = value ? value.actor : null;
-    const loadedRootProperties = popupObjectProperties[actor];
+
+    if (!value) {
+      return null;
+    }
 
-    if (!loadedRootProperties) {
+    return popupObjectProperties[value.actor];
+  }
+
+  getChildren() {
+    const properties = this.getObjectProperties();
+    const root = this.getRoot();
+
+    if (!properties) {
       return null;
     }
 
     const children = getChildren({
       item: root,
-      loadedProperties: new Map([[root.path, loadedRootProperties]])
+      loadedProperties: new Map([[root.path, properties]])
     });
 
     if (children.length > 0) {
       return children;
     }
 
     return null;
   }
@@ -216,22 +227,22 @@ class Popup extends _react.Component {
     let roots = this.getChildren();
 
     if (!Array.isArray(roots) || roots.length === 0) {
       return null;
     }
 
     let header = null;
 
-    if (extra.immutable) {
+    if ((0, _preview.isImmutable)(this.getObjectProperties())) {
       header = this.renderImmutable(extra.immutable);
       roots = roots.filter(r => r.type != NODE_TYPES.PROTOTYPE);
     }
 
-    if (extra.react) {
+    if ((0, _preview.isReactComponent)(this.getObjectProperties())) {
       header = this.renderReact(extra.react);
       roots = roots.filter(r => ["state", "props"].includes(r.name));
     }
 
     return _react2.default.createElement("div", {
       className: "preview-popup"
     }, header, this.renderObjectInspector(roots));
   }
--- a/devtools/client/debugger/new/src/components/Editor/Tab.js
+++ b/devtools/client/debugger/new/src/components/Editor/Tab.js
@@ -19,16 +19,18 @@ var _SourceIcon2 = _interopRequireDefaul
 var _Button = require("../shared/Button/index");
 
 var _actions = require("../../actions/index");
 
 var _actions2 = _interopRequireDefault(_actions);
 
 var _source = require("../../utils/source");
 
+var _devtoolsModules = require("devtools/client/debugger/new/dist/vendors").vendored["devtools-modules"];
+
 var _clipboard = require("../../utils/clipboard");
 
 var _tabs = require("../../utils/tabs");
 
 var _selectors = require("../../selectors/index");
 
 var _classnames = require("devtools/client/debugger/new/dist/vendors").vendored["classnames"];
 
@@ -168,17 +170,17 @@ class Tab extends _react.PureComponent {
       onMouseUp: handleTabClick,
       onContextMenu: e => this.onTabContextMenu(e, sourceId),
       title: (0, _source.getFileURL)(source)
     }, _react2.default.createElement(_SourceIcon2.default, {
       source: source,
       shouldHide: icon => ["file", "javascript"].includes(icon)
     }), _react2.default.createElement("div", {
       className: "filename"
-    }, filename), _react2.default.createElement(_Button.CloseButton, {
+    }, (0, _devtoolsModules.getUnicodeUrlPath)(filename)), _react2.default.createElement(_Button.CloseButton, {
       handleClick: onClickClose,
       tooltip: L10N.getStr("sourceTabs.closeTabButtonTooltip")
     }));
   }
 
 }
 
 const mapStateToProps = (state, {
--- a/devtools/client/debugger/new/src/components/QuickOpenModal.js
+++ b/devtools/client/debugger/new/src/components/QuickOpenModal.js
@@ -284,20 +284,16 @@ class QuickOpenModal extends _react.Comp
 
       if (e.key === "Enter") {
         if (this.isGotoQuery()) {
           const location = (0, _quickOpen.parseLineColumn)(query);
           return this.gotoLocation(location);
         }
 
         if (results) {
-          if (this.isShortcutQuery()) {
-            return this.setModifier(results[selectedIndex]);
-          }
-
           return this.selectResultItem(e, results[selectedIndex]);
         }
       }
 
       if (e.key === "Tab") {
         return this.closeModal();
       }
 
@@ -355,28 +351,16 @@ class QuickOpenModal extends _react.Comp
       newQuery = query.replace(/[@:#?]/gi, " ");
       return results.map(result => {
         return _objectSpread({}, result, {
           title: this.renderHighlight(result.title, (0, _path.basename)(newQuery), "title")
         });
       });
     };
 
-    this.renderLoading = () => {
-      const {
-        symbolsLoading
-      } = this.props;
-
-      if ((this.isFunctionQuery() || this.isVariableQuery()) && symbolsLoading) {
-        return _react2.default.createElement("div", {
-          className: "loading-indicator"
-        }, L10N.getStr("loadingText"));
-      }
-    };
-
     this.state = {
       results: null,
       selectedIndex: 0
     };
   }
 
   componentDidMount() {
     const {
@@ -411,16 +395,28 @@ class QuickOpenModal extends _react.Comp
 
     if (this.isGotoQuery()) {
       return !/^:\d*$/.test(query);
     }
 
     return !this.getResultCount() && !!query;
   }
 
+  getSummaryMessage() {
+    let summaryMsg = "";
+
+    if (this.isGotoQuery()) {
+      summaryMsg = L10N.getStr("shortcuts.gotoLine");
+    } else if ((this.isFunctionQuery() || this.isVariableQuery()) && this.props.symbolsLoading) {
+      summaryMsg = L10N.getStr("loadingText");
+    }
+
+    return summaryMsg;
+  }
+
   render() {
     const {
       enabled,
       query
     } = this.props;
     const {
       selectedIndex,
       results
@@ -428,36 +424,35 @@ class QuickOpenModal extends _react.Comp
 
     if (!enabled) {
       return null;
     }
 
     const newResults = results && results.slice(0, 100);
     const items = this.highlightMatching(query, newResults || []);
     const expanded = !!items && items.length > 0;
-    const summaryMsg = this.isGotoQuery() ? L10N.getStr("shortcuts.gotoLine") : "";
     return _react2.default.createElement(_Modal2.default, {
       "in": enabled,
       handleClose: this.closeModal
     }, _react2.default.createElement(_SearchInput2.default, _extends({
       query: query,
       hasPrefix: true,
       count: this.getResultCount(),
       placeholder: L10N.getStr("sourceSearch.search"),
-      summaryMsg: summaryMsg,
+      summaryMsg: this.getSummaryMessage(),
       showErrorEmoji: this.shouldShowErrorEmoji(),
       onChange: this.onChange,
       onKeyDown: this.onKeyDown,
       handleClose: this.closeModal,
       expanded: expanded,
       showClose: false,
       selectedItemId: expanded && items[selectedIndex] ? items[selectedIndex].id : ""
     }, this.isSourceSearch() ? {
       size: "big"
-    } : {})), this.renderLoading(), newResults && _react2.default.createElement(_ResultList2.default, _extends({
+    } : {})), newResults && _react2.default.createElement(_ResultList2.default, _extends({
       key: "results",
       items: items,
       selected: selectedIndex,
       selectItem: this.selectResultItem,
       ref: "resultList",
       expanded: expanded
     }, this.isSourceSearch() ? {
       size: "big"
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Frames/WhyPaused.js
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Frames/WhyPaused.js
@@ -18,17 +18,17 @@ function _interopRequireDefault(obj) { r
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 function renderExceptionSummary(exception) {
   if (typeof exception === "string") {
     return exception;
   }
 
   const preview = exception.preview;
 
-  if (!preview) {
+  if (!preview || !preview.name || !preview.message) {
     return;
   }
 
   return `${preview.name}: ${preview.message}`;
 }
 
 function renderMessage(why) {
   if (why.type == "exception" && why.exception) {
--- a/devtools/client/debugger/new/src/components/shared/SearchInput.js
+++ b/devtools/client/debugger/new/src/components/shared/SearchInput.js
@@ -106,16 +106,30 @@ class SearchInput extends _react.Compone
   renderArrowButtons() {
     const {
       handleNext,
       handlePrev
     } = this.props;
     return [arrowBtn(handleNext, "arrow-down", (0, _classnames2.default)("nav-btn", "next"), L10N.getFormatStr("editor.searchResults.nextResult")), arrowBtn(handlePrev, "arrow-up", (0, _classnames2.default)("nav-btn", "prev"), L10N.getFormatStr("editor.searchResults.prevResult"))];
   }
 
+  renderSummaryMsg() {
+    const {
+      summaryMsg
+    } = this.props;
+
+    if (!summaryMsg) {
+      return null;
+    }
+
+    return _react2.default.createElement("div", {
+      className: "summary"
+    }, summaryMsg);
+  }
+
   renderNav() {
     const {
       count,
       handleNext,
       handlePrev
     } = this.props;
 
     if (!handleNext && !handlePrev || !count || count == 1) {
@@ -134,17 +148,16 @@ class SearchInput extends _react.Compone
       onChange,
       onKeyDown,
       onKeyUp,
       placeholder,
       query,
       selectedItemId,
       showErrorEmoji,
       size,
-      summaryMsg,
       showClose
     } = this.props;
     const inputProps = {
       className: (0, _classnames2.default)({
         empty: showErrorEmoji
       }),
       onChange,
       onKeyDown,
@@ -164,19 +177,17 @@ class SearchInput extends _react.Compone
         focused: this.state.inputFocused
       })
     }, _react2.default.createElement("div", {
       className: (0, _classnames2.default)("search-field", size),
       role: "combobox",
       "aria-haspopup": "listbox",
       "aria-owns": "result-list",
       "aria-expanded": expanded
-    }, this.renderSvg(), _react2.default.createElement("input", inputProps), summaryMsg && _react2.default.createElement("div", {
-      className: "summary"
-    }, summaryMsg), this.renderNav(), showClose && _react2.default.createElement(_Button.CloseButton, {
+    }, this.renderSvg(), _react2.default.createElement("input", inputProps), this.renderSummaryMsg(), this.renderNav(), showClose && _react2.default.createElement(_Button.CloseButton, {
       handleClick: handleClose,
       buttonClass: size
     })));
   }
 
 }
 
 SearchInput.defaultProps = {
--- a/devtools/client/debugger/new/src/utils/preview.js
+++ b/devtools/client/debugger/new/src/utils/preview.js
@@ -1,45 +1,41 @@
 "use strict";
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.isImmutablePreview = isImmutablePreview;
 exports.isImmutable = isImmutable;
 exports.isReactComponent = isReactComponent;
 exports.isConsole = isConsole;
 
 /* 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/>. */
 const IMMUTABLE_FIELDS = ["_root", "__ownerID", "__altered", "__hash"];
+const REACT_FIELDS = ["_reactInternalInstance", "_reactInternalFiber"];
+
+function isImmutablePreview(result) {
+  return result && isImmutable(result.preview);
+}
 
 function isImmutable(result) {
-  if (!result || !result.preview) {
+  if (!result || typeof result.ownProperties != "object") {
     return;
   }
 
-  const ownProperties = result.preview.ownProperties;
-
-  if (!ownProperties) {
-    return;
-  }
-
+  const ownProperties = result.ownProperties;
   return IMMUTABLE_FIELDS.every(field => Object.keys(ownProperties).includes(field));
 }
 
 function isReactComponent(result) {
-  if (!result || !result.preview) {
+  if (!result || typeof result.ownProperties != "object") {
     return;
   }
 
-  const ownProperties = result.preview.ownProperties;
-
-  if (!ownProperties) {
-    return;
-  }
-
-  return Object.keys(ownProperties).includes("_reactInternalInstance") || Object.keys(ownProperties).includes("_reactInternalFiber");
+  const ownProperties = result.ownProperties;
+  return REACT_FIELDS.some(field => Object.keys(ownProperties).includes(field));
 }
 
 function isConsole(expression) {
   return /^console/.test(expression);
 }
\ No newline at end of file
--- a/devtools/client/debugger/new/src/utils/source.js
+++ b/devtools/client/debugger/new/src/utils/source.js
@@ -35,16 +35,18 @@ exports.getSourceClassnames = getSourceC
 var _devtoolsSourceMap = require("devtools/client/shared/source-map/index.js");
 
 var _utils = require("./utils");
 
 var _path = require("./path");
 
 var _url = require("devtools/client/debugger/new/dist/vendors").vendored["url"];
 
+var _devtoolsModules = require("devtools/client/debugger/new/dist/vendors").vendored["devtools-modules"];
+
 var _sourcesTree = require("./sources-tree/index");
 
 const sourceTypes = exports.sourceTypes = {
   coffee: "coffeescript",
   js: "javascript",
   jsx: "react",
   ts: "typescript"
 };
@@ -146,28 +148,35 @@ function getRawSourceURL(url) {
   return url ? url.replace(/:formatted$/, "") : url;
 }
 
 function resolveFileURL(url, transformUrl = initialUrl => initialUrl) {
   url = getRawSourceURL(url || "");
   const name = transformUrl(url);
   return (0, _utils.endTruncateStr)(name, 50);
 }
+/**
+ * Gets a readable filename from a URL for display purposes.
+ *
+ * @memberof utils/source
+ * @static
+ */
+
 
 function getFilenameFromURL(url) {
-  return resolveFileURL(url, initialUrl => (0, _path.basename)(initialUrl) || "(index)");
+  return resolveFileURL(url, initialUrl => (0, _devtoolsModules.getUnicodeUrlPath)((0, _path.basename)(initialUrl)) || "(index)");
 }
 
 function getFormattedSourceId(id) {
   const sourceId = id.split("/")[1];
   return `SOURCE${sourceId}`;
 }
 /**
- * Show a source url's filename.
- * If the source does not have a url, use the source id.
+ * Gets a readable filename from a source URL for display purposes.
+ * If the source does not have a URL, the source ID will be returned instead.
  *
  * @memberof utils/source
  * @static
  */
 
 
 function getFilename(source) {
   const {
@@ -184,35 +193,35 @@ function getFilename(source) {
 
   if (qMarkIdx > 0) {
     filename = filename.slice(0, qMarkIdx);
   }
 
   return filename;
 }
 /**
- * Show a source url.
- * If the source does not have a url, use the source id.
+ * Gets a readable source URL for display purposes.
+ * If the source does not have a URL, the source ID will be returned instead.
  *
  * @memberof utils/source
  * @static
  */
 
 
 function getFileURL(source) {
   const {
     url,
     id
   } = source;
 
   if (!url) {
     return getFormattedSourceId(id);
   }
 
-  return resolveFileURL(url);
+  return resolveFileURL(url, _devtoolsModules.getUnicodeUrl);
 }
 
 const contentTypeModeMap = {
   "text/javascript": {
     name: "javascript"
   },
   "text/typescript": {
     name: "javascript",
--- a/devtools/client/debugger/new/src/utils/sources-tree/getURL.js
+++ b/devtools/client/debugger/new/src/utils/sources-tree/getURL.js
@@ -5,16 +5,18 @@ Object.defineProperty(exports, "__esModu
 });
 exports.getFilenameFromPath = getFilenameFromPath;
 exports.getURL = getURL;
 
 var _url = require("devtools/client/debugger/new/dist/vendors").vendored["url"];
 
 var _lodash = require("devtools/client/shared/vendor/lodash");
 
+var _devtoolsModules = require("devtools/client/debugger/new/dist/vendors").vendored["devtools-modules"];
+
 /* 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/>. */
 function getFilenameFromPath(pathname) {
   let filename = "";
 
   if (pathname) {
     filename = pathname.substring(pathname.lastIndexOf("/") + 1); // This file does not have a name. Default should be (index).
@@ -106,17 +108,17 @@ function getURL(sourceUrl, debuggeeUrl =
       }
 
       break;
 
     case "http:":
     case "https:":
       return (0, _lodash.merge)(def, {
         path: pathname,
-        group: host,
+        group: (0, _devtoolsModules.getUnicodeHostname)(host),
         filename: filename
       });
   }
 
   return (0, _lodash.merge)(def, {
     path: path,
     group: protocol ? `${protocol}//` : "",
     filename: filename
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -4404,25 +4404,28 @@ class Tree extends Component {
         depth,
         renderItem: this.props.renderItem,
         focused: focused === item,
         expanded: this.props.isExpanded(item),
         isExpandable: this._nodeIsExpandable(item),
         onExpand: this._onExpand,
         onCollapse: this._onCollapse,
         onClick: e => {
+          // We can stop the propagation since click handler on the node can be
+          // created in `renderItem`.
+          e.stopPropagation();
+
           // Since the user just clicked the node, there's no need to check if
           // it should be scrolled into view.
           this._focus(item, { preventAutoScroll: true });
           if (this.props.isExpanded(item)) {
             this.props.onCollapse(item);
           } else {
             this.props.onExpand(item, e.altKey);
           }
-          e.stopPropagation();
         }
       });
     });
 
     const style = Object.assign({}, this.props.style || {}, {
       padding: 0,
       margin: 0
     });
@@ -6441,17 +6444,17 @@ class ObjectInspector extends Component 
 
     const parentElementProps = {
       className: classnames("node object-node", {
         focused,
         lessen: !expanded && (nodeIsDefaultProperties(item) || nodeIsPrototype(item) || dimTopLevelWindow === true && nodeIsWindow(item) && depth === 0),
         block: nodeIsBlock(item)
       }),
       onClick: e => {
-        if (e.metaKey && onCmdCtrlClick) {
+        if (onCmdCtrlClick && (isMacOS && e.metaKey || !isMacOS && e.ctrlKey)) {
           onCmdCtrlClick(item, {
             depth,
             event: e,
             focused,
             expanded
           });
           e.stopPropagation();
           return;
--- a/devtools/client/shared/source-map/worker.js
+++ b/devtools/client/shared/source-map/worker.js
@@ -2141,31 +2141,51 @@ async function getAllGeneratedLocations(
 
   return positions.map(({ line, column }) => ({
     sourceId: generatedSourceId,
     line,
     column
   }));
 }
 
-async function getOriginalLocation(location) {
+async function getOriginalLocation(location, { search } = {}) {
   if (!isGeneratedId(location.sourceId)) {
     return location;
   }
 
   const map = await getSourceMap(location.sourceId);
   if (!map) {
     return location;
   }
 
-  const { source: sourceUrl, line, column } = map.originalPositionFor({
+  // First check for an exact match
+  let match = map.originalPositionFor({
     line: location.line,
     column: location.column == null ? 0 : location.column
   });
 
+  // If there is not an exact match, look for a match with a bias at the
+  // current location and then on subsequent lines
+  if (search) {
+    let line = location.line;
+    let column = location.column == null ? 0 : location.column;
+
+    while (match.source === null) {
+      match = map.originalPositionFor({
+        line,
+        column,
+        bias: SourceMapConsumer[search]
+      });
+
+      line += search == "LEAST_UPPER_BOUND" ? 1 : -1;
+      column = search == "LEAST_UPPER_BOUND" ? 0 : Infinity;
+    }
+  }
+
+  const { source: sourceUrl, line, column } = match;
   if (sourceUrl == null) {
     // No url means the location didn't map.
     return location;
   }
 
   return {
     sourceId: generatedToOriginalId(location.sourceId, sourceUrl),
     sourceUrl,