padloc/packages/extension/src/content.ts

315 lines
10 KiB
TypeScript

import "@webcomponents/webcomponentsjs";
import { browser } from "webextension-polyfill-ts";
// import { throttle } from "@padloc/core/src/util";
import { Message } from "./message";
const css = `
@font-face {
font-family: "Nunito";
font-style: normal;
font-weight: 400;
src: url("${browser.runtime.getURL("Nunito-Regular.ttf")}") format("truetype");
}
@font-face {
font-family: "Nunito";
font-style: normal;
font-weight: 600;
src: url("${browser.runtime.getURL("Nunito-SemiBold.ttf")}") format("truetype");
}
@font-face {
font-family: "FontAwesome";
src: url("${browser.runtime.getURL("fontawesome-webfont.ttf")}") format("truetype");
font-weight: normal;
font-style: normal;
}
@keyframes ripple {
from {
opacity: 0.3;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(2);
}
}
@keyframes highlight {
from {
opacity: 0;
transform: scale(1.1);
}
to {
opacity: 1;
transform: scale(1);
}
}
.ripple {
position: absolute;
z-index: 9999999;
border-radius: 8px;
background: #3bb7f9;
animation: ripple 0.8s both;
pointer-events: none;
will-change: transform, opacity;
}
.highlight {
position: absolute;
left: 0;
top: 0;
z-index: 9999999;
border-radius: 8px;
border: solid 2px #3bb7f9;
box-sizing: border-box;
pointer-events: none;
will-change: transform, width, height, opacity;
animation: highlight 0.3s both;
}
.highlight.out {
animation-direction: reverse;
}
.drag-element {
position: absolute;
top: 0;
left: 0;
z-index: 9999999;
border-radius: 8px;
background: #3bb7f9;
color: white;
padding: 6px;
cursor: grabbing;
will-change: transform;
font-family: "Nunito";
font-size: 14px;
}
body.dragging {
cursor: grabbing !important;
}
`;
class ExtensionContent {
// private _hoveredInput: HTMLInputElement | null = null;
//
// private _highlightElement: HTMLDivElement;
async init() {
const style = document.createElement("style");
document.head.appendChild(style);
style.type = "text/css";
style.appendChild(document.createTextNode(css));
browser.runtime.onMessage.addListener((msg: Message) => this._handleMessage(msg));
}
private _handleMessage(msg: Message) {
switch (msg.type) {
case "fillActive":
// console.log("autofill", msg);
return Promise.resolve(this._fill(msg.value));
// case "fillOnDrop":
// // console.log("autofill", msg);
// return new Promise(resolve => {
// let timeout: number;
//
// const dragover = (e: DragEvent) => {
// // console.log("dragover", performance.now());
// if (timeout) {
// clearTimeout(timeout);
// }
// this._updateHovered(e);
// };
//
// const dragleave = () => {
// // console.log("dragleave", performance.now());
// timeout = window.setTimeout(() => {
// if (this._hoveredInput) {
// resolve(this._fill(msg.value, this._hoveredInput));
// } else {
// resolve(false);
// }
//
// this._highlight(null);
// this._hoveredInput = null;
// document.removeEventListener("dragover", dragover);
// document.removeEventListener("dragleave", dragleave);
// }, 100);
// };
//
// document.addEventListener("dragover", dragover);
// document.addEventListener("dragleave", dragleave);
// });
case "hasActiveInput":
const activeInput = this._getActiveInput();
// console.log("has active input", activeInput);
return Promise.resolve(!!activeInput);
case "isContentReady":
return Promise.resolve(true);
}
}
private _getActiveElement(doc: DocumentOrShadowRoot): Element | null {
const el = doc.activeElement;
return (el && el.shadowRoot && this._getActiveElement(el.shadowRoot)) || el;
}
private _isElementFillable(el: Element) {
return (
el instanceof HTMLInputElement &&
["text", "number", "email", "password", "tel", "date", "month", "search", "url"].includes(el.type)
);
}
private _getActiveInput(): HTMLInputElement | null {
const el = this._getActiveElement(document);
return el && this._isElementFillable(el) ? (el as HTMLInputElement) : null;
}
private async _fill(value: string, input: HTMLInputElement | null = this._getActiveInput()) {
if (!input) {
return false;
}
input.value = value;
// Do some song and dance for various frameworks that expect input events
input.dispatchEvent(
new KeyboardEvent("keydown", {
bubbles: true,
key: "",
})
);
input.dispatchEvent(
new KeyboardEvent("keyup", {
bubbles: true,
key: "",
})
);
input.dispatchEvent(
new KeyboardEvent("keypress", {
bubbles: true,
key: "",
})
);
input.dispatchEvent(new Event("input", { bubbles: true }));
input.dispatchEvent(new Event("change", { bubbles: true }));
// this._ripple(input);
return true;
}
// private _ripple(el: HTMLElement) {
// const { left, top, width, height } = el.getBoundingClientRect();
// const ripple = document.createElement("div");
// const { scrollTop, scrollLeft } = document.documentElement;
// Object.assign(ripple.style, {
// top: `${top + scrollTop}px`,
// left: `${left + scrollLeft}px`,
// width: width + "px",
// height: height + "px"
// });
// ripple.classList.add("ripple");
// document.body.appendChild(ripple);
// setTimeout(() => ripple.remove(), 800);
// }
// private _highlight(el: HTMLElement | null) {
// if (this._highlightElement) {
// const hel = this._highlightElement;
// hel.classList.add("out");
// setTimeout(() => hel.remove(), 500);
// }
//
// if (el) {
// this._highlightElement = document.createElement("div");
// this._highlightElement.classList.add("highlight");
// document.body.appendChild(this._highlightElement);
//
// const { left, top, width, height } = el.getBoundingClientRect();
// const { scrollTop, scrollLeft } = document.documentElement;
// Object.assign(this._highlightElement.style, {
// top: `${top + scrollTop}px`,
// left: `${left + scrollLeft}px`,
// width: width + "px",
// height: height + "px",
// opacity: 1
// });
// }
// }
// private _keydown({ code, ctrlKey, metaKey, altKey }: KeyboardEvent) {
// if (code === "Escape") {
// this.close();
// }
// if (!this._item) {
// return;
// }
//
// const matchNumber = code.match(/Digit(\d)/);
// const index = (matchNumber && parseInt(matchNumber[1])) || NaN;
// if (!isNaN(index) && !!this._item.fields[index - 1]) {
// const input = this._getActiveInput();
// if ((ctrlKey || metaKey) && altKey && input) {
// this._fillIndex(index - 1);
// } else if (!input) {
// this._fieldIndex = index - 1;
// }
// }
// }
//
// private _dragstart(index: number) {
// this._fieldIndex = index;
// document.body.classList.add("dragging");
// this.classList.add("dragging");
// }
// private _getFillableFromPoint(
// root: Document | ShadowRoot,
// x: number,
// y: number,
// depth = 0
// ): HTMLInputElement | null {
// const els = root.elementsFromPoint(x, y);
//
// // Check all elements on this level first
// const input = els.find(el => this._isElementFillable(el));
//
// if (input) {
// return input as HTMLInputElement;
// }
//
// // If no fillable elements on this level were found, go one level deeper
// for (const el of els) {
// if (el.shadowRoot && el.shadowRoot !== root) {
// const input = this._getFillableFromPoint(el.shadowRoot, x, y, depth + 1);
// if (input) {
// return input;
// }
// }
// }
//
// // If nothing was found, return null
// return null;
// }
// private _updateHovered = throttle((e: DragEvent) => {
// // console.log("update hovered", performance.now());
// const input = this._getFillableFromPoint(document, e.clientX, e.clientY);
// if (input !== this._hoveredInput) {
// this._highlight(input);
// this._hoveredInput = input;
// }
// }, 50);
}
if (typeof window.extension === "undefined") {
window.extension = new ExtensionContent();
window.extension.init();
}