aminomancer / uc.css.js

A dark indigo CSS theme for Firefox and a large collection of privileged scripts to add new buttons, menus, and behaviors and eliminate nuisances. The theme is similar to other userChrome stylesheets, but it's intended for use with an autoconfig loader like fx-autoconfig, since it uses JavaScript to implement its more functional features.
Other
329 stars 27 forks source link

Smart keywords, "window.location.hostname" and new tabs #42

Open ericpa06 opened 2 years ago

ericpa06 commented 2 years ago

Sorry if the title doesn't make much sense, I didn't know how to explain in a short manner. A better explanation would be, Firefox has the smart keyword thing, where you can associate some shortcuts and there are several codes you can apply to it. Like for instance, this one here, which searches the current site you are in on Google:

javascript:location.href=('http://www.google.com/search?q=site:'+window.location.hostname+'%20%s')

image_3298

https://user-images.githubusercontent.com/6937720/167739464-76f2a1f8-f4c4-41e4-b54d-8e6ca4bea82f.mp4

So for instance, if I'm on hackernews, and press "s" [my search term] + enter, the command automatically does a google search on that page.

The thing that is that, if instead "enter", I pressed "shit+enter", to do the same thing I wanted to do previously, but instead opening the that search on a new tab, it doesn't work, it ignores the "window.location.hostname" part, so therefore you end up with something like this:

https://user-images.githubusercontent.com/6937720/167827961-9e9c75d0-0051-48b2-88b4-34489013e2e6.mp4

I couldn't find any bugzilla report on this, actually, I'm not even sure if is a bug to be fair, but is something that it might break some people workflow. Anyway, I was wondering if it would be possible to make, sorta firefox use that "window.location.hostname" reference even when pressing shit+enter so that you can perform that search and open it on a new tab.

Or maybe you know some other workaround through this issue, like through some editing on the very smartkeywords that I'm not seeing. I would also be interesting on that.

Also, as always sorry to bother you. And thanks in advance for any help or advice.

aminomancer commented 2 years ago

Yeah I see what you mean. It's a good point, this is a very unfortunate limitation of the javascript scheme feature. So, it's not quite ignoring the hostname part, more like the hostname is just evaluating to an empty string. So that javascript location.href=('http://www.google.com/search?q=site:'+window.location.hostname+'%20%s') is basically saying to combine the string http://www.google.com/search?q=site: with the value of window.location.hostname and then append the string %20%s which is a space + the search string.

The problem is that the target browser (a new tab) is empty at the time this property's value is evaluated. Instead of checking the current browser's location, it's creating a new tab first, then loading the bookmark URL into it, which is parsing the javascript encoded in the URL, and that javascript is then checking the current browser's location — from within the new tab, where it's running. So because the javascript is loaded inside the context of a new tab instead of the current browser, the window refers to a completely different object with a different location property. Not really a simple way around this because the whole "opening a new tab" thing is way upstream of the execution of the JS code inside the bookmark URL. The sequence basically goes

  1. Hit Enter
  2. Open new tab
  3. Load parsed URI in the new browsing context
  4. Parsed URI is of javascript scheme...
  5. Evaluate the "path" of the URL string in the new browser
  6. That javascript evaluation references window.location.hostname which, from this execution context, refers to the empty browser we just opened. So it returns "" (empty string)
  7. The javascript further sets location.href equal to 'http://www.google.com/search?q=site:'+''+'%20%s'
  8. We therefore load http://www.google.com/search?q=site:%20%s which means the google input field should say site: {search string}

When you just hit Enter, you're basically executing the javascript in the current selected tab. So location.hostname returns the hostname for the current browser's doc. But when you use a modifier key, you're causing it to open the result in a new tab/window, which means a new browser. And the javascript URL is evaluated in the browsing context associated with that new browser. So at the time of execution it's gonna be an empty string I believe.

There might be solutions or workarounds though. It might seem like we could just change the behavior in some way so that these URLs are parsed in the parent process or something. The main issue I see is that we can't make assumptions about where the execution should happen. Even in just this one example, we need to execute inside the browser in which the content should load, because that's how the google search is initiated. See where it says location.href=...? That's how it's launching google. It just means change the URL to the following string.

But we can't have it both ways. For that to successfully change the URL of the new browser, it needs to be executed inside the new browser. But that means when we query location.hostname we're going to get the hostname for the new browser. So you can see the problem. We're asking for information about the current browser's location and then we're trying to use that information to set a new browser's location. So those would need to refer to the location property of 2 different window objects. But that's not possible from within the very restrictive context in which these javascript URLs run.

Technically I think it's possible to write a keyword bookmark in such a way that it will open a new tab, but that wouldn't have anything to do with whether a modifier key is pressed. It would just be the normal behavior of the bookmark. And that would be done by using something like javascript:window.open('http://www.google.com/search?q=site:'+window.location.hostname+'%20%s', '_blank'); location.href = location.href;. Unfortunately that reloads the current tab in addition to opening a new tab, which sucks. I don't think that's avoidable since this relies on a new URI being loaded in the first place.

Overall I just don't think the keyword bookmark feature is very practical for this kind of feature. I think an extension would have a much easier time doing something like this, though address bar extensions are quite rare. It would also be possible to make something like this with autoconfig, but it would need to be incredibly sophisticated to integrate with the urlbar. We'd need to basically let the user create a "bookmark" whose only content is parent-process JS instructions. That way we can create all the parameters before even making a new tab. But that would be quite a project. A new urlbar result provider, a new UI for creating templates, etc. I don't think I'll have enough down time to produce and maintain something so massive by myself.

I'll show this to a few people and see what they think. This is a pretty open-ended idea so I'm sure there are lots of possibilities I'm not thinking of.

ericpa06 commented 2 years ago

Thank you so much for your well-explained and well-defined spot on explanation.

aminomancer commented 2 years ago

Hey let me know if this new script does what you're looking for. You can use the same keyword bookmark, but just replace the URL field with this:

ucjs:let where = gURLBar._whereToOpen(event); openWebLinkIn("https://www.google.com/search?q=site:" + gBrowser.currentURI.host + " %{searchString}", where);
ericpa06 commented 2 years ago

Man, you are a LEGEND! Thank you so much!!! It worked flawlessly :)

aminomancer commented 2 years ago

Great 😊

aminomancer commented 2 years ago

Here try this instead. I improved the keyword code so it should feel more professional

ucjs:let where = gURLBar._whereToOpen(event); let typedValue = "%{searchString}"; let {host} = new URL(gBrowser.currentURI.spec); let pathString = typedValue.trim().length ? "/search?client=firefox-b-1-d&q=" : ""; let hostString = pathString && host && ["https", "http"].includes(gBrowser.currentURI.scheme) ? "site:" + host + " " : ""; gURLBar.handleRevert(); gURLBar.blur(); openWebLinkIn("https://www.google.com" + pathString + hostString + typedValue, where);
aminomancer commented 2 years ago

Remind me to look into setting custom favicons for ucjs: bookmarks. I assume they are seeking icons with page-icon: or moz-anno:favicon: and since these aren't really URLs, we just get a default globe favicon. Ideally we'd intercept the creation of the bookmark somehow and give it a custom icon if it matches /^ucjs:/, like the console icon I set for the ucjs: urlbar results. Not sure if that's possible and I gotta crash but I'll look into that when I get a chance.

note: PlacesUIUtils.loadFavicon