diff options
Diffstat (limited to 'src')
24 files changed, 3537 insertions, 0 deletions
diff --git a/src/config.def.h b/src/config.def.h new file mode 100644 index 0000000..4ba77e6 --- /dev/null +++ b/src/config.def.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2022 mini-rose + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and any associated documentation + * files to modify, copy, merge, publish, distribute and/or + * sublicense copies of this sotware for their own use. + * This code does not come with any warranty. + * + * Author: fenze <contact@fenze.dev> + */ + +#include <gdk/gdkkeysyms.h> + +/* See more: + * https://webkitgtk.org/reference/webkit2gtk/stable/class.Settings.html */ +#define WEBKIT \ + "enable-back-forward-navigation-gestures", true, \ + "enable-developer-extras", true, \ + "enable-smooth-scrolling", false + +#define GTK \ + "gtk-application-prefer-dark-theme", true, \ + "gtk-enable-animations", false + +#define HOME "https://lite.duckduckgo.com/html/" +#define SEARCH "https://lite.duckduckgo.com/html/?q=%s" +#define CACHE_DIR "/home/fenze/.cache/rose" + +#define KEY(x) GDK_KEY_##x +#define ZOOM 1 /* Starting zoom level */ +#define ZOOM_VAL .1 /* Zooming value in zoomin/zoomout functions */ +#define BG_COLOR "#1E1E2E" /* or e.g., "#FEFEFE" if not using dark theme */ +#define WIDTH 500 +#define HEIGHT 400 + +typedef enum { + goback, + goforward, + refresh, + refresh_force, + back_to_home, + toggle_fullscreen, + zoomin, + zoomout, + zoom_reset, + next_tab, + prev_tab, + close_tab, + show_searchbar, + show_finder, + finder_next, + finder_prev, + newtab, + /*prettify,*/ + hidebar +} func; + +#define SFT 1 << 0 +#define CTRL 1 << 2 +#define ALT 1 << 3 + +static struct { + unsigned mod; + unsigned key; + func id; +} keys[] = { + { CTRL, KEY(h), goback }, + { CTRL, KEY(l), goforward }, + { CTRL, KEY(r), refresh }, + { CTRL | SFT, KEY(R), refresh_force }, + { CTRL | SFT, KEY(H), back_to_home }, + { CTRL, KEY(equal), zoomin }, + { CTRL, KEY(minus), zoomout }, + { CTRL, KEY(0), zoom_reset }, + { ALT, KEY(h), prev_tab }, + { ALT, KEY(l), next_tab }, + { CTRL, KEY(w), close_tab }, + { 0x0, KEY(F11), toggle_fullscreen }, + { CTRL, KEY(e), show_searchbar }, + { CTRL, KEY(f), show_finder }, + { CTRL, KEY(n), finder_next }, + { CTRL | SFT, KEY(N), finder_prev }, + { CTRL, KEY(t), newtab }, + { CTRL, KEY(p), prettify }, + { 0x0, KEY(Escape), hidebar } +}; +/* For controls more akin to normal browsers, use: +{ + { CTRL, KEY(h), goback }, + { CTRL, KEY(j), goforward }, + { CTRL, KEY(r), refresh }, + { CTRL | SFT, KEY(R), refresh_force }, + { CTRL | SFT, KEY(H), back_to_home }, + { CTRL, KEY(equal), zoomin }, + { CTRL, KEY(minus), zoomout }, + { CTRL, KEY(0), zoom_reset }, + { CTRL, KEY(Page_Down), prev_tab }, + { CTRL, KEY(Page_Up), next_tab }, + { CTRL, KEY(w), close_tab }, + { 0x0, KEY(F11), toggle_fullscreen }, + { CTRL, KEY(l), show_searchbar }, + { CTRL, KEY(f), show_finder }, + { CTRL, KEY(n), finder_next }, + { CTRL | SFT, KEY(N), finder_prev }, + { CTRL, KEY(p), prettify }, + +}; +*/ +/* Reference for the key shorthand: + * <https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gdk/gdkkeysyms.h> */ diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..7a4e3bf --- /dev/null +++ b/src/config.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022 Nuño Sempere. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and any associated documentation + * files to modify, copy, merge, publish, distribute and/or + * sublicense copies of this sotware for their own use. + * This code does not come with any warranty. + * + */ + +#include <gdk/gdkkeysyms.h> +#include <stdbool.h> + +/* See more: + * https://webkitgtk.org/reference/webkit2gtk/stable/class.Settings.html */ +#define WEBKIT \ + "enable-back-forward-navigation-gestures", true, "enable-developer-extras", true, \ + "enable-smooth-scrolling", false + +#define GTK "gtk-application-prefer-dark-theme", false, "gtk-enable-animations", false +#define ROSE_HOMEPAGE true +#define HOME ROSE_HOMEPAGE ? "file:///home/loki/Documents/core/software/fresh/C/rose-browser/rosenrot/user-scripts/ubuntu-20.04/rose-images/rose-homepage.png" : "https://lite.duckduckgo.com/html" +#define SEARCH "https://lite.duckduckgo.com/html/?q=%s" +#define CACHE_DIR "/home/loki/.cache/rose" + +#define WIDTH 1920 +#define HEIGHT 1080 +#define KEY(x) GDK_KEY_##x +#define ZOOM 1.4 /* Starting zoom level.*/ +#define ZOOM_VAL .1 /* Zooming value in zoomin/zoomout functions */ +#define BG_COLOR "#FEFEFE" /* "FEFEFE", "#1E1E2E" */ +#define DEBUG false + +typedef enum { + goback, + goforward, + refresh, + refresh_force, + back_to_home, + toggle_fullscreen, + zoomin, + zoomout, + zoom_reset, + next_tab, + prev_tab, + close_tab, + show_searchbar, + show_finder, + finder_next, + finder_prev, + new_tab, + prettify, + hide_bar +} func; + +#define SFT 1 << 0 +#define CTRL 1 << 2 +#define ALT 1 << 3 + +static struct { + unsigned mod; + unsigned key; + func id; +} keys[] = { + { CTRL, KEY(h), goback }, + { CTRL, KEY(j), goforward }, + { CTRL, KEY(r), refresh }, + { CTRL | SFT, KEY(R), refresh_force }, + { CTRL | SFT, KEY(H), back_to_home }, + { CTRL, KEY(equal), zoomin }, + { CTRL, KEY(minus), zoomout }, + { CTRL, KEY(0), zoom_reset }, + { CTRL, KEY(Page_Up), prev_tab }, + { CTRL, KEY(Page_Down), next_tab }, + { CTRL, KEY(t), next_tab }, + { CTRL, KEY(w), close_tab }, + { 0x0, KEY(F11), toggle_fullscreen }, + { CTRL, KEY(l), show_searchbar }, + { CTRL, KEY(k), hide_bar }, + { CTRL, KEY(f), show_finder }, + { CTRL, KEY(n), finder_next }, + { CTRL | SFT, KEY(N), finder_prev }, + { CTRL, KEY(p), prettify } +}; +/* ^ For controls more akin to normal browsers */ +/* Reference for the key shorthand: + * <https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gdk/gdkkeysyms.h> */ + +/* Old controls: { + { CTRL, KEY(h), goback }, + { CTRL, KEY(l), goforward }, + { CTRL, KEY(r), refresh }, + { CTRL | SFT, KEY(R), refresh_force }, + { CTRL | SFT, KEY(H), back_to_home }, + { CTRL, KEY(equal), zoomin }, + { CTRL, KEY(minus), zoomout }, + { CTRL, KEY(0), zoom_reset }, + { ALT, KEY(h), prev_tab }, + { CTRL, KEY(k), hide_searchbar }, + { ALT, KEY(l), next_tab }, + { CTRL, KEY(w), close_tab }, + { 0x0, KEY(F11), toggle_fullscreen }, + { CTRL, KEY(e), show_searchbar }, + { CTRL, KEY(f), show_finder }, + { CTRL, KEY(n), finder_next }, + { CTRL | SFT, KEY(N), finder_prev }, + { CTRL, KEY(p), prettify } +}; +*/ diff --git a/src/plugins/libre_redirect/README.md b/src/plugins/libre_redirect/README.md new file mode 100644 index 0000000..f3f58c9 --- /dev/null +++ b/src/plugins/libre_redirect/README.md @@ -0,0 +1,3 @@ +## About + +This code automatically redirects webpage to their open-source frontends. It is based on <https://libredirect.codeberg.page/>
\ No newline at end of file diff --git a/src/plugins/libre_redirect/libre_redirect.c b/src/plugins/libre_redirect/libre_redirect.c new file mode 100644 index 0000000..3794add --- /dev/null +++ b/src/plugins/libre_redirect/libre_redirect.c @@ -0,0 +1,84 @@ +#include <stdbool.h> +#include <stdio.h> +#include <string.h> + +#include "str_replace_start.h" + +#define LIBRE_N 19 + +/* Uncomment for debug */ +/* #define DEBUG */ + +/* Inspired by https://libredirect.github.io/, but in C. */ + +void str_init(char* str, int n) +{ + for (int i = 0; i < n; i++) + str[i] = ' '; + str[n] = '\0'; +} // could also use <https://manpages.ubuntu.com/manpages/impish/man3/strinit.3pub.html> + +int libre_redirect(const char* uri, char* output) +{ + int l1 = strlen(uri); + int l2 = strlen(output); + int len; + char tmp_uri[l2++]; + char tmp_output[l2++]; + + if ((l2 - l1) < LIBRE_N) { +#ifdef DEBUG + printf("Not enough memory\n"); +#endif + return 1; // not enough memory. + } else { + strcpy(tmp_uri, uri); // strcpy also copies the terminating '\0' + strcpy(tmp_output, output); + + char* annoying_sites[] = { + "https://www.youtube.com", + "https://www.reddit.com", + "https://medium.com", + "https://translate.google.com", + // "https://forum.effectivealtruism.org", + "https://www.bloomberg.com", + "https://twitter.com" + }; + + char* alternatives[] = { + "https://yt.artemislena.eu", + "https://teddit.nunosempere.com", + "https://scribe.rip", + "https://simplytranslate.org/", + // "https://ea.greaterwrong.com", + "https://archive.is/https://www.bloomberg.com", + "https://nitter.net" + }; + + len = sizeof(annoying_sites) / sizeof(annoying_sites[0]); + + for (int i = 0; i < len; i++) { + int replace_check = str_replace_start(tmp_uri, annoying_sites[i], + alternatives[i], output); + if (replace_check == 2) { +#ifdef DEBUG + printf("tmp_uri: %s\n", tmp_uri); + printf("output: %s\n", output); +#endif + return 2; + } else if (replace_check == 1) { +#ifdef DEBUG + printf("replace_check failed\n"); +#endif + return 1; + } + strcpy(tmp_uri, output); + str_init(output, l2); + } + strcpy(output, tmp_uri); + } +#ifdef DEBUG + printf("No match found\n\n"); +#endif + return 0; +} diff --git a/src/plugins/libre_redirect/libre_redirect.h b/src/plugins/libre_redirect/libre_redirect.h new file mode 100644 index 0000000..150c95e --- /dev/null +++ b/src/plugins/libre_redirect/libre_redirect.h @@ -0,0 +1,6 @@ +#pragma once + +#define LIBRE_N 19 + +int libre_redirect(const char* uri, char* uri_filtered); +void str_init(char* str, int n); diff --git a/src/plugins/libre_redirect/str_replace_start.c b/src/plugins/libre_redirect/str_replace_start.c new file mode 100644 index 0000000..d1fe083 --- /dev/null +++ b/src/plugins/libre_redirect/str_replace_start.c @@ -0,0 +1,65 @@ +#include <stdbool.h> +#include <stdio.h> +#include <string.h> + +#define DEBUG false + +/* +See also: +* <https://web.archive.org/web/20160201212501/coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring> +* https://github.com/irl/la-cucina/blob/master/str_replace.c +*/ + +int str_replace_start(const char* string, const char* target, const char* replacement, char* output) +{ + int l1 = strlen(string); + int l2 = strlen(target); + int l3 = strlen(replacement); + int l4 = strlen(output); + if (DEBUG) + printf("%d,%d,%d,%d\n", l1, l2, l3, l4); + // if(DEBUG) printf("%s,%s,%s,%s\n", string, target, replacement, output); + + if ((l4 < (l1 - l2 + l3)) || l4 < l1) { + // Not enough memory in output string. + if (DEBUG) + printf("String not long enough.\n"); + return 1; + } + /* else if(l1 < l2){ + // Not even possible that there is a match. + if(DEBUG) printf("Target larger than string.\n"); + strcpy(output, string); + } */ + else { + if (DEBUG) + printf("Looking for a match for %s in %s.\n", target, string); + int match = true; + for (int i = 0; i < l2; i++) { + if (string[i] != target[i]) { + match = false; + break; + } + } + if (match) { + if (DEBUG) + printf("Found match.\n"); + for (int i = 0; i < l3; i++) { + output[i] = replacement[i]; + } + int counter = l3; + for (int i = l2; i < l1; i++) { + output[counter] = string[i]; + counter++; + } + output[counter] = '\0'; + return 2; // success + } else { + if (DEBUG) + printf("Did not find match.\n"); + strcpy(output, string); + } + } + + return 0; +} diff --git a/src/plugins/libre_redirect/str_replace_start.h b/src/plugins/libre_redirect/str_replace_start.h new file mode 100644 index 0000000..78c79b5 --- /dev/null +++ b/src/plugins/libre_redirect/str_replace_start.h @@ -0,0 +1,4 @@ +#pragma once + +int str_replace_start(const char* string, const char* target, + const char* replacement, char* output); diff --git a/src/plugins/libre_redirect/str_replace_test/build-example.sh b/src/plugins/libre_redirect/str_replace_test/build-example.sh new file mode 100644 index 0000000..a9f8022 --- /dev/null +++ b/src/plugins/libre_redirect/str_replace_test/build-example.sh @@ -0,0 +1,11 @@ +#!/bin/bash +CC=gcc + +FLAGS="-std=c99 -Wall -lm" + +SRC=example.c +REQS="../str_replace_start.c ../libre_redirect.c" + +echo -e "\n\n\n" +$CC $FLAGS $SRC $REQS -o example + diff --git a/src/plugins/libre_redirect/str_replace_test/example.c b/src/plugins/libre_redirect/str_replace_test/example.c new file mode 100644 index 0000000..b93634a --- /dev/null +++ b/src/plugins/libre_redirect/str_replace_test/example.c @@ -0,0 +1,19 @@ +#include "../libre_redirect.h" +#include <stdio.h> +#include <string.h> + +int main() +{ + char uri[] = "https://reddit.com/r/blah"; + + int l = LIBRE_N + strlen(uri) + 1; + char uri_filtered[l]; + str_init(uri_filtered, l); + + if (!libre_redirect(uri, uri_filtered)) { + printf("Filtered uri: %s\n", uri_filtered); + } else { + printf("Uri: %s\n", uri); + // failure; do something with the original uri. + } +} diff --git a/src/plugins/readability/README.md b/src/plugins/readability/README.md new file mode 100644 index 0000000..2a19a8e --- /dev/null +++ b/src/plugins/readability/README.md @@ -0,0 +1,2 @@ +This code reimplements firefox readability mode. Code taken from <https://raw.githubusercontent.com/ushnisha/readability-reader-webextensions/master/content_scripts/tranquilize.js> + diff --git a/src/plugins/readability/readability b/src/plugins/readability/readability Binary files differnew file mode 100755 index 0000000..2c70985 --- /dev/null +++ b/src/plugins/readability/readability diff --git a/src/plugins/readability/readability.c b/src/plugins/readability/readability.c new file mode 100644 index 0000000..bc2f02f --- /dev/null +++ b/src/plugins/readability/readability.c @@ -0,0 +1,30 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#define READABILITY_N 85133 + 1000 + +void read_readability_js(char* string) +{ + FILE* fp = fopen("/home/loki/Documents/core/software/fresh/C/rose-browser/rosenrot/plugins/readability/readability.js", "r"); + if (!fp) { // fp is NULL, fopen failed + fprintf(stderr, "Failed to open file\n"); + string = NULL; + return; + } + int i = 0; + int c; + while ((c = fgetc(fp)) != EOF) { + string[i++] = c; + } + string[i] = '\0'; + fclose(fp); +} + +/* +int main(){ + char* readability_js = malloc(READABILITY_N+1); + read_readability_js(readability_js); + printf("%s", readability_js); + free(readability_js); +} +*/ diff --git a/src/plugins/readability/readability.h b/src/plugins/readability/readability.h new file mode 100644 index 0000000..6a21a8c --- /dev/null +++ b/src/plugins/readability/readability.h @@ -0,0 +1,8 @@ +#ifndef READABILITY +#define READABILITY + +#define READABILITY_N 85133 + 1000 + +void read_readability_js(char* string); + +#endif diff --git a/src/plugins/readability/readability.js b/src/plugins/readability/readability.js new file mode 100644 index 0000000..0532bba --- /dev/null +++ b/src/plugins/readability/readability.js @@ -0,0 +1,2401 @@ +/*eslint-env es6:false*/ +/* + * Copyright (c) 2010 Arc90 Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This code is heavily based on Arc90's readability.js (1.7.1) script + * available at: http://code.google.com/p/arc90labs-readability + */ + +/** + * Public constructor. + * @param {HTMLDocument} doc The document to parse. + * @param {Object} options The options object. + */ +function Readability(doc, options) { + // In some older versions, people passed a URI as the first argument. Cope: + if (options && options.documentElement) { + doc = options; + options = arguments[2]; + } else if (!doc || !doc.documentElement) { + throw new Error("First argument to Readability constructor should be a document object."); + } + options = options || {}; + + this._doc = doc; + this._docJSDOMParser = this._doc.firstChild.__JSDOMParser__; + this._articleTitle = null; + this._articleByline = null; + this._articleDir = null; + this._articleSiteName = null; + this._attempts = []; + + // Configurable options + this._debug = !!options.debug; + this._maxElemsToParse = options.maxElemsToParse || this.DEFAULT_MAX_ELEMS_TO_PARSE; + this._nbTopCandidates = options.nbTopCandidates || this.DEFAULT_N_TOP_CANDIDATES; + this._charThreshold = options.charThreshold || this.DEFAULT_CHAR_THRESHOLD; + this._classesToPreserve = this.CLASSES_TO_PRESERVE.concat(options.classesToPreserve || []); + this._keepClasses = !!options.keepClasses; + this._serializer = options.serializer || function(el) { + return el.innerHTML; + }; + this._disableJSONLD = !!options.disableJSONLD; + + // Start with all flags set + this._flags = this.FLAG_STRIP_UNLIKELYS | + this.FLAG_WEIGHT_CLASSES | + this.FLAG_CLEAN_CONDITIONALLY; + + + // Control whether log messages are sent to the console + if (this._debug) { + let logNode = function(node) { + if (node.nodeType == node.TEXT_NODE) { + return `${node.nodeName} ("${node.textContent}")`; + } + let attrPairs = Array.from(node.attributes || [], function(attr) { + return `${attr.name}="${attr.value}"`; + }).join(" "); + return `<${node.localName} ${attrPairs}>`; + }; + this.log = function () { + if (typeof dump !== "undefined") { + var msg = Array.prototype.map.call(arguments, function(x) { + return (x && x.nodeName) ? logNode(x) : x; + }).join(" "); + dump("Reader: (Readability) " + msg + "\n"); + } else if (typeof console !== "undefined") { + let args = Array.from(arguments, arg => { + if (arg && arg.nodeType == this.ELEMENT_NODE) { + return logNode(arg); + } + return arg; + }); + args.unshift("Reader: (Readability)"); + console.log.apply(console, args); + } + }; + } else { + this.log = function () {}; + } +} + +Readability.prototype = { + FLAG_STRIP_UNLIKELYS: 0x1, + FLAG_WEIGHT_CLASSES: 0x2, + FLAG_CLEAN_CONDITIONALLY: 0x4, + + // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType + ELEMENT_NODE: 1, + TEXT_NODE: 3, + + // Max number of nodes supported by this parser. Default: 0 (no limit) + DEFAULT_MAX_ELEMS_TO_PARSE: 0, + + // The number of top candidates to consider when analysing how + // tight the competition is among candidates. + DEFAULT_N_TOP_CANDIDATES: 5, + + // Element tags to score by default. + DEFAULT_TAGS_TO_SCORE: "section,h2,h3,h4,h5,h6,p,td,pre".toUpperCase().split(","), + + // The default number of chars an article must have in order to return a result + DEFAULT_CHAR_THRESHOLD: 500, + + // All of the regular expressions in use within readability. + // Defined up here so we don't instantiate them repeatedly in loops. + REGEXPS: { + // NOTE: These two regular expressions are duplicated in + // Readability-readerable.js. Please keep both copies in sync. + unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i, + okMaybeItsACandidate: /and|article|body|column|content|main|shadow/i, + + positive: /article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story/i, + negative: /-ad-|hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i, + extraneous: /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|utility/i, + byline: /byline|author|dateline|writtenby|p-author/i, + replaceFonts: /<(\/?)font[^>]*>/gi, + normalize: /\s{2,}/g, + videos: /\/\/(www\.)?((dailymotion|youtube|youtube-nocookie|player\.vimeo|v\.qq)\.com|(archive|upload\.wikimedia)\.org|player\.twitch\.tv)/i, + shareElements: /(\b|_)(share|sharedaddy)(\b|_)/i, + nextLink: /(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i, + prevLink: /(prev|earl|old|new|<|«)/i, + tokenize: /\W+/g, + whitespace: /^\s*$/, + hasContent: /\S$/, + hashUrl: /^#.+/, + srcsetUrl: /(\S+)(\s+[\d.]+[xw])?(\s*(?:,|$))/g, + b64DataUrl: /^data:\s*([^\s;,]+)\s*;\s*base64\s*,/i, + // See: https://schema.org/Article + jsonLdArticleTypes: /^Article|AdvertiserContentArticle|NewsArticle|AnalysisNewsArticle|AskPublicNewsArticle|BackgroundNewsArticle|OpinionNewsArticle|ReportageNewsArticle|ReviewNewsArticle|Report|SatiricalArticle|ScholarlyArticle|MedicalScholarlyArticle|SocialMediaPosting|BlogPosting|LiveBlogPosting|DiscussionForumPosting|TechArticle|APIReference$/ + }, + + UNLIKELY_ROLES: [ "menu", "menubar", "complementary", "navigation", "alert", "alertdialog", "dialog" ], + + DIV_TO_P_ELEMS: new Set([ "BLOCKQUOTE", "DL", "DIV", "IMG", "OL", "P", "PRE", "TABLE", "UL" ]), + + ALTER_TO_DIV_EXCEPTIONS: ["DIV", "ARTICLE", "SECTION", "P"], + + PRESENTATIONAL_ATTRIBUTES: [ "align", "background", "bgcolor", "border", "cellpadding", "cellspacing", "frame", "hspace", "rules", "style", "valign", "vspace" ], + + DEPRECATED_SIZE_ATTRIBUTE_ELEMS: [ "TABLE", "TH", "TD", "HR", "PRE" ], + + // The commented out elements qualify as phrasing content but tend to be + // removed by readability when put into paragraphs, so we ignore them here. + PHRASING_ELEMS: [ + // "CANVAS", "IFRAME", "SVG", "VIDEO", + "ABBR", "AUDIO", "B", "BDO", "BR", "BUTTON", "CITE", "CODE", "DATA", + "DATALIST", "DFN", "EM", "EMBED", "I", "IMG", "INPUT", "KBD", "LABEL", + "MARK", "MATH", "METER", "NOSCRIPT", "OBJECT", "OUTPUT", "PROGRESS", "Q", + "RUBY", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRONG", "SUB", + "SUP", "TEXTAREA", "TIME", "VAR", "WBR" + ], + + // These are the classes that readability sets itself. + CLASSES_TO_PRESERVE: [ "page" ], + + // These are the list of HTML entities that need to be escaped. + HTML_ESCAPE_MAP: { + "lt": "<", + "gt": ">", + "amp": "&", + "quot": '"', + "apos": "'", + }, + + /** + * Run any post-process modifications to article content as necessary. + * + * @param Element + * @return void + **/ + _postProcessContent: function(articleContent) { + // Readability cannot open relative uris so we convert them to absolute uris. + this._fixRelativeUris(articleContent); + + this._simplifyNestedElements(articleContent); + + if (!this._keepClasses) { + // Remove classes. + this._cleanClasses(articleContent); + } + }, + + /** + * Iterates over a NodeList, calls `filterFn` for each node and removes node + * if function returned `true`. + * + * If function is not passed, removes all the nodes in node list. + * + * @param NodeList nodeList The nodes to operate on + * @param Function filterFn the function to use as a filter + * @return void + */ + _removeNodes: function(nodeList, filterFn) { + // Avoid ever operating on live node lists. + if (this._docJSDOMParser && nodeList._isLiveNodeList) { + throw new Error("Do not pass live node lists to _removeNodes"); + } + for (var i = nodeList.length - 1; i >= 0; i--) { + var node = nodeList[i]; + var parentNode = node.parentNode; + if (parentNode) { + if (!filterFn || filterFn.call(this, node, i, nodeList)) { + parentNode.removeChild(node); + } + } + } + }, + + /** + * Iterates over a NodeList, and calls _setNodeTag for each node. + * + * @param NodeList nodeList The nodes to operate on + * @param String newTagName the new tag name to use + * @return void + */ + _replaceNodeTags: function(nodeList, newTagName) { + // Avoid ever operating on live node lists. + if (this._docJSDOMParser && nodeList._isLiveNodeList) { + throw new Error("Do not pass live node lists to _replaceNodeTags"); + } + for (const node of nodeList) { + this._setNodeTag(node, newTagName); + } + }, + + /** + * Iterate over a NodeList, which doesn't natively fully implement the Array + * interface. + * + * For convenience, the current object context is applied to the provided + * iterate function. + * + * @param NodeList nodeList The NodeList. + * @param Function fn The iterate function. + * @return void + */ + _forEachNode: function(nodeList, fn) { + Array.prototype.forEach.call(nodeList, fn, this); + }, + + /** + * Iterate over a NodeList, and return the first node that passes + * the supplied test function + * + * For convenience, the current object context is applied to the provided + * test function. + * + * @param NodeList nodeList The NodeList. + * @param Function fn The test function. + * @return void + */ + _findNode: function(nodeList, fn) { + return Array.prototype.find.call(nodeList, fn, this); + }, + + /** + * Iterate over a NodeList, return true if any of the provided iterate + * function calls returns true, false otherwise. + * + * For convenience, the current object context is applied to the + * provided iterate function. + * + * @param NodeList nodeList The NodeList. + * @param Function fn The iterate function. + * @return Boolean + */ + _someNode: function(nodeList, fn) { + return Array.prototype.some.call(nodeList, fn, this); + }, + + /** + * Iterate over a NodeList, return true if all of the provided iterate + * function calls return true, false otherwise. + * + * For convenience, the current object context is applied to the + * provided iterate function. + * + * @param NodeList nodeList The NodeList. + * @param Function fn The iterate function. + * @return Boolean + */ + _everyNode: function(nodeList, fn) { + return Array.prototype.every.call(nodeList, fn, this); + }, + + /** + * Concat all nodelists passed as arguments. + * + * @return ...NodeList + * @return Array + */ + _concatNodeLists: function() { + var slice = Array.prototype.slice; + var args = slice.call(arguments); + var nodeLists = args.map(function(list) { + return slice.call(list); + }); + return Array.prototype.concat.apply([], nodeLists); + }, + + _getAllNodesWithTag: function(node, tagNames) { + if (node.querySelectorAll) { + return node.querySelectorAll(tagNames.join(",")); + } + return [].concat.apply([], tagNames.map(function(tag) { + var collection = node.getElementsByTagName(tag); + return Array.isArray(collection) ? collection : Array.from(collection); + })); + }, + + /** + * Removes the class="" attribute from every element in the given + * subtree, except those that match CLASSES_TO_PRESERVE and + * the classesToPreserve array from the options object. + * + * @param Element + * @return void + */ + _cleanClasses: function(node) { + var classesToPreserve = this._classesToPreserve; + var className = (node.getAttribute("class") || "") + .split(/\s+/) + .filter(function(cls) { + return classesToPreserve.indexOf(cls) != -1; + }) + .join(" "); + + if (className) { + node.setAttribute("class", className); + } else { + node.removeAttribute("class"); + } + + for (node = node.firstElementChild; node; node = node.nextElementSibling) { + this._cleanClasses(node); + } + }, + + /** + * Converts each <a> and <img> uri in the given element to an absolute URI, + * ignoring #ref URIs. + * + * @param Element |