Skip to content

Commit

Permalink
Refactor injected script to handle onmousedown, etc
Browse files Browse the repository at this point in the history
Modifies the common extract_javascript_link() to return an URL object.
Fix #101, mentioned in #77.
Also reinstate respecting the target or not, point 3 in #103
  • Loading branch information
Cimbali committed Apr 1, 2020
1 parent ebe7d02 commit 6f336db
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 69 deletions.
2 changes: 1 addition & 1 deletion addon/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ function handle_message(message, sender)
else if (message.target === new_tab)
{
const extra = browser_version > 57 ? { openerTabId: tab_id } : {};
browser.tabs.create({...extra, url: message.link, active: Prefs.values.switch_to_tab })
return browser.tabs.create({...extra, url: message.link, active: Prefs.values.switch_to_tab })
}
else
return browser.tabs.update(tab_id, { url: message.link });
Expand Down
151 changes: 91 additions & 60 deletions addon/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,93 +22,101 @@ function highlight_link(node, remove)
node.style.setProperty(prop, remove ? '' : val, 'important');
}


function find_target_frame(wnd, target)
{
if (target === '_top')
return wnd.top || wnd;
else if (target === '_parent')
return wnd.parent || wnd;
else if (target !== '_self')
return Array.from(wnd.frames).find(f => f.name === target) || wnd;
else
return wnd;
}


function event_do_click(url, node, evt)
{
if (evt.button == 0 && evt.altKey)
return false; // alt+click, do nothing

let wnd = window;
let open_newtab = evt.ctrlKey || evt.button == 1 || evt.metaKey;
let open_newwin = evt.shiftKey;
evt.stopPropagation();
evt.preventDefault();

if (/*Prefs.values.gotarget && */ evt.button == 0 && !(evt.shiftKey || evt.ctrlKey || evt.metaKey || evt.altKey))
{
let target = node.hasAttribute('target') && node.getAttribute('target') || '_self';
if ("_blank" == target)
open_newtab = true;
else
{
let frames = content.frames;
wnd = node.ownerDocument.defaultView;

switch (target)
{
case '_top':
wnd = wnd.top;
break;
case '_parent':
wnd = wnd.parent;
break;
case '_self':
break;
default:
wnd = Array.from(frames).find(f => f.name === target);
}
// NB: use default _self target if we don’t want to follow target attributes
const target = Prefs.values.gotarget && node.hasAttribute('target') && node.getAttribute('target') || '_self';

if (wnd)
{
evt.stopPropagation();
evt.preventDefault();
const open_new_tab = evt.ctrlKey || evt.button == 1 || evt.metaKey || target === '_blank';
const open_new_win = evt.shiftKey;

wnd.location = url;
return true;
}
}
}
// same tab => find the correct frame
const wnd = find_target_frame(node.ownerDocument.defaultView || window, target);

evt.stopPropagation();
evt.preventDefault();
if (open_new_tab || open_new_win)
browser.runtime.sendMessage({ action: 'open url', link: url, target: open_new_tab ? new_tab : new_window })
.catch(() => { wnd.top.location = url; })

browser.runtime.sendMessage({
action: 'open url',
link: url,
target: open_newtab ? new_tab : (open_newwin ? new_window : same_tab)
}).catch(() =>
{
// Could not find a target window or assigning a location to it failed
node.setAttribute('href', url);
node.click();
else if (wnd)
wnd.location = url;

// Alternately: window.content.location = url;
});
else
browser.runtime.sendMessage({ action: 'open url', link: url, target: same_tab })

return true;
}


function find_click_target(node, searchevents)
function find_click_target(node)
{
do
{
if (node.nodeName === 'A')
return { node, link: node.href };
return { node, href: node.href };

for (let evttype of ['onclick', 'onmouseup', 'onmousedown'])
if (searchevents && node[evttype] !== null)
return { node, link: node[evttype].toString().slice(text_link.indexOf('{') + 1, text_link.lastIndexOf('}')) }
}
while (['A', 'BODY', 'HTML'].indexOf(node.nodeName) === -1 && (node = node.parentNode));

} while (['A', 'BODY', 'HTML'].indexOf(node.nodeName) === -1 && (node = node.parentNode));
return {}
}


// NB: this is not great to decode obfuscated javascript codes, but at least returns if there are events
function find_click_event(node)
{
const searched_events = ['click', 'mousedown', 'touchstart', 'mouseup']
do
{
const actions = (node.hasAttribute('jsaction') && node.getAttribute('jsaction').split(';') || [])
.filter(val => val.trim().length !== 0)
.map(val => val.trim().split(':', 2))
.map(act => act.length === 1 ? ['click'].concat(act) : act);

let jsaction;
for (const event_type of searched_events)
if (node.hasAttribute(`on${event_type}`) && node[`on${event_type}`])
{
const func = node[`on${event_type}`].toString();
return { node, func: func.slice(func.indexOf('{') + 1, func.lastIndexOf('}')), event_type }
}
else if (jsaction = actions.find(([evt, func]) => evt === event_type))
{
const [evt, func] = jsaction;
return { node, func, event_type };
}
}
while (node.nodeName !== 'HTML' && (node = node.parentNode));

return {}
}


