Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
37c9cb8
Support loading SVG from files
anthonyec Apr 19, 2023
dc80df2
Start adding content script
anthonyec Apr 19, 2023
f1600dd
Add button styles
anthonyec Apr 19, 2023
af8c894
Add adding functionality
anthonyec Apr 19, 2023
f36e856
Format
anthonyec Apr 19, 2023
3e881f9
Add content script to example page
anthonyec Apr 20, 2023
1760454
Add add animation
anthonyec Apr 20, 2023
76b4f12
Add error check
anthonyec Apr 20, 2023
52e0264
Check for common or untitled scripts
anthonyec Apr 20, 2023
fe6a818
Add add button to description
anthonyec Apr 20, 2023
2c60c3e
Darken drop shadow animation
anthonyec Apr 20, 2023
76c31f3
Add more checks for false positives
anthonyec Apr 20, 2023
ddd9b32
Move logic into function and emit console error when there is an error
anthonyec Apr 21, 2023
28ce6bb
Move function into own file
anthonyec Apr 21, 2023
3c70d06
Reorganise CSS a bit
anthonyec Apr 21, 2023
0652d4b
Make button appear on top when mouse over
anthonyec Apr 21, 2023
6e86fa9
Disable add button once already added
anthonyec Apr 21, 2023
2250259
Use action const for message passing
anthonyec Apr 21, 2023
e7a3fad
Merge branch 'main' into find-bookmarklets-on-page
anthonyec Apr 27, 2023
3ada5fe
Merge branch 'main' into find-bookmarklets-on-page
anthonyec May 16, 2023
338716a
Check for single functions to see if they are custom or builtin windo…
anthonyec May 18, 2023
426c1db
Remove class heuristic
anthonyec May 18, 2023
d68cd84
Merge branch 'main' into find-bookmarklets-on-page
anthonyec May 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"description": "Title used on the example scripts page"
},
"getting_started_subtitle": {
"message": "To add a script, drag it to your Bookmarks Bar.",
"message": "To add a script, drag it to your Bookmarks Bar or press the %r button",
"description": "Subtitle used to explain how to add bookmarklets to the bookmarkbar"
},
"edit_script_title": {
Expand Down
12 changes: 12 additions & 0 deletions src/background/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { CREATE_BOOKMARK } from '../content/show_button_inside_element';
import createIconRenderer from './icon_renderer';

const renderIcon = createIconRenderer();

setInterval(renderIcon, 1000);
renderIcon();

chrome.runtime.onMessage.addListener(
(message = { action: null, payload: null }) => {
if (message.action === CREATE_BOOKMARK) {
chrome.bookmarks.create({
title: message.payload.title,
url: message.payload.url
});
}
}
);
3 changes: 3 additions & 0 deletions src/content/add_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/content/check_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions src/content/create_shadow_dom_inside.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export function createShadowDomInside(target) {
const shadowElement = document.createElement('div');

shadowElement.style.background = 'none';
shadowElement.style.border = 'none';
shadowElement.style.outline = 'none';
shadowElement.style.boxShadow = 'none';
shadowElement.style.margin = '0';
shadowElement.style.padding = '0';
shadowElement.style.top = '0';
shadowElement.style.left = '0';
shadowElement.style.transform = 'none';
shadowElement.style.width = '0';
shadowElement.style.height = '0';
shadowElement.style.minWidth = '0';
shadowElement.style.minHeight = '0';
shadowElement.style.position = 'relative';
shadowElement.style.display = 'inline';

target.appendChild(shadowElement);

return shadowElement.attachShadow({ mode: 'open' });
}
50 changes: 50 additions & 0 deletions src/content/default_window_methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export const DEFAULT_WINDOW_METHODS = [
'alert',
'atob',
'blur',
'btoa',
'cancelAnimationFrame',
'cancelIdleCallback',
'captureEvents',
'clearInterval',
'clearTimeout',
'close',
'confirm',
'createImageBitmap',
'fetch',
'find',
'focus',
'getComputedStyle',
'getSelection',
'matchMedia',
'moveBy',
'moveTo',
'open',
'postMessage',
'print',
'prompt',
'queueMicrotask',
'releaseEvents',
'reportError',
'requestAnimationFrame',
'requestIdleCallback',
'resizeBy',
'resizeTo',
'scroll',
'scrollBy',
'scrollTo',
'setInterval',
'setTimeout',
'stop',
'structuredClone',
'webkitCancelAnimationFrame',
'webkitRequestAnimationFrame',
'getScreenDetails',
'queryLocalFonts',
'showDirectoryPicker',
'showOpenFilePicker',
'showSaveFilePicker',
'openDatabase',
'webkitRequestFileSystem',
'webkitResolveLocalFileSystemURL'
];
61 changes: 61 additions & 0 deletions src/content/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { DEFAULT_WINDOW_METHODS } from './default_window_methods';
import { showButtonInsideElement } from './show_button_inside_element';

const FUNCTION_REGEX = /^([a-zA-Z0-9].*)\(.*?\)$/g;

function main() {
const scripts = document.body.querySelectorAll('[href*="javascript:"]');

Array.from(scripts).forEach((script) => {
const title = script.textContent;
const href = script.getAttribute('href');
const cleanHref = href.trim().replaceAll(' ', '').replaceAll(';', '');
const cleanHrefWithoutPrefix = cleanHref.replace(/^javascript:/, '');
const classList = Array.from(script.classList);

// A lot of random buttons on websites include `void` code to prevent the
// default behaviour of links. These should not be considered bookmarklets.
if (
cleanHref === 'javascript:' ||
cleanHref === 'javascript:void(0)' ||
cleanHref === 'javascript:void0'
) {
return;
}

// Empty links are not considered bookmarklets, such as icon buttons.
if (title.trim() === '') {
return;
}

// A single character, like "x", is something used for buttons. Example on
// this cookie banner: https://www.universityofgalway.ie/t4training/bookmarklets.html
if (title.length === 1) {
return;
}

// A regex object is stateful. Not resetting this will cause the regex test
// to toggle between `true` and `false` on each iteration of the loop, which
// is bananas!
FUNCTION_REGEX.lastIndex = 0;

// Look for `href` values that are single function calls. If it's not a
// default window method, then it's probably not a bookmarklet.
const match = FUNCTION_REGEX.exec(cleanHrefWithoutPrefix);

if (match) {
const potentialFunctionName = match[1].replace(/^window\./g, '');
const isDefaultWindowMethod = DEFAULT_WINDOW_METHODS.includes(
potentialFunctionName
);

if (!isDefaultWindowMethod) {
return;
}
}

showButtonInsideElement(script, title, href);
});
}

main();
148 changes: 148 additions & 0 deletions src/content/show_button_inside_element.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { createShadowDomInside } from './create_shadow_dom_inside';

import addIcon from './add_icon.svg';
import checkIcon from './check_icon.svg';

const CONTAINER_CLASS_NAME = 'powerlet-button';
const MANAGE_BUTTON_CLASS_NAME = 'manage-button';
const LABEL_CLASS_NAME = 'add-remove-button__label';

export const CREATE_BOOKMARK = 'CREATE_BOOKMARK';

export function showButtonInsideElement(target, title, code) {
const shadow = createShadowDomInside(target);
const style = document.createElement('style');

const container = document.createElement('div');
const button = document.createElement('button');
const icon = document.createElement('img');
const label = document.createElement('span');

container.classList.add(CONTAINER_CLASS_NAME);
button.classList.add(MANAGE_BUTTON_CLASS_NAME);

label.classList.add(LABEL_CLASS_NAME);
label.textContent = 'Add Bookmarklet';

icon.src = addIcon;

style.innerHTML = `
.${CONTAINER_CLASS_NAME} {
width: 28px;
height: 28px;
position: absolute;
transform: translate(-30%, -100%);
top: 50%;
left: 50%;
}

.${CONTAINER_CLASS_NAME}:hover .${MANAGE_BUTTON_CLASS_NAME} {
width: auto;
height: auto;
padding: 5px;
transform: translate(-12.5px, -50%);
}

.${CONTAINER_CLASS_NAME}:hover .${LABEL_CLASS_NAME} {
display: block;
}

.${MANAGE_BUTTON_CLASS_NAME} {
color: white;
background-image: linear-gradient(-30deg, #D86299 0%, #A449D6 100%);
border-radius: 100px;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 15px;
font-weight: 500;
position: absolute;
top: 50%;
left: 50%;
width: 19px;
height: 19px;
transform: translate(-9px, -50%);
}

.${MANAGE_BUTTON_CLASS_NAME}--added {
background: green;
animation: added 600ms ease;
}

.${MANAGE_BUTTON_CLASS_NAME} img {
flex-shrink: 0;
user-select: none;
pointer-events: none;
}

.${MANAGE_BUTTON_CLASS_NAME}--added img {
transform: translateY(1px);
}

.${LABEL_CLASS_NAME} {
margin-right: 4px;
white-space: nowrap;
display: none;
user-select: none;
pointer-events: none;
}

@keyframes added {
from {
box-shadow: 0 0 0 rgba(0, 100, 0, 1);
}

to {
box-shadow: 0 0 50px rgba(0, 100, 0, 0);
}
}
`;

button.addEventListener('mouseenter', () => {
container.style.zIndex = '2147483647';
});

button.addEventListener('mouseleave', () => {
container.style.zIndex = 'auto';
});

button.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();

if (button.classList.contains(`${MANAGE_BUTTON_CLASS_NAME}--added`)) {
return;
}

try {
chrome.runtime.sendMessage({
action: CREATE_BOOKMARK,
payload: {
title: title.trim(),
url: code.trim()
}
});
} catch (error) {
alert(
'Powerlet: Something went wrong when adding bookmarklet. Please reload the page and try again.'
);
console.error(error);
return;
}

label.textContent = 'Added!';
button.classList.add(`${MANAGE_BUTTON_CLASS_NAME}--added`);
icon.src = checkIcon;
});

