Initial Chrome support

Part of #35
This commit is contained in:
Nils Maier 2019-08-31 18:10:47 +02:00
parent 0a9155dcec
commit 164aa99eca
25 changed files with 9334 additions and 9255 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,7 @@ import {
Tab, Tab,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
MenuClickInfo, MenuClickInfo,
CHROME,
} from "./browser"; } from "./browser";
import { Bus } from "./bus"; import { Bus } from "./bus";
import { filterInSitu } from "./util"; import { filterInSitu } from "./util";
@ -27,6 +28,20 @@ const menus = typeof (_menus) !== "undefined" && _menus || _cmenus;
const GATHER = "/bundles/content-gather.js"; const GATHER = "/bundles/content-gather.js";
const CHROME_CONTEXTS = Object.freeze(new Set([
"all",
"audio",
"browser_action",
"editable",
"frame",
"image",
"launcher",
"link",
"page",
"page_action",
"selection",
"video",
]));
async function runContentJob(tab: Tab, file: string, msg: any) { async function runContentJob(tab: Tab, file: string, msg: any) {
try { try {
@ -83,11 +98,15 @@ class Handler {
async performSelection(options: SelectionOptions) { async performSelection(options: SelectionOptions) {
try { try {
const selectedTabs = options.allTabs ? const toptions: any = {
await tabs.query({
currentWindow: true, currentWindow: true,
discarded: false, discarded: false,
hidden: false}) as any[] : };
if (!CHROME) {
toptions.hidden = true;
}
const selectedTabs = options.allTabs ?
await tabs.query(toptions) as any[] :
[options.tab]; [options.tab];
const textLinks = await Prefs.get("text-links", true); const textLinks = await Prefs.get("text-links", true);
@ -146,10 +165,18 @@ locale.then(() => {
this.onClicked = this.onClicked.bind(this); this.onClicked = this.onClicked.bind(this);
const alls = new Map<string, string[]>(); const alls = new Map<string, string[]>();
const mcreate = (options: any) => { const mcreate = (options: any) => {
if (CHROME) {
delete options.icons;
options.contexts = options.contexts.
filter((e: string) => CHROME_CONTEXTS.has(e));
if (!options.contexts.length) {
return;
}
}
if (options.contexts.includes("all")) { if (options.contexts.includes("all")) {
alls.set(options.id, options.contexts); alls.set(options.id, options.contexts);
} }
return menus.create(options); menus.create(options);
}; };
mcreate({ mcreate({
id: "DTARegularLink", id: "DTARegularLink",
@ -520,13 +547,21 @@ locale.then(() => {
function adjustAction(globalTurbo: boolean) { function adjustAction(globalTurbo: boolean) {
action.setPopup({ action.setPopup({
popup: globalTurbo ? "" : null popup: globalTurbo ? "" : "/windows/popup.html"
}); });
action.setIcon({ action.setIcon({
path: globalTurbo ? { path: globalTurbo ? {
16: "/style/button-turbo.png", 16: "/style/button-turbo.png",
32: "/style/button-turbo@2x.png", 32: "/style/button-turbo@2x.png",
} : null } : {
16: "/style/icon16.png",
32: "/style/icon32.png",
48: "/style/icon48.png",
64: "/style/icon64.png",
96: "/style/icon96.png",
128: "/style/icon128.png",
256: "/style/icon256.png"
}
}); });
} }

View File

@ -50,3 +50,5 @@ export const {storage} = polyfill;
export const {tabs} = polyfill; export const {tabs} = polyfill;
export const {webNavigation} = polyfill; export const {webNavigation} = polyfill;
export const {windows} = polyfill; export const {windows} = polyfill;
export const CHROME = navigator.appVersion.includes("Chrome/");

View File

@ -9,6 +9,8 @@ declare let chrome: any;
const CACHE_KEY = "_cached_locales"; const CACHE_KEY = "_cached_locales";
const CUSTOM_KEY = "_custom_locale"; const CUSTOM_KEY = "_custom_locale";
const normalizer = /[^A-Za-z0-9_]/g;
interface JSONEntry { interface JSONEntry {
message: string; message: string;
placeholders: any; placeholders: any;
@ -72,7 +74,7 @@ class Localization {
} }
localize(id: string, ...args: any[]) { localize(id: string, ...args: any[]) {
const entry = this.strings.get(id); const entry = this.strings.get(id.replace(normalizer, "_"));
if (!entry) { if (!entry) {
return ""; return "";
} }
@ -121,7 +123,8 @@ function loadCached() {
async function loadRawLocales() { async function loadRawLocales() {
// en is the base locale // en is the base locale
const langs = new Set<string>(["en"]); const langs = new Set<string>(["en"]);
const ui = (browser.i18n || chrome.i18n).getUILanguage(); const ui = (typeof browser !== "undefined" ? browser : chrome).
i18n.getUILanguage();
langs.add(ui); langs.add(ui);
// Try the base too // Try the base too

View File

@ -1,14 +1,14 @@
"use strict"; "use strict";
// License: MIT // License: MIT
import { downloads } from "./browser"; import { downloads, CHROME } from "./browser";
import { EventEmitter } from "../uikit/lib/events"; import { EventEmitter } from "../uikit/lib/events";
import { PromiseSerializer } from "./pserializer"; import { PromiseSerializer } from "./pserializer";
const VERSION = 1; const VERSION = 1;
const STORE = "iconcache"; const STORE = "iconcache";
// eslint-disable-next-line no-magic-numbers // eslint-disable-next-line no-magic-numbers
const CACHE_SIZES = [16, 32, 64, 127]; const CACHE_SIZES = CHROME ? [16, 32] : [16, 32, 64, 127];
const BLACKLISTED = Object.freeze(new Set([ const BLACKLISTED = Object.freeze(new Set([
"", "",
@ -28,7 +28,8 @@ const BLACKLISTED = Object.freeze(new Set([
])); ]));
async function getIcon(size: number, manId: number) { async function getIcon(size: number, manId: number) {
const icon = new URL(await downloads.getFileIcon(manId, {size})); const raw = await downloads.getFileIcon(manId, {size});
const icon = new URL(raw);
if (icon.protocol === "data:") { if (icon.protocol === "data:") {
const res = await fetch(icon.toString()); const res = await fetch(icon.toString());
const blob = await res.blob(); const blob = await res.blob();

View File

@ -11,14 +11,16 @@ import { BaseDownload } from "./basedownload";
import { PromiseSerializer } from "../pserializer"; import { PromiseSerializer } from "../pserializer";
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { Manager } from "./man"; import { Manager } from "./man";
import { downloads } from "../browser"; import { downloads, CHROME } from "../browser";
import { IconCache } from "../iconcache"; import { debounce } from "../../uikit/lib/util";
const setShelfEnabled = downloads.setShelfEnabled || function() { const setShelfEnabled = downloads.setShelfEnabled || function() {
// ignored // ignored
}; };
const reenableShelf = debounce(() => setShelfEnabled(true), 1000, true);
type Header = {name: string; value: string}; type Header = {name: string; value: string};
interface Options { interface Options {
conflictAction: string; conflictAction: string;
@ -27,7 +29,7 @@ interface Options {
url: string; url: string;
method?: string; method?: string;
body?: string; body?: string;
incognito: boolean; incognito?: boolean;
headers: Header[]; headers: Header[];
} }
@ -104,13 +106,15 @@ export class Download extends BaseDownload {
saveAs: false, saveAs: false,
url: this.url, url: this.url,
headers: [], headers: [],
incognito: this.private
}; };
if (!CHROME && this.private) {
options.incognito = true;
}
if (this.postData) { if (this.postData) {
options.body = this.postData; options.body = this.postData;
options.method = "POST"; options.method = "POST";
} }
if (this.referrer) { if (!CHROME && this.referrer) {
options.headers.push({ options.headers.push({
name: "Referer", name: "Referer",
value: this.referrer value: this.referrer
@ -135,12 +139,9 @@ export class Download extends BaseDownload {
this.manager.addManId( this.manager.addManId(
this.manId = await downloads.download(options), this); this.manId = await downloads.download(options), this);
} }
await IconCache.
set(this.renamer.p_ext, this.manId).
catch(console.error);
} }
finally { finally {
setShelfEnabled(true); reenableShelf();
} }
this.markDirty(); this.markDirty();
} }

View File

@ -5,7 +5,6 @@ import { donate, openPrefs } from "../windowutils";
import { API } from "../api"; import { API } from "../api";
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { BaseDownload } from "./basedownload"; import { BaseDownload } from "./basedownload";
import { IconCache } from "../iconcache";
type SID = {sid: number}; type SID = {sid: number};
type SIDS = { type SIDS = {
@ -63,10 +62,6 @@ export class ManagerPort {
delete this.port; delete this.port;
}); });
IconCache.on("cached", ext => {
this.port.post("icon-cached", ext);
});
this.port.post("active", this.manager.active); this.port.post("active", this.manager.active);
this.sendAll(); this.sendAll();
} }

View File

@ -8,9 +8,6 @@ import * as DEFAULT_ICONS from "../data/icons.json";
const DONATE_URL = "https://www.downthemall.org/howto/donate/"; const DONATE_URL = "https://www.downthemall.org/howto/donate/";
const MANAGER_URL = "/windows/manager.html"; const MANAGER_URL = "/windows/manager.html";
const IS_CHROME = navigator && navigator.userAgent.includes("Chrome");
export async function mostRecentBrowser(): Promise<any> { export async function mostRecentBrowser(): Promise<any> {
let window = Array.from(await windows.getAll({windowTypes: ["normal"]})). let window = Array.from(await windows.getAll({windowTypes: ["normal"]})).
filter((w: any) => w.type === "normal").pop(); filter((w: any) => w.type === "normal").pop();
@ -106,32 +103,10 @@ const ICONS = Object.freeze((() => {
return new Map<string, string>(rv); return new Map<string, string>(rv);
})()); })());
let iconForPathPlatform: Function; export const DEFAULT_ICON_SIZE = 16;
if (IS_CHROME) {
const FOUR = 128;
const DOUBLE = 64;
iconForPathPlatform = function(icon: string, size: number) {
let scale = "1x";
if (size > FOUR) {
// wishful thinking at this point
scale = "4x";
}
else if (size > DOUBLE) {
scale = "2x";
}
return `chrome://fileicon/${icon}?scale=${scale}`;
};
}
else {
// eslint-disable-next-line no-unused-vars
iconForPathPlatform = function(icon: string, size: number) {
return ICONS.get(icon) || "icon-file-generic";
};
}
// eslint-disable-next-line no-unused-vars
// eslint-disable-next-line no-magic-numbers export function iconForPath(path: string, size = DEFAULT_ICON_SIZE) {
export function iconForPath(path: string, size = 16) {
const web = /^https?:\/\//.test(path); const web = /^https?:\/\//.test(path);
let file = path.split(/[\\/]/).pop(); let file = path.split(/[\\/]/).pop();
if (file) { if (file) {
@ -152,7 +127,7 @@ export function iconForPath(path: string, size = 16) {
file = "file"; file = "file";
} }
} }
return iconForPathPlatform(file, size); return ICONS.get(file) || "icon-file-generic";
} }
/** /**

View File

@ -33,7 +33,7 @@
.modal-footer { .modal-footer {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: right; justify-content: flex-end;
background: rgba(30, 30, 30, 0.2); background: rgba(30, 30, 30, 0.2);
margin-top: 2em; margin-top: 2em;
border-top: 1px solid rgba(30, 30, 30, 0.5); border-top: 1px solid rgba(30, 30, 30, 0.5);

View File

@ -15,16 +15,20 @@ export function addClass(elem: HTMLElement, ...cls: string[]) {
interface Timer { interface Timer {
args: any[]; args: any[];
id: number;
} }
export function debounce(fn: Function, to: number) { export function debounce(fn: Function, to: number, reset?: boolean) {
let timer: Timer | null; let timer: Timer | null;
return function(...args: any[]) { return function(...args: any[]) {
if (timer) { if (timer) {
if (!reset) {
timer.args = args; timer.args = args;
return; return;
} }
setTimeout(function() { window.clearTimeout(timer.id);
}
const id = window.setTimeout(function() {
if (!timer) { if (!timer) {
return; return;
} }
@ -37,7 +41,7 @@ export function debounce(fn: Function, to: number) {
console.error(ex.toString(), ex); console.error(ex.toString(), ex);
} }
}, to); }, to);
timer = {args}; timer = {args, id};
}; };
} }

View File

@ -27,6 +27,7 @@ LICENSED = set((".css", ".html", ".js", "*.ts"))
IGNORED = set((".DS_Store", "Thumbs.db")) IGNORED = set((".DS_Store", "Thumbs.db"))
PERM_IGNORED_FX = set(("downloads.shelf",)) PERM_IGNORED_FX = set(("downloads.shelf",))
PERM_IGNORED_CHROME = set(("menus",))
SCRIPTS = [ SCRIPTS = [
"yarn build:regexps", "yarn build:regexps",
@ -90,8 +91,6 @@ def build_firefox(args):
else: else:
infos["browser_specific_settings"]["gecko"]["id"] = RELEASE_ID infos["browser_specific_settings"]["gecko"]["id"] = RELEASE_ID
infos["permissions"] = [p for p in infos.get("permissions") if not p in PERM_IGNORED_FX] infos["permissions"] = [p for p in infos.get("permissions") if not p in PERM_IGNORED_FX]
out = Path("web-ext-artifacts") / f"dta-{version}-{args.mode}-fx.zip" out = Path("web-ext-artifacts") / f"dta-{version}-{args.mode}-fx.zip"
if not out.parent.exists(): if not out.parent.exists():
@ -101,6 +100,33 @@ def build_firefox(args):
print("Output", out) print("Output", out)
build(out, json.dumps(infos, indent=2).encode("utf-8")) build(out, json.dumps(infos, indent=2).encode("utf-8"))
def build_chrome(args):
now = datetime.now().strftime("%Y%m%d%H%M%S")
with open("manifest.json") as manip:
infos = json.load(manip, object_pairs_hook=OrderedDict)
version = infos.get("version")
if args.mode == "nightly":
version = infos["version"] = f"{version}.{now}"
version = infos.get("version")
del infos["browser_specific_settings"]
if args.mode != "release":
infos["version_name"] = f"{version}-{args.mode}"
infos["short_name"] = infos.get("name")
infos["name"] = f"{infos.get('name')} {args.mode}"
infos["permissions"] = [p for p in infos.get("permissions") if not p in PERM_IGNORED_CHROME]
out = Path("web-ext-artifacts") / f"dta-{version}-{args.mode}-crx.zip"
if not out.parent.exists():
out.parent.mkdir()
if out.exists():
out.unlink()
print("Output", out)
build(out, json.dumps(infos, indent=2).encode("utf-8"))
def main(): def main():
from argparse import ArgumentParser from argparse import ArgumentParser
args = ArgumentParser() args = ArgumentParser()
@ -114,6 +140,7 @@ def main():
else: else:
run([script], shell=True) run([script], shell=True)
build_firefox(args) build_firefox(args)
build_chrome(args)
print("DONE.") print("DONE.")
if __name__ == "__main__": if __name__ == "__main__":

25
util/i18ntochrome.py Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env python3
import json
import re
from collections import OrderedDict
from pathlib import Path
re_valid = re.compile("[^A-Za-z0-9_]")
for file in Path("_locales/").glob("**/*.json"):
with file.open("r") as filep:
messages = json.load(filep, object_pairs_hook=OrderedDict)
for x in list(messages):
prev = x
while True:
y = re_valid.sub("_", x)
if prev == y:
break
prev = y
if x == y:
continue
messages[y] = messages[x]
del messages[x]
with file.open("w", encoding="utf-8") as filep:
json.dump(messages, filep, ensure_ascii=False, indent=2)

View File

@ -93,10 +93,6 @@ addEventListener("DOMContentLoaded", function dom() {
Table.setItems(items); Table.setItems(items);
}); });
}); });
PORT.on("icon-cached", async () => {
await fullyloaded;
Table.onIconCached();
});
// Updates // Updates
const serializer = new PromiseSerializer(1); const serializer = new PromiseSerializer(1);

View File

@ -33,7 +33,7 @@ import { DownloadState, StateTexts, StateClasses, StateIcons } from "./state";
import { Tooltip } from "./tooltip"; import { Tooltip } from "./tooltip";
import "../../lib/util"; import "../../lib/util";
import { CellTypes } from "../../uikit/lib/constants"; import { CellTypes } from "../../uikit/lib/constants";
import { downloads } from "../../lib/browser"; import { downloads, CHROME } from "../../lib/browser";
import { $ } from "../winutil"; import { $ } from "../winutil";
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { TableConfig } from "../../uikit/lib/config"; import { TableConfig } from "../../uikit/lib/config";
@ -57,9 +57,11 @@ const HIDPI = window.matchMedia &&
window.matchMedia("(min-resolution: 2dppx)").matches; window.matchMedia("(min-resolution: 2dppx)").matches;
const ICON_BASE_SIZE = 16; const ICON_BASE_SIZE = 16;
const ICON_REAL_SIZE = HIDPI ? ICON_BASE_SIZE * 2 : ICON_BASE_SIZE; const ICON_REAL_SIZE = !CHROME && HIDPI ? ICON_BASE_SIZE * 2 : ICON_BASE_SIZE;
const LARGE_ICON_BASE_SIZE = 64; // eslint-disable-next-line no-magic-numbers
const MAX_ICON_BASE_SIZE = 127; const LARGE_ICON_BASE_SIZE = CHROME ? 32 : 64;
// eslint-disable-next-line no-magic-numbers
const MAX_ICON_BASE_SIZE = CHROME ? 32 : 127;
const LARGE_ICON_REAL_SIZE = HIDPI ? MAX_ICON_BASE_SIZE : LARGE_ICON_BASE_SIZE; const LARGE_ICON_REAL_SIZE = HIDPI ? MAX_ICON_BASE_SIZE : LARGE_ICON_BASE_SIZE;
let TEXT_SIZE_UNKNOWM = "unknown"; let TEXT_SIZE_UNKNOWM = "unknown";
@ -475,6 +477,8 @@ export class DownloadTable extends VirtualTable {
col.iconElem.classList.remove("icon-filter"); col.iconElem.classList.remove("icon-filter");
}); });
IconCache.on("cached", this.onIconCached.bind(this));
this.sids = new Map<number, DownloadItem>(); this.sids = new Map<number, DownloadItem>();
this.icons = new Icons($("#icons")); this.icons = new Icons($("#icons"));
@ -1061,10 +1065,16 @@ export class DownloadTable extends VirtualTable {
this.updateSizes(); this.updateSizes();
$("#statusSpeedContainer").classList.remove("hidden"); $("#statusSpeedContainer").classList.remove("hidden");
} }
if (item.manId && item.ext) {
IconCache.set(item.ext, item.manId).catch(console.error);
}
break; break;
case DownloadState.DONE: case DownloadState.DONE:
this.finished++; this.finished++;
if (item.manId && item.ext) {
IconCache.set(item.ext, item.manId).catch(console.error);
}
break; break;
} }
this.selectionChanged(); this.selectionChanged();

View File

@ -9,6 +9,8 @@
html, body { html, body {
height: auto !important; height: auto !important;
-webkit-user-select: none;
user-select: none;
} }
ul { ul {
@ -29,6 +31,7 @@
vertical-align: center; vertical-align: center;
align-items: center; align-items: center;
border-radius: 4px; border-radius: 4px;
cursor: default;
white-space: nowrap; white-space: nowrap;
} }

View File

@ -6,7 +6,9 @@ import { localize } from "../lib/i18n";
declare let browser: any; declare let browser: any;
declare let chrome: any; declare let chrome: any;
const runtime = browser !== "undefined" ? browser.runtime : chrome.runtime; const runtime = typeof browser !== "undefined" ?
browser.runtime :
chrome.runtime;
function handler(e: Event) { function handler(e: Event) {
e.preventDefault(); e.preventDefault();