@@ -1077,7 +1077,18 @@ MarkupView.prototype = {
1077
1077
1078
1078
const slotted = selection . isSlotted ( ) ;
1079
1079
const smoothScroll = reason === "reveal-from-slot" ;
1080
- const onShow = this . showNode ( selection . nodeFront , { slotted, smoothScroll } )
1080
+ const selectionSearchQuery = selection . getSearchQuery ( ) ;
1081
+
1082
+ const onShow = this . showNode ( selection . nodeFront , {
1083
+ slotted,
1084
+ smoothScroll,
1085
+ // Don't scroll if we selected the node from the search, we'll scroll to the first
1086
+ // matching Range done in _updateSearchResultsHighlightingInSelectedNode.
1087
+ // This need to be done there because the matching Range might be out of screen,
1088
+ // for example if the node is very tall, or if the markup view overflows horizontally
1089
+ // and the Range is located near the right end of the node container.
1090
+ scroll : ! selectionSearchQuery ,
1091
+ } )
1081
1092
. then ( ( ) => {
1082
1093
// We could be destroyed by now.
1083
1094
if ( this . _destroyed ) {
@@ -1088,7 +1099,7 @@ MarkupView.prototype = {
1088
1099
const container = this . getContainer ( selection . nodeFront , slotted ) ;
1089
1100
this . _markContainerAsSelected ( container ) ;
1090
1101
this . _updateSearchResultsHighlightingInSelectedNode (
1091
- selection . getSearchQuery ( )
1102
+ selectionSearchQuery
1092
1103
) ;
1093
1104
1094
1105
// Make sure the new selection is navigated to.
@@ -1111,6 +1122,20 @@ MarkupView.prototype = {
1111
1122
return highlights . get ( highlightName ) ;
1112
1123
} ,
1113
1124
1125
+ /**
1126
+ * @returns {nsISelectionController }
1127
+ */
1128
+ _getSelectionController ( ) {
1129
+ if ( ! this . _selectionController ) {
1130
+ // QueryInterface can be expensive, so cache the controller.
1131
+ this . _selectionController = this . win . docShell
1132
+ . QueryInterface ( Ci . nsIInterfaceRequestor )
1133
+ . getInterface ( Ci . nsISelectionDisplay )
1134
+ . QueryInterface ( Ci . nsISelectionController ) ;
1135
+ }
1136
+ return this . _selectionController ;
1137
+ } ,
1138
+
1114
1139
/**
1115
1140
* Highlight search results in the markup view.
1116
1141
*
@@ -1136,6 +1161,8 @@ MarkupView.prototype = {
1136
1161
searchQuery = searchQuery . toLowerCase ( ) ;
1137
1162
const searchQueryLength = searchQuery . length ;
1138
1163
let currentNode = treeWalker . nextNode ( ) ;
1164
+ let scrolled = false ;
1165
+
1139
1166
while ( currentNode ) {
1140
1167
const text = currentNode . textContent . toLowerCase ( ) ;
1141
1168
let startPos = 0 ;
@@ -1152,10 +1179,47 @@ MarkupView.prototype = {
1152
1179
searchHighlight . add ( range ) ;
1153
1180
1154
1181
startPos = index + searchQuery . length ;
1182
+
1183
+ // We want to scroll the first matching range into view
1184
+ if ( ! scrolled ) {
1185
+ // We want to take advantage of nsISelectionController.scrollSelectionIntoView,
1186
+ // so we need to put the range in the selection. That's fine to do here because
1187
+ // in this situation the user shouldn't have any text selected
1188
+ const selection = this . win . getSelection ( ) ;
1189
+ selection . removeAllRanges ( ) ;
1190
+ selection . addRange ( range ) ;
1191
+
1192
+ const selectionController = this . _getSelectionController ( ) ;
1193
+ selectionController . scrollSelectionIntoView (
1194
+ selectionController . SELECTION_NORMAL ,
1195
+ selectionController . SELECTION_ON ,
1196
+ selectionController . SCROLL_SYNCHRONOUS |
1197
+ selectionController . SCROLL_VERTICAL_CENTER
1198
+ ) ;
1199
+ selection . removeAllRanges ( ) ;
1200
+ scrolled = true ;
1201
+ }
1155
1202
}
1156
1203
1157
1204
currentNode = treeWalker . nextNode ( ) ;
1158
1205
}
1206
+
1207
+ // It can happen that we didn't find a Range for a search result (e.g. if the matching
1208
+ // string is in a cropped attribute). In such case, go back to scroll the container
1209
+ // into view.
1210
+ if ( ! scrolled ) {
1211
+ const container = this . getContainer (
1212
+ this . inspector . selection . nodeFront ,
1213
+ this . inspector . selection . isSlotted ( )
1214
+ ) ;
1215
+ scrollIntoViewIfNeeded (
1216
+ container . editor . elt ,
1217
+ // centered
1218
+ true ,
1219
+ // smoothScroll
1220
+ false
1221
+ ) ;
1222
+ }
1159
1223
} ,
1160
1224
1161
1225
/**
@@ -1770,23 +1834,38 @@ MarkupView.prototype = {
1770
1834
/**
1771
1835
* Make sure the given node's parents are expanded and the
1772
1836
* node is scrolled on to screen.
1773
- */
1774
- showNode ( node , { centered = true , slotted, smoothScroll = false } = { } ) {
1775
- if ( slotted && ! this . hasContainer ( node , slotted ) ) {
1837
+ *
1838
+ * @param {NodeFront } nodeFront
1839
+ * @param {Object } options
1840
+ * @param {Boolean } options.centered
1841
+ * @param {Boolean } options.scroll
1842
+ * @param {Boolean } options.slotted
1843
+ * @param {Boolean } options.smoothScroll
1844
+ * @returns
1845
+ */
1846
+ showNode (
1847
+ nodeFront ,
1848
+ { centered = true , scroll = true , slotted, smoothScroll = false } = { }
1849
+ ) {
1850
+ if ( slotted && ! this . hasContainer ( nodeFront , slotted ) ) {
1776
1851
throw new Error ( "Tried to show a slotted node not previously imported" ) ;
1777
1852
} else {
1778
- this . _ensureNodeImported ( node ) ;
1853
+ this . _ensureNodeImported ( nodeFront ) ;
1779
1854
}
1780
1855
1781
1856
return this . _waitForChildren ( )
1782
1857
. then ( ( ) => {
1783
1858
if ( this . _destroyed ) {
1784
1859
return Promise . reject ( "markupview destroyed" ) ;
1785
1860
}
1786
- return this . _ensureVisible ( node ) ;
1861
+ return this . _ensureVisible ( nodeFront ) ;
1787
1862
} )
1788
1863
. then ( ( ) => {
1789
- const container = this . getContainer ( node , slotted ) ;
1864
+ if ( ! scroll ) {
1865
+ return ;
1866
+ }
1867
+
1868
+ const container = this . getContainer ( nodeFront , slotted ) ;
1790
1869
scrollIntoViewIfNeeded ( container . editor . elt , centered , smoothScroll ) ;
1791
1870
} , this . _handleRejectionIfNotDestroyed ) ;
1792
1871
} ,
@@ -2642,6 +2721,7 @@ MarkupView.prototype = {
2642
2721
this . _elt . innerHTML = "" ;
2643
2722
this . _elt = null ;
2644
2723
2724
+ this . _selectionController = null ;
2645
2725
this . controllerWindow = null ;
2646
2726
this . doc = null ;
2647
2727
this . highlighters = null ;
0 commit comments