Skip to content

Instantly share code, notes, and snippets.

@pintassilgo
Created November 10, 2017 16:10
Show Gist options
  • Save pintassilgo/c107021740af9c6914485eb3c2bf95fa to your computer and use it in GitHub Desktop.
Save pintassilgo/c107021740af9c6914485eb3c2bf95fa to your computer and use it in GitHub Desktop.
// ==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