1 { createElement } = require 'utils'
3 { classes: Cc, interfaces: Ci } = Components
5 CONTAINER_ID = 'VimFxFindContainer'
7 DIRECTION_BACKWARDS = 1
9 HTMLDocument = Ci.nsIDOMHTMLDocument
11 # Create and inserts into DOM find controls and handlers
12 injectFind = (document, cb) ->
13 # Find only works on HTML documents, not XUL documents
14 if document not instanceof HTMLDocument
17 # First get starting range - it might begin where last search ended
18 startFindRng = getStartFindRng(document.defaultView)
20 # Clean up just in case...
23 # Create container and insert a text input into it
24 [container, input] = createFindContainer(document)
26 # Call back in new input
27 input.addEventListener 'input', (event) ->
28 result = cb(input.value, startFindRng)
29 method = if result then 'remove' else 'add'
30 input.classList[method]('VimFxNotFound')
32 # Call back on (Shift)-Enter with proper direction
33 input.addEventListener 'keypress', (event) ->
34 if event.keyCode == event.DOM_VK_RETURN
35 focusSelection(document, Ci.nsISelectionController.SELECTION_FIND)
36 removeFind(document, false)
38 document.documentElement.appendChild(container)
41 # Removes find controls from DOM
42 removeFind = (document, clear = true) ->
43 document.getElementById(CONTAINER_ID)?.remove()
46 clearSelection(document.defaultView)
48 getStartFindRng = (window) ->
49 controller = getController(window)
50 for selectionType in [Ci.nsISelectionController.SELECTION_NORMAL, Ci.nsISelectionController.SELECTION_FIND]
51 selection = controller.getSelection(selectionType)
52 if selection.rangeCount > 0
53 rng = selection.getRangeAt(0)
55 rng.selectNode(rng.commonAncestorContainer)
56 if rng.commonAncestorContainer != window.document
60 focusSelection = (document, selectionType) ->
61 if controller = getController(document.defaultView)
62 if selection = controller.getSelection(selectionType)
63 if selection.rangeCount > 0
64 # commonAncestorContainer is a Text node, we need to get the tag that wraps it
65 element = selection.getRangeAt(0).commonAncestorContainer?.parentNode
66 if element != document and element.focus
69 createFindContainer = (document) ->
70 container = createElement document, 'div',
73 input = createElement document, 'input',
77 container.appendChild(input)
79 return [container, input]
81 clearSelection = (window, selectionType = Ci.nsISelectionController.SELECTION_FIND) ->
82 for frame in window.frames
85 if controller = getController(window)
86 controller.getSelection(selectionType).removeAllRanges()
88 findFactory = (selectionType) ->
89 finder = Cc['@mozilla.org/embedcomp/rangefind;1']
91 .QueryInterface(Components.interfaces.nsIFind)
93 return (window, findStr, findRng = null, direction = DIRECTION_FORWARDS, focus = false) ->
94 # `find` will also recursively search in all frames. `innerFind` does the work:
95 # searches, selects, scrolls, and optionally reaches into frames
96 innerFind = (window) ->
97 if controller = getController(window)
98 finder.findBackwards = (direction == DIRECTION_BACKWARDS)
99 finder.caseSensitive = (findStr != findStr.toLowerCase())
101 searchRange = window.document.createRange()
102 searchRange.selectNodeContents(window.document.body)
104 if findRng and findRng.commonAncestorContainer.ownerDocument == window.document
105 if finder.findBackwards
106 searchRange.setEnd(findRng.startContainer, findRng.startOffset)
108 searchRange.setStart(findRng.endContainer, findRng.endOffset)
110 (startPt = searchRange.cloneRange()).collapse(true)
111 (endPt = searchRange.cloneRange()).collapse(false)
113 if finder.findBackwards
114 [startPt, endPt] = [endPt, startPt]
116 if range = finder.Find(findStr, searchRange, startPt, endPt)
117 controller.getSelection(selectionType).addRange(range)
118 controller.scrollSelectionIntoView(selectionType, range, Ci.nsISelectionController.SCROLL_CENTER_VERTICALLY)
120 focusSelection(window.document, selectionType)
124 clearSelection(window, selectionType)
126 if findStr.length > 0
127 # Get all embedded windows/frames including the passed window
128 wnds = getAllWindows(window)
129 # In backward searching reverse windows mode so that
130 # it starts off the deepest iframe
131 if finder.findBackwards
134 # First search in the same window to which current `findRng` belongs
135 if rngWindow = findRng?.commonAncestorContainer.ownerDocument.defaultView
136 wnds = cycleToItem(wnds, rngWindow)
139 if range = innerFind(w)
142 return if findStr.length == 0 then true else range
144 highlightFactory = (selectionType) ->
145 finder = Cc['@mozilla.org/embedcomp/rangefind;1']
147 .QueryInterface(Components.interfaces.nsIFind)
149 return (window, findStr) ->
151 finder.findBackwards = false
152 finder.caseSensitive = (findStr != findStr.toLowerCase())
154 innerHighlight = (window) ->
155 if controller = getController(window)
156 searchRange = window.document.createRange()
157 searchRange.selectNodeContents(window.document.body)
159 (startPt = searchRange.cloneRange()).collapse(true)
160 (endPt = searchRange.cloneRange()).collapse(false)
162 selection = controller.getSelection(selectionType)
163 while range = finder.Find(findStr, searchRange, startPt, endPt)
164 selection.addRange(range)
166 (startPt = range.cloneRange()).collapse(false)
168 # Highlight in iframes
169 for frame in window.frames
170 innerHighlight(frame)
172 clearSelection(window, selectionType)
174 if findStr.length > 0
175 innerHighlight(window)
177 return if findStr.length == 0 then true else matchesCount
179 getController = (window) ->
180 if not window.innerWidth or not window.innerHeight
183 return window.QueryInterface(Ci.nsIInterfaceRequestor)
184 .getInterface(Ci.nsIWebNavigation)
185 .QueryInterface(Ci.nsIInterfaceRequestor)
186 .getInterface(Ci.nsISelectionDisplay)
187 .QueryInterface(Ci.nsISelectionController)
189 # Returns flat list of frmaes within provided window
190 getAllWindows = (window) ->
192 for frame in window.frames
193 result = result.concat(getAllWindows(frame))
197 cycleToItem = (array, item) ->
198 if item and array.indexOf(item) != -1
199 while array[0] != item
200 array.push(array.shift())
204 exports.injectFind = injectFind
205 exports.removeFind = removeFind
206 exports.find = findFactory(Ci.nsISelectionController.SELECTION_FIND)
207 exports.highlight = highlightFactory(Ci.nsISelectionController.SELECTION_FIND)
208 exports.DIRECTION_FORWARDS = DIRECTION_FORWARDS
209 exports.DIRECTION_BACKWARDS = DIRECTION_BACKWARDS