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,
// eslint-disable-next-line no-unused-vars
MenuClickInfo,
CHROME,
} from "./browser";
import { Bus } from "./bus";
import { filterInSitu } from "./util";
@ -27,6 +28,20 @@ const menus = typeof (_menus) !== "undefined" && _menus || _cmenus;
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) {
try {
@ -83,11 +98,15 @@ class Handler {
async performSelection(options: SelectionOptions) {
try {
const toptions: any = {
currentWindow: true,
discarded: false,
};
if (!CHROME) {
toptions.hidden = true;
}
const selectedTabs = options.allTabs ?
await tabs.query({
currentWindow: true,
discarded: false,
hidden: false}) as any[] :
await tabs.query(toptions) as any[] :
[options.tab];
const textLinks = await Prefs.get("text-links", true);
@ -146,10 +165,18 @@ locale.then(() => {
this.onClicked = this.onClicked.bind(this);
const alls = new Map<string, string[]>();
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")) {
alls.set(options.id, options.contexts);
}
return menus.create(options);
menus.create(options);
};
mcreate({
id: "DTARegularLink",
@ -520,13 +547,21 @@ locale.then(() => {
function adjustAction(globalTurbo: boolean) {
action.setPopup({
popup: globalTurbo ? "" : null
popup: globalTurbo ? "" : "/windows/popup.html"
});
action.setIcon({
path: globalTurbo ? {
16: "/style/button-turbo.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 {webNavigation} = 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 CUSTOM_KEY = "_custom_locale";
const normalizer = /[^A-Za-z0-9_]/g;
interface JSONEntry {
message: string;
placeholders: any;
@ -72,7 +74,7 @@ class Localization {
}
localize(id: string, ...args: any[]) {
const entry = this.strings.get(id);
const entry = this.strings.get(id.replace(normalizer, "_"));
if (!entry) {
return "";
}
@ -121,7 +123,8 @@ function loadCached() {
async function loadRawLocales() {
// en is the base locale
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);
// Try the base too

View File

@ -1,14 +1,14 @@
"use strict";
// License: MIT
import { downloads } from "./browser";
import { downloads, CHROME } from "./browser";
import { EventEmitter } from "../uikit/lib/events";
import { PromiseSerializer } from "./pserializer";
const VERSION = 1;
const STORE = "iconcache";
// 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([
"",
@ -28,7 +28,8 @@ const BLACKLISTED = Object.freeze(new Set([
]));
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:") {
const res = await fetch(icon.toString());
const blob = await res.blob();

View File

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

View File

@ -5,7 +5,6 @@ import { donate, openPrefs } from "../windowutils";
import { API } from "../api";
// eslint-disable-next-line no-unused-vars
import { BaseDownload } from "./basedownload";
import { IconCache } from "../iconcache";
type SID = {sid: number};
type SIDS = {
@ -63,10 +62,6 @@ export class ManagerPort {
delete this.port;
});
IconCache.on("cached", ext => {
this.port.post("icon-cached", ext);
});
this.port.post("active", this.manager.active);
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 MANAGER_URL = "/windows/manager.html";
const IS_CHROME = navigator && navigator.userAgent.includes("Chrome");
export async function mostRecentBrowser(): Promise<any> {
let window = Array.from(await windows.getAll({windowTypes: ["normal"]})).
filter((w: any) => w.type === "normal").pop();
@ -106,32 +103,10 @@ const ICONS = Object.freeze((() => {
return new Map<string, string>(rv);
})());
let iconForPathPlatform: Function;
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";
};
}
export const DEFAULT_ICON_SIZE = 16;
// eslint-disable-next-line no-magic-numbers
export function iconForPath(path: string, size = 16) {
// eslint-disable-next-line no-unused-vars
export function iconForPath(path: string, size = DEFAULT_ICON_SIZE) {
const web = /^https?:\/\//.test(path);
let file = path.split(/[\\/]/).pop();
if (file) {
@ -152,7 +127,7 @@ export function iconForPath(path: string, size = 16) {
file = "file";
}
}
return iconForPathPlatform(file, size);
return ICONS.get(file) || "icon-file-generic";
}
/**

View File

@ -33,7 +33,7 @@
.modal-footer {
display: flex;
flex-wrap: nowrap;
justify-content: right;
justify-content: flex-end;
background: rgba(30, 30, 30, 0.2);
margin-top: 2em;
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 {
args: any[];
id: number;
}
export function debounce(fn: Function, to: number) {
export function debounce(fn: Function, to: number, reset?: boolean) {
let timer: Timer | null;
return function(...args: any[]) {
if (timer) {
timer.args = args;
return;
if (!reset) {
timer.args = args;
return;
}
window.clearTimeout(timer.id);
}
setTimeout(function() {
const id = window.setTimeout(function() {
if (!timer) {
return;
}
@ -37,7 +41,7 @@ export function debounce(fn: Function, to: number) {
console.error(ex.toString(), ex);
}
}, to);
timer = {args};
timer = {args, id};
};
}

View File

@ -27,6 +27,7 @@ LICENSED = set((".css", ".html", ".js", "*.ts"))
IGNORED = set((".DS_Store", "Thumbs.db"))
PERM_IGNORED_FX = set(("downloads.shelf",))
PERM_IGNORED_CHROME = set(("menus",))
SCRIPTS = [
"yarn build:regexps",
@ -90,8 +91,6 @@ def build_firefox(args):
else:
infos["browser_specific_settings"]["gecko"]["id"] = RELEASE_ID
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"
if not out.parent.exists():
@ -101,6 +100,33 @@ def build_firefox(args):
print("Output", out)
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():
from argparse import ArgumentParser
args = ArgumentParser()
@ -114,6 +140,7 @@ def main():
else:
run([script], shell=True)
build_firefox(args)
build_chrome(args)
print("DONE.")
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);
});
});
PORT.on("icon-cached", async () => {
await fullyloaded;
Table.onIconCached();
});
// Updates
const serializer = new PromiseSerializer(1);

View File

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

View File

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

View File

@ -6,7 +6,9 @@ import { localize } from "../lib/i18n";
declare let browser: 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) {
e.preventDefault();