Skip to content
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

Using vimium to add custom javascript commands #1392

Open
Serisium opened this issue Dec 30, 2014 · 23 comments
Open

Using vimium to add custom javascript commands #1392

Serisium opened this issue Dec 30, 2014 · 23 comments

Comments

@Serisium
Copy link

With the ease of making site-specific key mappings, I would like to add in the ability for the user to execute arbitrary javascript one-liners on the open webpage with a vimium hook. This would allow savvy users to quickly enhance keyboard support for a unique site, and create new vimium commands.

For example, the following mapping and command would be possible:

map a javascript alert("Hello world!");

When the user presses 'a', vimium would eval the javascript stored under that command and alert the user.

I would be willing to code this feature, but I would like to ask a few things first:

  1. This feature requires configuration, but would add a great amount of utility to vimium, greatly expanding its possible feature set. However, would it be applicable enough to be included in the master branch, or would it have to live on a fork.
  2. I'm new to this project and coffeescript, though I have experience in javascript web development. Where would I get started adding this feature?
  3. Would anyone else find this useful?

Thanks, all.
-Garrett

@smblott-github
Copy link
Collaborator

This is reasonable proposal, and we should entertain the idea. However, it needs to be fleshed out a bit.

What Vimium does well is modes and key bindings. And those key bindings just end up calling JavaScript. So it's natural for users to want to bind their own JavaScript to keys. And the proposed syntax for key mappings is clean.

The problem, as I see it, is that users will quickly (and reasonably) want more:

  • Long functions which don't fit neatly into the options page, access to libraries, a decent JavaScript editor, the ability to save/restore their code, and so on.

Vimium's current options page doesn't lend itself to these things. And extending it to do so would distract from the core Vimium design goals.

Here's a half-baked idea I've been thinking about. Admit key mappings as @ggreenwood proposes:

map a CustomJS.alert

Add a below-the-fold option customJavaScriptUrl (defaults to empty):

customJavaScriptUrl http://www.mysite.com/vimium.js

If customJavaScriptUrl is set, then Vimium downloads the linked file, which should define a map of command names (alert, in the example) to functions and evaluates it:

window.CustomJS = {
    alert: function() { window.alert("hello"); }
}; /* I can't remember how to write JS! */

Why is this a good idea?

  • It has minimal impact on the Vimium UI and UX, while accommodating the type of functionality @ggreenwood proposes.
  • Users can use whatever editor/environment they want for writing their functions.
  • Pretty much everybody who's geeky enough to use this functionality will have no problem shoving some JS on a web site somewhere.

Why is this a bad idea?

  • It's a hack.
  • It's not really consistent with the Vimium goal of providing a consistent, keyboard-driven chrome UX.
  • Edit: We create new failure modes which might be difficult for the user to understand.

Edit: Users will also quickly (and reasonably) want site-specific key mappings. Something like:

map a Custom.alert http://www.example.com/*

@mrmr1993
Copy link
Contributor

If customJavaScriptUrl is set, then Vimium downloads the linked file, which should define a map of command names (alert, in the example) to functions and evaluates it

I'm inherently uncomfortable with running remote code in our privileged Vimium context. For now, I feel like the safest way to do this is to let other extensions register their own actions (by messaging us), potentially with default key bindings that we prompt the user before enabling. Surely anyone who can write and host some javascript can also write a 5 line manifest.json, declare their functions on an object, and do

chrome.runtime.sendMessage("VIMIUM_EXTENSION_ID", {
  "objectName": "YOUR_OBJECT_NAME_HERE",
  "functions": Object.keys(YOUR_OBJECT_HERE)
  "bindings": {"an": "YOUR_OBJECT_NAME_HERE.example",
                     "and": "YOUR_OBJECT_NAME_HERE.another"}
});
chrome.runtime.onConnectExternal.addListener(function(port) {
  if (port.sender.id != "VIMIUM_EXTENSION_ID")
    return;
  port.onMessage.addListener(function(msg) {
    if (msg.name in YOUR_OBJECT_HERE)
      YOUR_OBJECT_HERE[msg.name]();
  });
});

at the end of their code. (And of course load the extension into Chrome.) This removes the responsibility of managing someone else's code from us, which is a big security/stability win.

Edit: Users will also quickly (and reasonably) want site-specific key mappings