button.append(icon);
button.append(label);
container.append(button);

shadow.appendChild(style);
shadow.appendChild(container);
}
6 changes: 6 additions & 0 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
"scripts": ["background.bundle.js"],
"persistent": false
},
"content_scripts": [
{
"js": ["content.bundle.js"],
"matches": ["http://*/*", "https://*/*"]
}
],
"browser_action": {
"default_popup": "popup.html",
"default_icon": {
Expand Down
23 changes: 17 additions & 6 deletions src/pages/examples.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,27 @@
display: inline-block;
font-size: 16px;
font-weight: 500;
padding: 12px 24px;
padding: 7px;
text-decoration: none;
margin-bottom: 20px;
margin-right: 10px;
margin-right: 20px;
margin-bottom: 22px;
}

.bookmarklet:hover {
color: #2A7CEA;
}

.add-button {
background-image: linear-gradient(-30deg, #D86299 0%, #A449D6 100%);
border-radius: 100px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 19px;
height: 19px;
margin: 0 3px;
}

@media(prefers-color-scheme: dark) {
body {
background: #202124;
Expand All @@ -66,8 +77,8 @@ <h1 data-translation-key="getting_started_title">
Getting Started
</h1>

<h2 data-translation-key="getting_started_subtitle">
To add a script, drag it to your Bookmarks Bar.
<h2 data-translation-key="getting_started_subtitle" data-translation-replacement='<div class="add-button"><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLXBsdXMiPgogICAgPHBhdGggZD0iTTEyIDV2MTRNNSAxMmgxNCIvPgo8L3N2Zz4K" /></div>'>
To add a script, drag it to your Bookmarks Bar!
</h2>

<a class="bookmarklet" href="javascript:window.scrollTo(0,0);">
Expand Down Expand Up @@ -103,6 +114,6 @@ <h2 data-translation-key="getting_started_subtitle">
</a>
</main>

<script src="examples.js"></script>
<script src="examples.bundle.js"></script>
</body>
</html>
Loading