function on_pre_click(evt)
{
const { link, node } = find_click_target(evt.target, false);
const { href, node } = find_click_target(evt.target);

// This is a valid link: cancel onmousedown trickeries by stopping the event.
if (link)
if (href)
{
evt.stopPropagation();
evt.preventDefault();
Expand All @@ -118,7 +126,10 @@ function on_pre_click(evt)

function on_click(evt)
{
const { link : text_link, node } = find_click_target(evt.target, true);
const { href, func, node, event_type } = { ...find_click_event(evt.target), ...find_click_target(evt.target) };

const text_link = href || func;
log(`click on ${node.nodeName} ${node} with URL "${text_link}" and event ${event_type}`)

if (!text_link)
return;
Expand All @@ -135,15 +146,32 @@ function on_click(evt)
}


let cleaned_link = extract_javascript_link(text_link, window.location);
if (!cleaned_link || cleaned_link === text_link)
const base = (node.ownerDocument.defaultView || window).location.href;
let cleaned_link = extract_javascript_link(text_link, base);

try
{
// NB: if event_type is not undefined, we need to prevent the click event,
// and perform the action of activating the link manually, even if the link is clean.
if (event_type && href && !cleaned_link)
cleaned_link = new URL(href, base);
}
catch(e) {}

if (!cleaned_link)
return;

log(`Cleaning javascript ${text_link} to ${cleaned_link}`)
if (event_do_click(cleaned_link, node, evt))
{
// instead of blinking the URL bar, tell the background to show a notification.
browser.runtime.sendMessage({action: 'notify', url: cleaned_link, orig: text_link, type: 'clicked'}).catch(() => {});
browser.runtime.sendMessage({
action: 'notify',
url: cleaned_link,
orig: text_link,
type: 'clicked',
cleaned: {javascript: event_type || 'href'}
}).catch(() => {});
}
}

Expand All @@ -162,11 +190,13 @@ function toggle_active(enabled)
{
window.addEventListener('click', on_click, {capture: true});
window.addEventListener('mousedown', on_pre_click, {capture: true});
window.addEventListener('touchstart', on_pre_click, {capture: true});
}
else
{
window.removeEventListener('click', on_click, {capture: true});
window.removeEventListener('mousedown', on_pre_click, {capture: true});
window.removeEventListener('touchstart', on_pre_click, {capture: true});
}
}

Expand Down Expand Up @@ -195,3 +225,4 @@ browser.runtime.onMessage.addListener(message =>
else
return Promise.reject('Unexpected message: ' + String(message));
});
console.log('successfully injected');
3 changes: 1 addition & 2 deletions addon/modules/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,7 @@ function extract_javascript_link(text_link, base_url)
log('matched javascript link: ' + cleaned_link)
try
{
let url = new URL(cleaned_link, base_url)
return cleaned_link;
return new URL(cleaned_link, base_url)
}
catch (e)
{
Expand Down
6 changes: 3 additions & 3 deletions addon/modules/display_cleaned_link.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ function remove_js(orig)
let js_cleaned_link = extract_javascript_link(orig);
if (js_cleaned_link)
{
let start = orig.indexOf(js_cleaned_link);
let end = start + js_cleaned_link.length;
let start = orig.indexOf(js_cleaned_link.href);
let end = start + js_cleaned_link.href.length;

return [new URL(js_cleaned_link), orig.slice(0, start), orig.slice(end)];
return [js_cleaned_link, orig.slice(0, start), orig.slice(end)];
}
else
return [new URL(orig)];
Expand Down
6 changes: 3 additions & 3 deletions tests/cleanlink.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,12 @@ describe('wrapped_cl', function() {
describe('extract_javascript_link', function() {
it('should clean the link to the simple javascript function argument', done =>
{
expect(extract_javascript_link("javascript:window.open('http://somesite.com/')")).to.equal('http://somesite.com/');
expect(extract_javascript_link("javascript:window.open('http://somesite.com/')").href).to.equal('http://somesite.com/');
done();
});
it('should clean the link to the complex javascript function argument', done =>
{
expect(extract_javascript_link("javascript:func(\"arg1\", 'arg2', 'http://somesite.com/', 'target=\"_self\"')")).to.equal('http://somesite.com/');
expect(extract_javascript_link("javascript:func(\"arg1\", 'arg2', 'http://somesite.com/', 'target=\"_self\"')").href).to.equal('http://somesite.com/');
done();
});
});
Expand All @@ -159,7 +159,7 @@ describe('extract_javascript_link + wrapped_cl', function() {
it('should clean the link to the javascript function relative path argument', () =>
{
let rel_url = "javascript:displayWindowzdjecie('/_misc/zdj_wym.php?url_zdjecie=https://static2.s-trojmiasto.pl/zdj/c/n/9/2079/1100x0/2079199-Wizualizacja-obrotowej-kladki-Sw-Ducha.jpg',1100,778);";
let unjs_url = extract_javascript_link(rel_url, 'http://somedomain.com/a/page.html?foo=qux')
let unjs_url = extract_javascript_link(rel_url, 'http://somedomain.com/a/page.html?foo=qux').href
return Rules.loaded.then(() => expect(wrapped_cl(unjs_url, 'http://somedomain.com/a/page.html?foo=qux')).to
.equal('https://static2.s-trojmiasto.pl/zdj/c/n/9/2079/1100x0/2079199-Wizualizacja-obrotowej-kladki-Sw-Ducha.jpg')
)
Expand Down

0 comments on commit 6f336db

Please sign in to comment.