Suggest discussing this separately (#1188), since this is a useful feature to have for our existing commands too.

Edit: documenting the commands is also an issue, the discussions on #1269, #1280 are potentially relevant.

@philc
Copy link
Owner

philc commented Dec 30, 2014

I have some thoughts, will come in soon.
On Dec 30, 2014 6:44 AM, "Matthew Ryan" notifications@github.com wrote:

If customJavaScriptUrl is set, then Vimium downloads the linked file,
which should define a map of command names (alert, in the example) to
functions and evaluates it

I'm inherently uncomfortable with running remote code in our privileged
Vimium context. For now, I feel like the safest way to do this is to let
other extensions register their own actions (by messaging us
https://developer.chrome.com/extensions/messaging), potentially with
default key bindings that we prompt the user before enabling. Surely anyone
who can write and host some javascript can also write a 5 line
manifest.json, declare their functions on an object, and do

chrome.runtime.sendMessage("VIMIUM_EXTENSION_ID", {
"objectName": "YOUR_OBJECT_NAME_HERE",
"functions": Object.keys(YOUR_OBJECT_HERE)
"bindings": {"an": "YOUR_OBJECT_NAME_HERE.example",
"and": "YOUR_OBJECT_NAME_HERE.another"}
});
chrome.runtime.onConnectExternal.addListener(function(port) {
if (port.sender.id != "VIMIUM_EXTENSION_ID")
return;
port.onMessage.addListener(function(msg) {
if (msg.name in YOUR_OBJECT_HERE)
YOUR_OBJECT_HEREmsg.name;
});
});

at the end of their code. (And of course load the extension into Chrome.)
This removes the responsibility of managing someone else's code from us,
which is a big security/stability win.

Edit: Users will also quickly (and reasonably) want site-specific key
mappings

Suggest discussing this separately (#1188
#1188), since this is a useful
feature to have for our existing commands too.


Reply to this email directly or view it on GitHub
#1392 (comment).

@mrmr1993
Copy link
Contributor

I have some thoughts, will come in soon.

@philc I'd quite like to get started on my suggestion here, what were your thoughts on the issue?

@maximbaz
Copy link

Theoretically the idea of calling custom JS code with vimium sounds interesting. Do you have in mind any concrete examples, what are the snippets you would like to bind?

I'm just curious if some snippets would appear to be so useful, that we would eventually like to ship pre-defined set of snippets as a part of vimium.

@mrmr1993
Copy link
Contributor

When I start work on this, I'm planning to implement all the commands open as PRs blocked #1269, so these will be things that we're actively considering shipping.

I'd also like to implement some commands for moving tabs between windows, but I'll submit a PR for that here first.

@Serisium
Copy link
Author

I'm just curious if some snippets would appear to be so useful, that we would eventually like to ship pre-defined set of snippets as a part of vimium.

I know that I would like to be able to open a predefined link in a new and current tab. For example, 'bf' could open facebook from any page, and 'br' would open Reddit.

Other websites, like fimfiction.net, are well designed and consistent, but don't have keyboard shortcuts for every common functionality. By calling the page's javascript functions, I could implement them myself.

@smblott-github
Copy link
Collaborator

I know that I would like to be able to open a predefined link in a new and current tab. For example, 'bf' could open facebook from any page, and 'br' would open Reddit.

Not really addressing your question, but I do something almost like this all the time....

  • Create a bookmark for Facebook.
  • Make sure that the "name" is shortish, something like "Facebook (FB)". Matches in long names score poorly, so keep it short.
  • Use bfb<Enter> -- so, b open bookmark, then fb.

The way Vimium does its scoring, whole words score higher than partial matches. So, fb gets a higher score as a whole word. In addition, Vimium uses recency for ranking bookmarks. This trick usually brings the correct bookmark right to the top, for me.

If you really, really want to encourage Vimium to choose your bookmark, use "Facebook (FB FB)". It'll score even higher.

It'd be nice if Vimium just got it right. But this is one way to help Vimium to get it right.

@mrmr1993
Copy link
Contributor

By calling the page's javascript functions, I could implement them myself.

Since we don't run in the page's javascript scope, we never actually get access to these. This leaves 2 options:

  • A Tampermonkey script (or similar), but then we still have the issue of how to connect the script to our extension (since we can't enable messages from arbitrary URLs).
  • A bookmark (called by the Vomnibar), but these pollute the omni completer for the Vomnibar (and your actual bookmarks!).

For page context, I recon we could just use a custom event (VimiumRegisterCommands/ VimiumRegisterMode). When I get working on this and throw together some examples, I expect I'll write a wrapper so the user doesn't have to worry about interacting with these events.

@edrex
Copy link

edrex commented Jan 30, 2016

Suggest drawing inspiration from Vimium's Firefox-verse counterpart, VimFx, which has custom commands pretty fleshed out. They are registered by other extensions, which provide a natural way to distribute them (and allow other addons to contribute their own commands/bindings). I'm assuming without verification that it's possible for a Chrome extension to expose an API for other extensions.

@wmertens
Copy link

wmertens commented Aug 9, 2016

I propose to allow multiple ways to define commands. I really like where #2109 is going, but for many things I would not want to create an extension since I can express them in one line if I use the existing vimium functions.

Example:

map I coffee v ->
  v.goToTab url: 'https://inbox.google.com'

I'm imagining you can write coffeescript, which will get transpiled and when run it gets a vimium api with nice functions to call. goToTab in this case would switch to the first tab that matches the search.

(note that the API could be both a parameter and this, so that the above could both be written as coffee -> @goToTab ... and javascript v => v.gotToTab({url:...}))

@WhiteYet
Copy link

Hey guys,

Any progress on this feature?
It's really cool, and I wonder this.

@corbt
Copy link

corbt commented Apr 17, 2017

I also would love to see this functionality. Also, I agree with @wmertens -- a principle draw of the proposed functionality for me is that it's lightweight. I don't want to deal with creating and installing a custom extension to write a couple of javascript one-line convenience functions.

FWIW, the functionality I'd like to have is simply hiding all images on a page. I have an extension I use for this on noisy pages, but I'd prefer to be able to activate it with a simple keyboard shortcut.

@mrmr1993
Copy link
Contributor

@corbt you can use this branch (technically you only need 3d23c0a).

To use, add a line similar to the following to your key mappings:
map <keys> js javascript=uricomponentencoded_javascript_code
For example, I've tested with:
map aa js javascript=alert("Hello%20there!");

Note: this doesn't always work with Firefox, (eg. on github) since they don't grant content scripts an exemption from the script-src CSP directive.

vnikk referenced this issue May 30, 2017
Usage:
map <keys> js javascript=uricomponentencoded_javascript_code
@ahmedmohiduet
Copy link

ahmedmohiduet commented Nov 30, 2017

In my Chrome 62.0.3202.94 this does not seem to be working. Any idea how can I debug this?
In my mappings:
map aa js javascript=alert("Hello%20there!");

But pressing 'aa' do not seem to trigger it at all

@smblott-github
Copy link
Collaborator

This feature has not yet been implemented.

@Wonko7
Copy link

Wonko7 commented Mar 2, 2018

I'd like to be able to extend vimium by mapping to my own plugins. If not arbitrary JS, a sendMessage, or I'd be fine with @mrmr1993's idea of registering my plugin with vimium. Bonus points if a count can be optionally given to the mapping.

This would help keeping vimium's core features small and well maintained, while allowing people to extend it to their liking. Allowing people to extend vimium won't diminish its quality, people can install crap plugins in vim too, yet they tend to not do that...

@roachsinai
Copy link

Hi there and any updates? Thanks for what you guys have tried!

@GabrieleRisso
Copy link

there is there a forum discussing that. im on the watch. I keep you posted.

@gdh1995
Copy link
Contributor

gdh1995 commented Feb 17, 2022

It seems this issue is not solved yet...

Unfortunately, although Vimium's TabOperations.openUrlInCurrentTab recognizes JS URLs and it's used in p/P in VisualMode and openCopiedUrlInCurrentTab, createTab doesn't call openUrlInCurrentTab.

So I think the most simple solution is to add a check into createTab: if url is "JS URL" then call openUrlInCurrentTab. The new mapping will look like:

map xxx createTab url="javascript:alert(1)"

I've implemented this in my customized version of Vimium, Vimium C (https://github.com/gdh1995/vimium-c), which supports a command named openUrl to open all types of URL in various ways, so its mapping is:

map xxx openUrl url="javascript:alert(1)"

Communicate with other extensions

As for communicating with other extensions, Vimium hasn't provided such a method. If anyone is still interested in this, you may take a try with Vimium C 's command of sendToExtension to send repeating times and keyCode. And here're some usages:

# `data` can be any JSON value, including number, string, and dict
# if `raw` is true, then not wrap messages but use `data` directly

# the whole message sent through `runtime.sendMessage` will be like:
#    `{handler: "message", from: "Vimium C", count: 2, keyCode: 65, data: {key: "value"} }`
map xxx sendToExtension id="dbepggeogbaibhgnhhndojpepiihcmeb" data={"key":"value"}

# the whole message will be `{key: "value"}`
map xxx sendToExtension id="dbepggeogbaibhgnhhndojpepiihcmeb" raw data={"key":"value"}

@aalvarado
Copy link

aalvarado commented Jul 17, 2022

for me, opening urls is easy if I save the bookmark with a particular name before the actual name.

For example, bgh -> opens github, b opens up the vomnibar in bookmark mode and I saved my bookmark with gh - github.com as the name. Works for any bookmark as long as you have a separator on your first mnemonic. a space, dash or bar also work.

@walkingsnail
Copy link

This enlight me a workaround to use "custom search engine" to transfer parameter "target=%s" into local html file, and then you can use js to achieve your object. Of course it can be a js without any parameter.

fy: file://C:\translate.html?target=%s

Thank you!

@80avin
Copy link

80avin commented Feb 8, 2024

Any progress on this ?
I think an easy approach could be to provide an API using Custom Events.
I often create some tampermonkey scripts, which I would like to invoke using Vimium.

The API could be like

// From my script.
function onKeyPress(data) {
  // do something
}

const addToVimiumEvent = new CustomEvent('vimium_register_key', {
    detail: {
      key: 'g v' // Press `g` then `v` to initiate.
      handler: onKeyPress,
      callback: (error, message) => {
        // check if adding failed or worked.
     },
    }
  })
document.dispatchEvent(addToVimiumEvent);

We can also try more ways of integrating deeply with Vimium, just like plugins integrate with vim/neovim.
Which will open the possibility of vimium plugins.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests