diff --git a/apps/content/src/browser/components/keybindings/Keybindings.svelte b/apps/content/src/browser/components/keybindings/Keybindings.svelte index 6145b5a..b5fbb52 100644 --- a/apps/content/src/browser/components/keybindings/Keybindings.svelte +++ b/apps/content/src/browser/components/keybindings/Keybindings.svelte @@ -43,6 +43,22 @@ pref="browser.keybinds.previousTab" on:command={() => setCurrentTabIndex(getCurrentTabIndex() - 1)} /> + + + runOnCurrentTab((tab) => tab.zoom.update((zoom) => zoom + 0.1))} + /> + + runOnCurrentTab((tab) => tab.zoom.update((zoom) => zoom - 0.1))} + /> + runOnCurrentTab((tab) => tab.zoom.set(1))} + /> + {#each [1, 2, 3, 4, 5, 6, 7, 8] as tabNum} + zoom.set(1)} /> + {#each $pageActions as [_extId, pageAction]} {#if pageAction.shouldShow($uri.asciiSpec, tab.getTabId())} diff --git a/apps/content/src/browser/components/omnibox/ZoomDisplay.svelte b/apps/content/src/browser/components/omnibox/ZoomDisplay.svelte new file mode 100644 index 0000000..07c8deb --- /dev/null +++ b/apps/content/src/browser/components/omnibox/ZoomDisplay.svelte @@ -0,0 +1,28 @@ + + + + +{#if zoom != 1} + +{/if} + + diff --git a/apps/content/src/browser/lib/resources.ts b/apps/content/src/browser/lib/resources.ts index 5f427c0..af87056 100644 --- a/apps/content/src/browser/lib/resources.ts +++ b/apps/content/src/browser/lib/resources.ts @@ -11,4 +11,5 @@ export const resource = lazyESModuleGetters({ NetUtil: 'resource://gre/modules/NetUtil.sys.mjs', PageThumbs: 'resource://gre/modules/PageThumbs.sys.mjs', WindowTracker: 'resource://app/modules/BrowserWindowTracker.sys.mjs', + ZoomStore: 'resource://app/modules/ZoomStore.sys.mjs', }) diff --git a/apps/content/src/browser/lib/window/tab.ts b/apps/content/src/browser/lib/window/tab.ts index bdfe823..ea3ddbc 100644 --- a/apps/content/src/browser/lib/window/tab.ts +++ b/apps/content/src/browser/lib/window/tab.ts @@ -5,6 +5,8 @@ import { type ViewableWritable, viewableWritable } from '@experiment/shared' import mitt from 'mitt' import { type Writable, get, writable } from 'svelte/store' +import type { ZoomStoreEvents } from 'resource://app/modules/ZoomStore.sys.mjs' + import { type BookmarkTreeNode, search } from '@shared/ExtBookmarkAPI' import { resource } from '../resources' @@ -43,6 +45,8 @@ export class Tab { public loading = writable(false) public loadingProgress = writable(0) + public zoom = writable(1) + /** * This is used by the omnibox to determine if text input should be focused. */ @@ -59,6 +63,17 @@ export class Tab { this.goToUri(uri) this.title.set(uri.asciiHost) + this.zoom.subscribe((newZoom) => { + if ( + !this.browserElement.browsingContext || + this.browserElement.fullZoom === newZoom + ) { + return + } + + this.browserElement.fullZoom = newZoom + resource.ZoomStore.setZoomForUri(this.uri.readOnce(), newZoom) + }) this.uri.subscribe(async (uri) => this.bookmarkInfo.set( await search({ url: uri.spec }).then((r) => @@ -66,6 +81,9 @@ export class Tab { ), ), ) + + // Remember to unsubscribe from any listeners you register here! + resource.ZoomStore.events.on('setZoom', this.zoomChange) } public getId(): number { @@ -147,6 +165,11 @@ export class Tab { this.useProgressListener() } + zoomChange = (event: ZoomStoreEvents['setZoom']) => { + if (this.uri.readOnce().asciiHost != event.host) return + this.zoom.set(event.zoom) + } + protected useProgressListener() { this.progressListener.events.on('locationChange', (event) => { if (!event.aWebProgress.isTopLevel) return @@ -156,6 +179,8 @@ export class Tab { this.uri.set(event.aLocation) this.canGoBack.set(this.browserElement.canGoBack) this.canGoForward.set(this.browserElement.canGoForward) + + this.zoom.set(resource.ZoomStore.getZoomForUri(event.aLocation)) }) this.progressListener.events.on('progressPercent', this.loadingProgress.set) @@ -178,6 +203,9 @@ export class Tab { } public destroy() { + this.removeEventListeners() + resource.ZoomStore.events.off('setZoom', this.zoomChange) + this.browserElement.remove() } diff --git a/apps/content/src/settings/Settings.svelte b/apps/content/src/settings/Settings.svelte index 67de213..1047555 100644 --- a/apps/content/src/settings/Settings.svelte +++ b/apps/content/src/settings/Settings.svelte @@ -75,6 +75,10 @@ Next tab Previous Tab + Zoom in + Zoom out + Reset Zoom + {#each [1, 2, 3, 4, 5, 6, 7, 8] as tabNum} Jump to tab {tabNum} +import mitt from 'resource://app/modules/mitt.sys.mjs' + +const ZOOM_STORE_FILE = PathUtils.join(PathUtils.profileDir, 'zoomstore.json') + +/** @typedef {import("resource://app/modules/ZoomStore.sys.mjs").ZoomStoreInterface} ZoomStoreInterface */ + +/** @implements {ZoomStoreInterface} */ +class ZoomStoreImpl { + /** + * @private + * @type {Map | null} + */ + zoomPages = null + + /** @type {import('resource://app/modules/mitt.sys.mjs').Emitter} */ + events = mitt() + + constructor() { + this.init() + } + + /** @protected */ + async init() { + if (this.zoomPages) { + return + } + + if (!(await IOUtils.exists(ZOOM_STORE_FILE))) { + this.zoomPages = new Map() + return + } + + try { + const pages = await IOUtils.readJSON(ZOOM_STORE_FILE) + if (this.zoomPages) { + return + } + + this.zoomPages = new Map(pages) + } catch (e) { + console.error('Failed to load zoomStore from file: ', e) + return + } + } + + /** @private */ + async save() { + if (!this.zoomPages) { + return + } + + const toWrite = Array.from(this.zoomPages.entries()) + try { + await IOUtils.writeJSON(ZOOM_STORE_FILE, toWrite) + } catch (e) { + console.error('Failed to write zoomStore to file:', e) + return + } + } + + /** + * @param {nsIURIType} uri The uri to check zoom for + * @returns {number} + */ + getZoomForUri(uri) { + return this.zoomPages?.get(uri.asciiHost) || 1 + } + + /** + * @param {nsIURIType} uri + * @param {number} zoom The zoom to store. If set to 1, will delete any stored values + */ + setZoomForUri(uri, zoom) { + try { + uri.host + } catch { + return + } + + if (zoom === 1) { + this.zoomPages?.delete(uri.host) + this.events.emit('setZoom', { host: uri.host, zoom }) + return + } + + this.zoomPages?.set(uri.host, Math.round(zoom * 100) / 100) + + // @ts-ignore + Services.tm.dispatchToMainThread(() => { + this.events.emit('setZoom', { host: uri.host, zoom }) + this.save() + }) + } +} + +export const ZoomStore = new ZoomStoreImpl() diff --git a/libs/link/package.json b/libs/link/package.json index 8cc389f..15a7347 100644 --- a/libs/link/package.json +++ b/libs/link/package.json @@ -8,6 +8,7 @@ "license": "ISC", "dependencies": { "gecko-types": "github:quark-platform/gecko-types", + "gecko-types@latest": "link:quark-platform/gecko-types@latest", "mitt": "^3.0.1" } } diff --git a/libs/link/types/_link.d.ts b/libs/link/types/_link.d.ts index 7896154..4367b18 100644 --- a/libs/link/types/_link.d.ts +++ b/libs/link/types/_link.d.ts @@ -24,3 +24,4 @@ /// /// /// +/// diff --git a/libs/link/types/globals/Elements.d.ts b/libs/link/types/globals/Elements.d.ts index 2bfdaed..8d52fe4 100644 --- a/libs/link/types/globals/Elements.d.ts +++ b/libs/link/types/globals/Elements.d.ts @@ -51,6 +51,8 @@ declare interface XULBrowserElement extends HTMLElement { canGoForward: boolean goForward() reload() + fullZoom: number + browsingContext?: unknown loadURI(uri: nsIURIType, params?: LoadURIOptions) browserId: number mInitialized: boolean diff --git a/libs/link/types/modules/ZoomStore.d.ts b/libs/link/types/modules/ZoomStore.d.ts new file mode 100644 index 0000000..ff6ba0d --- /dev/null +++ b/libs/link/types/modules/ZoomStore.d.ts @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +declare module 'resource://app/modules/ZoomStore.sys.mjs' { + import { Emitter } from 'mitt' + + export type ZoomStoreEvents = { + setZoom: { host: string; zoom: number } + } + + export const ZoomStore: { + events: Emitter + + getZoomForUri(uri: nsIURIType): number + setZoomForUri(uri: nsIURIType, zoom: number): void + } + + export type ZoomStoreInterface = typeof ZoomStore +} + +declare interface MozESMExportFile { + ZoomStore: 'resource://app/modules/ZoomStore.sys.mjs' +} + +declare interface MozESMExportType { + ZoomStore: typeof import('resource://app/modules/ZoomStore.sys.mjs').ZoomStore +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aabdd8b..46b750d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -189,7 +189,10 @@ importers: dependencies: gecko-types: specifier: github:quark-platform/gecko-types - version: github.com/quark-platform/gecko-types/e4efe38ce26c3617d7a3ecb13491bca2004e0377 + version: github.com/quark-platform/gecko-types/015f4eddb9a02d5f60ef82bf04e7b78bbb15ffd6 + gecko-types@latest: + specifier: link:quark-platform/gecko-types@latest + version: link:quark-platform/gecko-types@latest mitt: specifier: ^3.0.1 version: 3.0.1 @@ -7595,7 +7598,14 @@ packages: resolution: {integrity: sha512-FSZOvfJVfMWhk/poictNsDBCXq/Z+2Zu2peWs6d8OhWWb9nY++czw95D47hdw06L/kfjasLevwrbUtnXyWLAJw==} dev: false + github.com/quark-platform/gecko-types/015f4eddb9a02d5f60ef82bf04e7b78bbb15ffd6: + resolution: {tarball: https://codeload.github.com/quark-platform/gecko-types/tar.gz/015f4eddb9a02d5f60ef82bf04e7b78bbb15ffd6} + name: gecko-types + version: 1.0.0 + dev: false + github.com/quark-platform/gecko-types/e4efe38ce26c3617d7a3ecb13491bca2004e0377: resolution: {tarball: https://codeload.github.com/quark-platform/gecko-types/tar.gz/e4efe38ce26c3617d7a3ecb13491bca2004e0377} name: gecko-types version: 1.0.0 + dev: true