1
0
mirror of https://github.com/AllanWang/Frost-for-Facebook.git synced 2024-09-19 15:11:42 +02:00

Remove gecko extension and add back old scripts

This commit is contained in:
Allan Wang 2023-06-20 22:00:37 -07:00
parent c3023b0da9
commit f13b2d2998
No known key found for this signature in database
GPG Key ID: C93E3F9C679D7A56
20 changed files with 471 additions and 145 deletions

View File

@ -42,11 +42,11 @@ internal constructor(
theme.inject(view)
}
fun getTheme(): JsInjector {
private fun getTheme(): JsInjector {
return try {
val content =
context.assets
.open("frostcore/css/facebook/themes/material_glass.css")
.open("frost/css/facebook/themes/material_glass.css")
.bufferedReader()
.use(BufferedReader::readText)
JsBuilder().css(content).build()

View File

@ -50,7 +50,7 @@ enum class JsAssets(private val singleLoad: Boolean = true) : JsInjector {
private fun injectorBlocking(context: Context): JsInjector {
return try {
val content =
context.assets.open("frostcore/js/$file").bufferedReader().use(BufferedReader::readText)
context.assets.open("frost/js/$file").bufferedReader().use(BufferedReader::readText)
JsBuilder().js(content).run { if (singleLoad) single(name) else this }.build()
} catch (e: FileNotFoundException) {
logger.atWarning().withCause(e).log("JsAssets file not found")

View File

@ -1,48 +0,0 @@
{
"manifest_version": 2,
"name": "frostcore",
"version": "1.0.0",
"description": "Core web extension for Frost",
"browser_specific_settings": {
"gecko": {
"id": "frost_gecko_core@pitchedapps"
}
},
"background": {
"scripts": [
"js/background/cookies.js"
]
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"js/frost.js"
]
},
{
"matches": [
"*://*.facebook.com/*"
],
"js": [
"js/click_a.js"
]
}
],
"permissions": [
"<all_urls>",
"activeTab",
"contextMenus",
"contextualIdentities",
"cookies",
"history",
"management",
"tabs",
"nativeMessaging",
"nativeMessagingFromContent",
"geckoViewAddons",
"webRequest"
]
}

View File

@ -1,7 +1,7 @@
{
"scripts": {
"compile": "tsc -p tsconfig.json && sass --no-source-map --style compressed --update scss:assets/frostcore/css",
"scss-watch": "sass --no-source-map --style compressed --update scss:assets/frostcore/css --watch"
"compile": "tsc -p tsconfig.json && sass --no-source-map --style compressed --update scss:assets/frost/css",
"scss-watch": "sass --no-source-map --style compressed --update scss:assets/frost/css --watch"
},
"license": "MPL-2.0",
"repository": {

View File

@ -0,0 +1,43 @@
// Credits to https://codepen.io/tomhodgins/pen/KgazaE
(function () {
const classTag = 'frostAutoExpand';
const textareas = <NodeListOf<HTMLTextAreaElement>>document.querySelectorAll(`textarea:not(.${classTag})`);
const dataAttribute = 'data-frost-minHeight';
const _frostAutoExpand = (el: HTMLElement) => {
if (!el.hasAttribute(dataAttribute)) {
el.setAttribute(dataAttribute, el.offsetHeight.toString());
}
// If no height is defined, have min bound to current height;
// otherwise we will allow for height decreases in case user deletes text
const minHeight = parseInt(el.getAttribute(dataAttribute) ?? '0');
// Save scroll position prior to height update
// See https://stackoverflow.com/a/18262927/4407321
const scrollLeft = window.pageXOffset ||
(document.documentElement || document.body.parentNode || document.body).scrollLeft;
const scrollTop = window.pageYOffset ||
(document.documentElement || document.body.parentNode || document.body).scrollTop;
el.style.height = 'inherit';
el.style.height = `${Math.max(el.scrollHeight, minHeight)}px`;
// Go to original scroll position
window.scrollTo(scrollLeft, scrollTop);
};
function _frostExpandAll() {
textareas.forEach(_frostAutoExpand);
}
textareas.forEach(el => {
el.classList.add(classTag)
const __frostAutoExpand = () => {
_frostAutoExpand(el)
};
el.addEventListener('paste', __frostAutoExpand)
el.addEventListener('input', __frostAutoExpand)
el.addEventListener('keyup', __frostAutoExpand)
});
window.addEventListener('load', _frostExpandAll)
window.addEventListener('resize', _frostExpandAll)
}).call(undefined);

View File

@ -1,44 +0,0 @@
async function updateCookies(changeInfo: browser.cookies._OnChangedChangeInfo) {
const application = "frostBackgroundChannel"
browser.runtime.sendNativeMessage(application, changeInfo)
}
async function readCookies() {
const application = "frostBackgroundChannel"
browser.runtime.sendNativeMessage(application, 'start cookie fetch')
// Testing with domains or urls didn't work
const cookies = await browser.cookies.getAll({});
const cookies2 = await browser.cookies.getAll({ storeId: "firefox-container-frost-context-1" })
const cookieStores = await browser.cookies.getAllCookieStores();
browser.runtime.sendNativeMessage(application, { name: "cookies", data: cookies.length, stores: cookieStores.map((s) => s.id), data2: cookies2.length, data3: cookies.filter(s => s.storeId != 'firefox-default').length })
}
async function handleMessage(request: any, sender: browser.runtime.MessageSender, sendResponse: (response?: any) => void) {
browser.runtime.sendNativeMessage("frostBackgroundChannel", 'pre send')
await new Promise(resolve => setTimeout(resolve, 1000));
browser.runtime.sendNativeMessage("frostBackgroundChannel", 'post send')
sendResponse({ received: request, asdf: "asdf" })
}
// Reading cookies with storeId might not be fully supported on Android
// https://stackoverflow.com/q/76505000/4407321
// Using manifest 3 stopped getAll from working
// Reading now always shows storeId as firefox-default
// Setting a cookie with a custom container does not seem to work
// browser.cookies.onChanged.addListener(updateCookies);
// browser.tabs.onActivated.addListener(readCookies);
// browser.runtime.onStartup.addListener(readCookies);
// browser.runtime.onMessage.addListener(handleMessage);

View File

@ -1,7 +1,6 @@
(async function () {
(function () {
let prevented = false;
/**
* Go up at most [depth] times, to retrieve a parent matching the provided predicate
* If one is found, it is returned immediately.
@ -40,29 +39,29 @@
/**
* Given event and target, return true if handled and false otherwise.
*/
type EventHandler = (e: Event, target: HTMLElement) => Promise<Boolean>
type EventHandler = (e: Event, target: HTMLElement) => Boolean
const _frostGeneral: EventHandler = async (e, target) => {
const _frostGeneral: EventHandler = (e, target) => {
// We now disable clicks for the main notification page
if (document.getElementById("notifications_list")) {
return false
}
const url = _parentUrl(target, 2);
return frost.loadUrl(url);
return Frost.loadUrl(url);
};
const _frostLaunchpadClick: EventHandler = async (e, target) => {
const _frostLaunchpadClick: EventHandler = (e, target) => {
if (!_parentEl(target, 6, (el) => el.id === 'launchpad')) {
return false
}
console.log('Clicked launchpad');
const url = _parentUrl(target, 5);
return frost.loadUrl(url);
return Frost.loadUrl(url);
};
const handlers: EventHandler[] = [_frostLaunchpadClick, _frostGeneral];
const _frostAClick = async (e: Event) => {
const _frostAClick = (e: Event) => {
if (prevented) {
console.log("Click intercept prevented");
return
@ -75,9 +74,8 @@
console.log("No element found");
return
}
// TODO cannot use await here; copy logic over here
for (const h of handlers) {
if (await h(e, target)) {
if (h(e, target)) {
e.stopPropagation();
e.preventDefault();
return

View File

@ -0,0 +1,15 @@
// For desktop only
(function () {
const _frostAContext = (e: Event) => {
// Commonality; check for valid target
const element = e.target || e.currentTarget || e.srcElement;
if (!(element instanceof Element)) {
console.log("No element found");
return
}
console.log(`Clicked element ${element.tagName} ${element.className}`);
};
document.addEventListener('contextmenu', _frostAContext, true);
}).call(undefined);

View File

@ -0,0 +1,145 @@
/**
* Context menu for links
* Largely mimics click_a.js
*/
(function () {
let longClick = false;
/**
* Go up at most [depth] times, to retrieve a parent matching the provided predicate
* If one is found, it is returned immediately.
* Otherwise, null is returned.
*/
function _parentEl(el: HTMLElement, depth: number, predicate: (el: HTMLElement) => boolean): HTMLElement | null {
for (let i = 0; i < depth + 1; i++) {
if (predicate(el)) {
return el
}
const parent = el.parentElement;
if (!parent) {
return null
}
el = parent
}
return null
}
/**
* Given event and target, return true if handled and false otherwise.
*/
type EventHandler = (e: Event, target: HTMLElement) => Boolean
const _frostCopyComment: EventHandler = (e, target) => {
if (!target.hasAttribute('data-commentid')) {
return false;
}
const text = target.innerText;
console.log(`Copy comment ${text}`);
Frost.contextMenu(null, text);
return true;
};
/**
* Posts should click a tag, with two parents up being div.story_body_container
*/
const _frostCopyPost: EventHandler = (e, target) => {
if (target.tagName !== 'A') {
return false;
}
const parent1 = target.parentElement;
if (!parent1 || parent1.tagName !== 'DIV') {
return false;
}
const parent2 = parent1.parentElement;
if (!parent2 || !parent2.classList.contains('story_body_container')) {
return false;
}
const url = target.getAttribute('href');
const text = parent1.innerText;
console.log(`Copy post ${url} ${text}`);
Frost.contextMenu(url, text);
return true;
};
const _getImageStyleUrl = (el: Element): string | null => {
// Emojis and special characters may be images from a span
const img = el.querySelector("[style*=\"background-image: url(\"]:not(span)");
if (!img) {
return null
}
return (<String>window.getComputedStyle(img, null).backgroundImage).trim().slice(4, -1);
};
/**
* Opens image activity for posts with just one image
*/
const _frostImage: EventHandler = (e, target) => {
const element = _parentEl(target, 2, (el) => el.tagName === 'A');
if (!element) {
return false;
}
const url = element.getAttribute('href');
if (!url || url === '#') {
return false;
}
const text = (<HTMLElement>element.parentElement).innerText;
// Check if image item exists, first in children and then in parent
const imageUrl = _getImageStyleUrl(element) || _getImageStyleUrl(<Element>element.parentElement);
if (imageUrl) {
console.log(`Context image: ${imageUrl}`);
Frost.loadImage(imageUrl, text);
return true;
}
// Check if true img exists
const img = element.querySelector("img[src*=scontent]");
if (img instanceof HTMLMediaElement) {
const imgUrl = img.src;
console.log(`Context img: ${imgUrl}`);
Frost.loadImage(imgUrl, text);
return true;
}
console.log(`Context content ${url} ${text}`);
Frost.contextMenu(url, text);
return true;
};
const handlers: EventHandler[] = [_frostImage, _frostCopyComment, _frostCopyPost];
const _frostAContext = (e: Event) => {
Frost.longClick(true);
longClick = true;
/**
* Don't handle context events while scrolling
*/
if (Frost.isScrolling()) {
console.log("Skip from scrolling");
return;
}
/*
* Commonality; check for valid target
*/
const target = e.target || e.currentTarget || e.srcElement;
if (!(target instanceof HTMLElement)) {
console.log("No element found");
return
}
for (const h of handlers) {
if (h(e, target)) {
e.stopPropagation();
e.preventDefault();
return
}
}
};
document.addEventListener('contextmenu', _frostAContext, true);
document.addEventListener('touchend', () => {
if (longClick) {
Frost.longClick(false);
longClick = false
}
}, true);
}).call(undefined);

View File

@ -0,0 +1,27 @@
// Emit key once half the viewport is covered
(function () {
const isReady = () => {
return document.body.scrollHeight > innerHeight + 100
};
if (isReady()) {
console.log('Already ready');
Frost.isReady();
return
}
console.log('Injected document watcher');
const observer = new MutationObserver(() => {
if (isReady()) {
observer.disconnect();
Frost.isReady();
console.log(`Documented surpassed height in ${performance.now()}`);
}
});
observer.observe(document, {
childList: true,
subtree: true
})
}).call(undefined);

View File

@ -1,21 +0,0 @@
/**
* Mobile browsers don't support modules, so I'm creating a shared variable.
*
* No idea if this is good practice.
*/
const frost = (function () {
const application = "frostChannel"
async function sendMessage<T>(message: ExtensionModel): Promise<T> {
return browser.runtime.sendNativeMessage(application, message)
}
async function loadUrl(url: string | null): Promise<boolean> {
if (url == null) return false
return sendMessage({ type: "url-click", url: url })
}
return {
sendMessage, loadUrl
}
}).call(undefined);

View File

@ -0,0 +1,7 @@
// Fetches the header contents if it exists
(function() {
const header = document.getElementById('header');
if (header) {
Frost.handleHeader(header.outerHTML);
}
}).call(undefined);

View File

@ -0,0 +1,61 @@
(function () {
/**
* Go up at most [depth] times, to retrieve a parent matching the provided predicate
* If one is found, it is returned immediately.
* Otherwise, null is returned.
*/
function _parentEl(el: HTMLElement, depth: number, predicate: (el: HTMLElement) => boolean): HTMLElement | null {
for (let i = 0; i < depth + 1; i++) {
if (predicate(el)) {
return el
}
const parent = el.parentElement;
if (!parent) {
return null
}
el = parent
}
return null
}
/**
* Check if element can scroll horizontally.
* We primarily rely on the overflow-x field.
* For performance reasons, we will check scrollWidth first to see if scrolling is a possibility
*/
function _canScrollHorizontally(el: HTMLElement): boolean {
/*
* Sometimes the offsetWidth is off by < 10px. We use the multiplier
* since the trays are typically more than 2 times greater
*/
if (el.scrollWidth > el.offsetWidth * 1.2) {
return true
}
const styles = window.getComputedStyle(el);
/*
* Works well in testing, but on mobile it just shows 'visible'
*/
return styles.overflowX === 'scroll';
}
const _frostCheckHorizontalScrolling = (e: Event) => {
const target = e.target || e.currentTarget || e.srcElement;
if (!(target instanceof HTMLElement)) {
return
}
const scrollable = _parentEl(target, 5, _canScrollHorizontally) !== null;
if (scrollable) {
console.log('Pause horizontal scrolling');
Frost.allowHorizontalScrolling(false);
}
};
const _frostResetHorizontalScrolling = (e: Event) => {
Frost.allowHorizontalScrolling(true)
};
document.addEventListener('touchstart', _frostCheckHorizontalScrolling, true);
document.addEventListener('touchend', _frostResetHorizontalScrolling, true);
}).call(undefined);

View File

@ -0,0 +1,47 @@
// Handles media events
(function () {
const _frostMediaClick = (e: Event) => {
const target = e.target || e.srcElement;
if (!(target instanceof HTMLElement)) {
return
}
let element: HTMLElement = target;
const dataset = element.dataset;
if (!dataset || !dataset.sigil || dataset.sigil.toLowerCase().indexOf('inlinevideo') == -1) {
return
}
let i = 0;
while (!element.hasAttribute('data-store')) {
if (++i > 2) {
return
}
element = <HTMLElement>element.parentNode;
}
const store = element.dataset.store;
if (!store) {
return
}
let dataStore;
try {
dataStore = JSON.parse(store)
} catch (e) {
return
}
const url = dataStore.src;
// !startsWith; see https://stackoverflow.com/a/36876507/4407321
if (!url || url.lastIndexOf('http', 0) !== 0) {
return
}
console.log(`Inline video ${url}`);
if (Frost.loadVideo(url, dataStore.animatedGifVideo || false)) {
e.stopPropagation()
}
};
document.addEventListener('click', _frostMediaClick, true);
}).call(undefined);

View File

@ -0,0 +1,25 @@
// Binds callback to an invisible webview to take in the search events
(function () {
let finished = false;
const x = new MutationObserver(() => {
const _f_thread = document.querySelector('#threadlist_rows');
if (!_f_thread) {
return
}
console.log(`Found message threads ${_f_thread.outerHTML}`);
Frost.handleHtml(_f_thread.outerHTML);
finished = true;
x.disconnect();
});
x.observe(document, {
childList: true,
subtree: true
});
setTimeout(() => {
if (!finished) {
finished = true;
console.log('Message thread timeout cancellation');
Frost.handleHtml("")
}
}, 20000);
}).call(undefined);

View File

@ -0,0 +1,25 @@
// Listen when scrolling events stop
(function () {
let scrollTimeout: number | undefined = undefined;
let scrolling: boolean = false;
window.addEventListener('scroll', function (event) {
if (!scrolling) {
Frost.setScrolling(true);
scrolling = true;
}
window.clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(function () {
if (scrolling) {
Frost.setScrolling(false);
scrolling = false;
}
}, 600);
// For our specific use case, we want to release other features pretty far after scrolling stops
// For general scrolling use cases, the delta can be much smaller
// My assumption for context menus is that the long press is 500ms
}, false);
}).call(undefined);

View File

@ -0,0 +1,31 @@
/*
* focus listener for textareas
* since swipe to refresh is quite sensitive, we will disable it
* when we detect a user typing
* note that this extends passed having a keyboard opened,
* as a user may still be reviewing his/her post
* swiping should automatically be reset on refresh
*/
(function () {
const _frostFocus = (e: Event) => {
const element = e.target || e.srcElement;
if (!(element instanceof Element)) {
return
}
console.log(`FrostJSI focus, ${element.tagName}`);
if (element.tagName === 'TEXTAREA') {
Frost.disableSwipeRefresh(true);
}
};
const _frostBlur = (e: Event) => {
const element = e.target || e.srcElement;
if (!(element instanceof Element)) {
return
}
console.log(`FrostJSI blur, ${element.tagName}`);
Frost.disableSwipeRefresh(false);
};
document.addEventListener("focus", _frostFocus, true);
document.addEventListener("blur", _frostBlur, true);
}).call(undefined);

View File

@ -16,7 +16,7 @@
"allowUnreachableCode": true,
"allowUnusedLabels": true,
"removeComments": true,
"outDir": "assets/frostcore/js"
"outDir": "assets/frost/js"
},
"include": [
"ts",

View File

@ -1,7 +0,0 @@
declare namespace browser.runtime {
interface Port {
postMessage: (message: string) => void;
postMessage: (message: ExtensionModel) => void;
}
function sendNativeMessage(application: string, message: ExtensionModel): Promise<any>;
}

View File

@ -1,11 +1,33 @@
type TestModel = {
type: 'test-model'
message: string
declare interface FrostJSI {
loadUrl(url: string | null): boolean
loadVideo(url: string | null, isGif: boolean): boolean
reloadBaseUrl(animate: boolean)
contextMenu(url: string | null, text: string | null)
longClick(start: boolean)
disableSwipeRefresh(disable: boolean)
loadLogin()
loadImage(imageUrl: string, text: string | null)
emit(flag: number)
isReady()
handleHtml(html: string | null)
handleHeader(html: string | null)
allowHorizontalScrolling(enable: boolean)
setScrolling(scrolling: boolean)
isScrolling(): boolean
}
type UrlClickModel = {
type: 'url-click'
url: string
}
type ExtensionModel = TestModel | UrlClickModel
declare var Frost: FrostJSI;