Skip to content

[Needs rebase] Move regex searches to a web worker to stop the browser from hanging #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions content_scripts/searchWorker.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
self.addEventListener "message", (event) ->
text = event.data.text
regex = event.data.regex

regexMatches = text.match regex

postData =
regexMatches: regexMatches
self.postMessage postData
, false
110 changes: 98 additions & 12 deletions content_scripts/vimium_frontend.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ initializeOnDomReady = ->
# Tell the background page we're in the dom ready state.
chrome.runtime.connect({ name: "domReady" })

RegexWorker.init()

# This is a little hacky but sometimes the size wasn't available on domReady?
registerFrameIfSizeAvailable = (is_top) ->
if (innerWidth != undefined && innerWidth != 0 && innerHeight != undefined && innerHeight != 0)
Expand Down Expand Up @@ -516,8 +518,84 @@ exitInsertMode = (target) ->

isInsertMode = -> insertModeLock != null

# set up a web worker to asynchronously peform regexp searches
RegexWorker =
initialised: false
searchWorker: undefined
callback: undefined
workerDataUrl: undefined

init: ->
# content scripts cannot create web workers from chrome-extension: URLs (chromium bug 357664).
# Based on the advice from http://stackoverflow.com/a/22719772, we can load the script via xmlhttprequest
# and create a data: URI from it, which we can load.
workerUrl = chrome.extension.getURL "content_scripts/searchWorker.js"
try
xmlhttp = new XMLHttpRequest()
xmlhttp.responseType = "blob"
xmlhttp.onload = (->
@workerDataUrl = URL.createObjectURL xmlhttp.response
).bind RegexWorker
xmlhttp.open "GET", workerUrl
xmlhttp.send()
catch
@workerDataUrl = workerUrl
@initialised = true
deinit: ->
@deinitWorker()
@initialised = false

initWorker: ->
try
@searchWorker = new Worker @workerDataUrl
@searchWorker.onmessage = @searchWorkerHandler
catch # Loading the worker has failed for some reason, still perform the search
@searchWorker = # Make an imitation Worker
onMessage: @searchWorkerHandler
terminate: ->

postMessage: (postData) -> # Do the Worker's job, but synchronously
_event =
data:
text: postData.text
regex: postData.regex
regexMatches: postData.text.match postData.regex
@onMessage _event

deinitWorker: ->
return unless @searchWorker
delete @searchWorker.onmessage
@searchWorker.terminate()
@searchWorker = undefined

searchWorkerHandler: (event) ->
text = event.data.text
regex = event.data.regex
regexMatches = event.data.regexMatches

findModeQuery.regexMatches = regexMatches
findModeQuery.activeRegexIndex = 0

RegexWorker.callback regexMatches

matchAsync: (text, regex, callback) ->
# Reset worker, stop any currently running searches
@deinitWorker()
@initWorker()

if callback
@callback = (regexMatches) ->
callback regexMatches, text, regex
else
@callback = undefined
postData =
text: text
regex: regex
@searchWorker.postMessage postData


# should be called whenever rawQuery is modified.
updateFindModeQuery = ->
updateFindModeQueryAsync = (callback) ->
# the query can be treated differently (e.g. as a plain string versus regex depending on the presence of
# escape sequences. '\' is the escape character and needs to be escaped itself to be used as a normal
# character. here we grep for the relevant escape sequences.
Expand Down Expand Up @@ -552,14 +630,17 @@ updateFindModeQuery = ->
return
# innerText will not return the text of hidden elements, and strip out tags while preserving newlines
text = document.body.innerText
findModeQuery.regexMatches = text.match(pattern)
findModeQuery.activeRegexIndex = 0
RegexWorker.matchAsync text, pattern, callback

else # Normal string, we can fall straight through to callback
callback()

handleKeyCharForFindMode = (keyChar) ->
findModeQuery.rawQuery += keyChar
updateFindModeQuery()
performFindInPlace()
showFindModeHUDForQuery()
showFindModeHUDForQuery true
updateFindModeQueryAsync ->
performFindInPlace()
showFindModeHUDForQuery false

handleEscapeForFindMode = ->
exitFindMode()
Expand All @@ -579,9 +660,10 @@ handleDeleteForFindMode = ->
performFindInPlace()
else
findModeQuery.rawQuery = findModeQuery.rawQuery.substring(0, findModeQuery.rawQuery.length - 1)
updateFindModeQuery()
performFindInPlace()
showFindModeHUDForQuery()
showFindModeHUDForQuery true
updateFindModeQueryAsync ->
performFindInPlace()
showFindModeHUDForQuery false

# <esc> sends us into insert mode if possible, but <cr> does not.
# <esc> corresponds approximately to 'nevermind, I have found it already' while <cr> means 'I want to save
Expand Down Expand Up @@ -675,8 +757,12 @@ findAndFocus = (backwards) ->
mostRecentQuery = settings.get("findModeRawQuery") || ""
if (mostRecentQuery != findModeQuery.rawQuery)
findModeQuery.rawQuery = mostRecentQuery
updateFindModeQuery()
updateFindModeQueryAsync ->
jumpToMatch backwards
else
jumpToMatch backwards

jumpToMatch = (backwards) ->
query =
if findModeQuery.isRegex
getNextQueryFromRegexMatches(if backwards then -1 else 1)
Expand Down Expand Up @@ -813,8 +899,8 @@ window.goNext = ->
nextStrings = nextPatterns.split(",").filter( (s) -> s.trim().length )
findAndFollowRel("next") || findAndFollowLink(nextStrings)

showFindModeHUDForQuery = ->
if (findModeQueryHasResults || findModeQuery.parsedQuery.length == 0)
showFindModeHUDForQuery = (searchOngoing) ->
if (searchOngoing || findModeQueryHasResults || findModeQuery.parsedQuery.length == 0)
HUD.show("/" + findModeQuery.rawQuery)
else
HUD.show("/" + findModeQuery.rawQuery + " (No Matches)")
Expand Down
3 changes: 3 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
"run_at": "document_start"
}
],
"web_accessible_resources": [
"content_scripts/searchWorker.js"
],
"browser_action": {
"default_icon": "icons/browser_action_disabled.png",
"default_popup": "pages/popup.html"
Expand Down
3 changes: 3 additions & 0 deletions tests/dom_tests/chrome.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ root.chrome = {
sendMessage: ->
getManifest: ->
}
extension: {
getURL: (relativeUrl) -> relativeUrl
}
}