Created
November 10, 2017 16:10
-
-
Save pintassilgo/c107021740af9c6914485eb3c2bf95fa to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name enterSelects.uc.js | |
// @include main | |
// ==/UserScript== | |
(function () { | |
/* | |
Cu.import("resource://gre/modules/PlacesUtils.jsm", this); | |
Cu.import("resource://gre/modules/Services.jsm", this);*/ | |
"use strict"; | |
// Keep a reference to unloaders that need to run | |
const unloaders = []; | |
/** | |
* Run all unloaders and clean up | |
*/ | |
function runUnloaders() { | |
unloaders.slice().forEach(unloader => unloader()); | |
unloaders.length = 0; | |
} | |
/** | |
* Save callbacks to run when unloading. Optionally scope the callback to a | |
* container, e.g., window. Provide a way to run all the callbacks. | |
* | |
* @usage unload(): Run all callbacks and release them. | |
* | |
* @usage unload(callback): Add a callback to run on unload. | |
* @param [function] callback: 0-parameter function to call on unload. | |
* @return [function]: A 0-parameter function that undoes adding the callback. | |
* | |
* @usage unload(callback, container) Add a scoped callback to run on unload. | |
* @param [function] callback: 0-parameter function to call on unload. | |
* @param [node] container: Remove the callback when this container unloads. | |
* @return [function]: A 0-parameter function that undoes adding the callback. | |
*/ | |
let unload = function(callback) { | |
// Calling with no arguments runs all the unloader callbacks | |
if (callback == null) { | |
runUnloaders(); | |
return; | |
} | |
// Wrap the callback in a function that ignores failures | |
let unloader = function() { | |
try { | |
callback(); | |
} | |
catch(ex) {} | |
} | |
// Save the unloader and provide a way to remove it | |
unloaders.push(unloader); | |
let removeUnloader = function() { | |
let index = unloaders.indexOf(unloader); | |
if (index != -1) | |
unloaders.splice(index, 1); | |
} | |
// The callback is bound to the lifetime of the container if we have one | |
if (window != null) { | |
// Remove the unloader when the container unloads | |
addEventListener("unload", removeUnloader, false); | |
// Wrap the callback to additionally remove the unload listener | |
let origCallback = callback; | |
callback = function() { | |
removeEventListener("unload", removeUnloader, false); | |
origCallback(); | |
} | |
} | |
return removeUnloader; | |
} | |
// Make sure to run the unloaders when unloading | |
//█require("sdk/system/unload").when(runUnloaders); | |
//const {unload} = require("./unload+"); | |
/** | |
* Helper that adds event listeners and remembers to remove on unload | |
*/ | |
let listen = function(node, event, func, capture) { | |
// Default to use capture | |
if (capture == null) | |
capture = true; | |
node.addEventListener(event, func, capture); | |
function undoListen() { | |
node.removeEventListener(event, func, capture); | |
} | |
// Undo the listener on unload and provide a way to undo everything | |
let undoUnload = unload(undoListen, window); | |
return function() { | |
undoListen(); | |
undoUnload(); | |
}; | |
} | |
/*const {listen} = require("./listen"); | |
const {unload} = require("./unload+");*/ | |
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; | |
// Call a function after waiting a little bit | |
function defer(callback, delay) { | |
let timer = setTimeout(function() { | |
stopTimer(); | |
callback(); | |
}, delay); | |
// Provide a way to stop an active timer | |
function stopTimer() { | |
if (timer == null) | |
return; | |
clearTimeout(timer); | |
timer = null; | |
unUnload(); | |
} | |
// Make sure to stop the timer when unloading | |
let unUnload = unload(stopTimer, window); | |
// Give the caller a way to cancel the timer | |
return stopTimer; | |
} | |
// Replace a value with another value or a function of the original value | |
function change(obj, prop, val) { | |
let orig = obj[prop]; | |
let newVal = typeof val == "function" ? val(orig) : val; | |
// Replace simple properties by assigning the new value | |
if (Object.getOwnPropertyDescriptor(obj, prop)) { | |
obj[prop] = newVal; | |
unload(_ => obj[prop] = orig, window); | |
} | |
// Define properties on the instance to avoid read-only access exceptions | |
else { | |
Object.defineProperty(obj, prop, { | |
configurable: true, | |
value: newVal, | |
writable: true | |
}); | |
unload(function() { | |
delete obj[prop]; | |
}, window); | |
} | |
} | |
// Create a XUL node | |
function createNode(nodeName) { | |
return document.createElementNS(XUL_NS, nodeName); | |
} | |
let watchWindows = function(callback) { | |
var unloaded = false; | |
unload(_ => unloaded = true); | |
// Wrap the callback in a function that ignores failures | |
function watcher(window) { | |
try { | |
// Now that the window has loaded, only handle browser windows | |
let {documentElement} = window.document; | |
if (documentElement.getAttribute("windowtype") == "navigator:browser") | |
callback(window); | |
} | |
catch(ex) {} | |
} | |
// Wait for the window to finish loading before running the callback | |
function runOnLoad(window) { | |
// Listen for one load event before checking the window type | |
window.addEventListener("load", function runOnce() { | |
window.removeEventListener("load", runOnce, false); | |
// Only run if the extension has not shutdown | |
if (!unloaded) | |
watcher(window); | |
}, false); | |
} | |
// Add functionality to existing windows | |
let windows = Services.wm.getEnumerator(null); | |
while (windows.hasMoreElements()) { | |
// Only run the watcher immediately if the window is completely loaded | |
let window = windows.getNext(); | |
if (window.document.readyState == "complete") | |
watcher(window); | |
// Wait for the window to load before continuing | |
else | |
runOnLoad(window); | |
} | |
// Watch for new browser windows opening then wait for it to load | |
function windowWatcher(subject, topic) { | |
if (topic == "domwindowopened") | |
runOnLoad(subject); | |
} | |
Services.ww.registerNotification(windowWatcher); | |
// Make sure to stop watching for windows if we're unloading | |
unload(_ => Services.ww.unregisterNotification(windowWatcher)); | |
} | |
// Milliseconds to wait for results after pressing enter | |
const MAX_WAIT_FOR_RESULTS = 350; | |
const cachedKeywords = new Map(); | |
function isKeyword(keyword) { | |
// Immediately handle the keyword if we've checked it before | |
if (cachedKeywords.has(keyword)) { | |
return cachedKeywords.get(keyword); | |
} | |
// Check for search engine keyword and remember if it is | |
if (Services.search.getEngineByAlias(keyword) != null) { | |
cachedKeywords.set(keyword, true); | |
return true; | |
} | |
// Asynchronously check for bookmark keywords | |
PlacesUtils.keywords.fetch(keyword).then(result => { | |
cachedKeywords.set(keyword, !!result); | |
}); | |
// Just return false immediately and correctly handle in the future | |
return false; | |
} | |
//let {change, defer, listen} = makeWindowHelpers(window); | |
let {gURLBar} = window; | |
let {popup} = gURLBar; | |
// Remember if the next result should be selected | |
let selectNext = false; | |
// Remember values to restore them if necessary | |
let origSearch = ""; | |
let origValue = ""; | |
// Starting with Firefox 48 Beta 3, the only behavior is unified complete, so | |
// automatically select the 1th result (default search is 0th result) | |
let targetIndex = 1; | |
// Figure out what part the user actually typed | |
function getTyped() { | |
let {value} = gURLBar; | |
if (gURLBar.selectionEnd == value.length) | |
value = value.slice(0, gURLBar.selectionStart); | |
return value.trim(); | |
} | |
// Determine if the location bar frontend will take care of the input | |
function willHandle(search) { | |
// Potentially it's a url if there's no spaces | |
if (search.match(/ /) == null) { | |
try { | |
// Quit early if the input is already a URI | |
return Services.io.newURI(search, null, null); | |
} | |
catch(ex) {} | |
try { | |
// Quit early if the input is domain-like (e.g., site.com/page) | |
return Services.eTLD.getBaseDomainFromHost(search); | |
} | |
catch(ex) {} | |
} | |
// Check if the first word is a keyword (search or bookmark) | |
if (isKeyword(search.split(/\s+/)[0])) { | |
return true; | |
} | |
return false; | |
} | |
// Detect when results are added to autoselect the first one | |
change(popup, "_appendCurrentResult", function(orig) { | |
return function() { | |
// Run the original first to get results added | |
orig.apply(this, arguments); | |
// Don't bother if something is already selected | |
if (popup.selectedIndex >= targetIndex) | |
return; | |
// Make sure there's results | |
if (popup._matchCount == 0) | |
return; | |
// Don't auto-select if we have a user-typed url | |
let currentSearch = getTyped(); | |
if (willHandle(currentSearch)) | |
return; | |
// Store these to resore if necessary when moving out of the popup | |
origSearch = currentSearch; | |
origValue = gURLBar.value; | |
// We passed all the checks, so pretend the user has the first result | |
// selected, so this causes the UI to show the selection style | |
// xiao, coloquei esse if pra poder abrir url com espaço | |
if (popup.richlistbox.children[targetIndex].getAttribute('type') !== 'searchengine') | |
popup.selectedIndex = targetIndex; | |
if (selectNext) { | |
selectNext = false; | |
defer(_ => gURLBar.controller.handleEnter(true)); | |
} | |
}; | |
}); | |
// Detect the user selecting results from the list | |
listen(gURLBar, "keydown", function(event) { | |
switch (event.keyCode) { | |
// For horizontal movement keys, unselect the first item to allow editing | |
case event.DOM_VK_LEFT: | |
case event.DOM_VK_RIGHT: | |
case event.DOM_VK_HOME: | |
popup.selectedIndex = -1; | |
return; | |
// For vertical movement keys, restore the inline completion if necessary | |
case event.DOM_VK_UP: | |
case event.DOM_VK_DOWN: | |
case event.DOM_VK_PAGE_UP: | |
case event.DOM_VK_PAGE_DOWN: | |
// Wait for the actual movement to finish before checking | |
defer(function() { | |
// If we have nothing selected in the popup, restore the completion | |
if (popup.selectedIndex == -1 && gURLBar.popupOpen) { | |
gURLBar.textValue = origValue; | |
gURLBar.selectionStart = origSearch.length; | |
} | |
}); | |
return | |
case event.DOM_VK_TAB: | |
//xiao o defer acima estava aqui, botei acima para as setas nao terem o mesmo comportamento do tab. e adicionei o if abaixo que é pra poder adicionar pathname no endereço facilmente, assim como no fx47- | |
if (popup.selectedIndex == 1 && gURLBar.value != gURLBar.popup.richlistbox.children[1].getAttribute('url') && new RegExp(/^(https?:\/\/(www\.)?)?/.source + gURLBar.value.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')).test(gURLBar.popup.richlistbox.children[1].getAttribute('url')) && gURLBar.value != gURLBar.popup.richlistbox.children[1].getAttribute('url').match(/(\w*:\/\/)?.+\..+?(\/|$)/)[0]) { | |
gURLBar.value = gURLBar.popup.richlistbox.children[1].getAttribute('url').match(/(\w*:\/\/)?.+\..+?(\/|$)/)[0]; | |
popup.selectedIndex = gURLBar.value == gURLBar.popup.richlistbox.children[1].getAttribute('url') ? 1 : -1; | |
event.preventDefault(); | |
} else if (popup.selectedIndex == -1) { | |
popup.selectedIndex = 1; | |
gURLBar.value = gURLBar.popup.richlistbox.children[1].getAttribute('url'); | |
event.preventDefault(); | |
} else if (popup.selectedIndex == 1 && gURLBar.value != gURLBar.popup.richlistbox.children[1].getAttribute('url')) { | |
gURLBar.value = gURLBar.popup.richlistbox.children[1].getAttribute('url'); | |
event.preventDefault(); | |
} | |
break; | |
// We're interested in handling enter (return) | |
case event.DOM_VK_RETURN: | |
// Ignore special key combinations | |
if (event.shiftKey || event.ctrlKey || event.metaKey) | |
return; | |
// Detect if there's no results so yet, so we're waiting for more | |
let {controller} = gURLBar; | |
let {matchCount, searchStatus} = controller; | |
if (matchCount == 0 && searchStatus <= controller.STATUS_SEARCHING) { | |
// If the location bar will handle the search, don't bother waiting | |
let enteredSearch = getTyped(); | |
if (willHandle(enteredSearch)) | |
return; | |
// Stop the location bar from handling search because we want results | |
event.preventDefault(); | |
// Remember that the next result will be selected | |
selectNext = true; | |
// In-case there are no results after a short wait, just load search | |
defer(function() { | |
if (enteredSearch == getTyped() && controller.matchCount == 0) { | |
selectNext = false; | |
gURLBar.onTextEntered(); | |
} | |
// Wait a shorter amount of time the more the user types | |
}, MAX_WAIT_FOR_RESULTS / enteredSearch.length); | |
// Do nothing now until more results are appended | |
return; | |
} | |
// For the auto-selected first result, act as if the user pressed down | |
// to select it so that 1) the urlbar will have the correct url for the | |
// enter handler to load and 2) the adaptive learning code-path will | |
// correctly associate the user's input to the selected popup item. | |
if (popup.selectedIndex == targetIndex) { | |
popup.selectedIndex = targetIndex - 1; | |
controller.handleKeyNavigation(event.DOM_VK_DOWN); | |
} | |
break; | |
} | |
}, false); | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment