|
| 1 | +import { |
| 2 | + framesForTab_, CurCVer_, OnChrome, set_setIcon_, setIcon_, set_iconData_, iconData_, blank_ |
| 3 | +} from "./store" |
| 4 | +import * as BgUtils_ from "./utils" |
| 5 | +import * as settings_ from "./settings" |
| 6 | +import { trans_ } from "./i18n" |
| 7 | +import { browser_, runtimeError_ } from "./browser" |
| 8 | +import { asyncIterFrames_ } from "./ports" |
| 9 | + |
| 10 | +const knownIcons_ = OnChrome ? As_<readonly [IconNS.BinaryPath, IconNS.BinaryPath, IconNS.BinaryPath]>([ |
| 11 | + "icons/enabled.bin", "icons/partial.bin", "icons/disabled.bin" |
| 12 | +]) : As_<readonly [IconNS.ImagePath, IconNS.ImagePath, IconNS.ImagePath]>([ |
| 13 | + { 19: "/icons/enabled_19.png", 38: "/icons/enabled_38.png" }, |
| 14 | + { 19: "/icons/partial_19.png", 38: "/icons/partial_38.png" }, |
| 15 | + { 19: "/icons/disabled_19.png", 38: "/icons/disabled_38.png" } |
| 16 | +]) |
| 17 | + |
| 18 | +export const browserAction_ = (browser_ as any).action as undefined || browser_.browserAction |
| 19 | +let tabIds_cr_: Map<Frames.ValidStatus, number[] | null> | null |
| 20 | +let mayShowIcons = true |
| 21 | + |
| 22 | +const onerror = (err: any): void => { |
| 23 | + if (!mayShowIcons) { return } |
| 24 | + mayShowIcons = false; |
| 25 | + console.log("Can not access binary icon data:", err); |
| 26 | + set_setIcon_(blank_) |
| 27 | + browserAction_.setTitle({ title: trans_("name") + "\n\nFailed in showing dynamic icons." }) |
| 28 | +} |
| 29 | + |
| 30 | +const loadBinaryImagesAndSetIcon_cr = (type: Frames.ValidStatus): void => { |
| 31 | + const path = knownIcons_[type] as IconNS.BinaryPath; |
| 32 | + const loadFromRawArray = (array: ArrayBuffer): void => { |
| 33 | + const uint8Array = new Uint8ClampedArray(array), firstSize = array.byteLength / 5, |
| 34 | + small = (Math.sqrt(firstSize / 4) | 0) as IconNS.ValidSizes, large = (small + small) as IconNS.ValidSizes, |
| 35 | + cache = BgUtils_.safeObj_() as IconNS.IconBuffer; |
| 36 | + cache[small] = new ImageData(uint8Array.subarray(0, firstSize), small, small); |
| 37 | + cache[large] = new ImageData(uint8Array.subarray(firstSize), large, large); |
| 38 | + iconData_![type] = cache; |
| 39 | + const arr = tabIds_cr_!.get(type)! |
| 40 | + tabIds_cr_!.delete(type) |
| 41 | + for (let w = 0, h = arr.length; w < h; w++) { |
| 42 | + framesForTab_.has(arr[w]) && setIcon_(arr[w], type, true) |
| 43 | + } |
| 44 | + }; |
| 45 | + if (Build.MinCVer >= BrowserVer.MinFetchExtensionFiles || CurCVer_ >= BrowserVer.MinFetchExtensionFiles) { |
| 46 | + const p = fetch(path).then(r => r.arrayBuffer()).then(loadFromRawArray) |
| 47 | + if (!Build.NDEBUG) { p.catch(onerror) } |
| 48 | + } else { |
| 49 | + const req = new XMLHttpRequest() as ArrayXHR |
| 50 | + req.open("GET", path, true) |
| 51 | + req.responseType = "arraybuffer" |
| 52 | + req.onload = function (this: typeof req) { loadFromRawArray(this.response) } |
| 53 | + if (!Build.NDEBUG) { req.onerror = onerror } |
| 54 | + req.send() |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +export const toggleIconBuffer_ = (): void => { |
| 59 | + const enabled = settings_.get_("showActionIcon") |
| 60 | + if (enabled === !!iconData_) { return } |
| 61 | + const iter = ({ cur_: { s: sender } }: Frames.Frames): void => { |
| 62 | + if (sender.status_ !== Frames.Status.enabled) { |
| 63 | + setIcon_(sender.tabId_, enabled ? sender.status_ : Frames.Status.enabled) |
| 64 | + } |
| 65 | + } |
| 66 | + const cond = (): boolean => settings_.get_("showActionIcon") === enabled |
| 67 | + if (!enabled) { |
| 68 | + setTimeout((): void => { |
| 69 | + if (settings_.get_("showActionIcon") || iconData_ == null) { return } |
| 70 | + set_iconData_(null) |
| 71 | + if (OnChrome) { tabIds_cr_ = null } |
| 72 | + else { |
| 73 | + asyncIterFrames_(iter, cond) |
| 74 | + } |
| 75 | + }, 200); |
| 76 | + return; |
| 77 | + } |
| 78 | + if (!OnChrome) { |
| 79 | + set_iconData_(1 as never) |
| 80 | + } else { |
| 81 | + set_iconData_([null, null, null]) |
| 82 | + tabIds_cr_ = new Map() |
| 83 | + } |
| 84 | + // only do partly updates: ignore "rare" cases like `sender.s` is enabled but the real icon isn't |
| 85 | + asyncIterFrames_(iter, cond) |
| 86 | +} |
| 87 | + |
| 88 | +/** Firefox does not use ImageData as inner data format |
| 89 | + * * https://dxr.mozilla.org/mozilla-central/source/toolkit/components/extensions/schemas/manifest.json#577 |
| 90 | + * converts ImageData objects in parameters into data:image/png,... URLs |
| 91 | + * * https://dxr.mozilla.org/mozilla-central/source/browser/components/extensions/parent/ext-browserAction.js#483 |
| 92 | + * builds a css text of "--webextension-***: url(icon-url)", |
| 93 | + * and then set the style of an extension's toolbar button to it |
| 94 | + */ |
| 95 | +export const doSetIcon_: typeof setIcon_ = !OnChrome ? (tabId, type): void => { |
| 96 | + tabId < 0 || browserAction_.setIcon({ tabId, path: knownIcons_[type]! }) |
| 97 | +} : (tabId: number, type: Frames.ValidStatus, isLater?: true): void => { |
| 98 | + let data: IconNS.IconBuffer | null | undefined; |
| 99 | + if (tabId < 0) { /* empty */ } |
| 100 | + else if (data = iconData_![type]) { |
| 101 | + const f = browserAction_.setIcon |
| 102 | + const args: Parameters<typeof f>[0] = { tabId, imageData: data } |
| 103 | + isLater ? f(args, runtimeError_) : f(args); |
| 104 | + } else if (tabIds_cr_!.has(type)) { |
| 105 | + tabIds_cr_!.get(type)!.push(tabId) |
| 106 | + } else { |
| 107 | + setTimeout(loadBinaryImagesAndSetIcon_cr, 0, type); |
| 108 | + tabIds_cr_!.set(type, [tabId]) |
| 109 | + } |
| 110 | +} |
0 commit comments