Skip to content

Commit

Permalink
🚧 Partial browserAction implementation (#60)
Browse files Browse the repository at this point in the history
The goal is to implement just enough of the browser action api to resolve the error in #53.

Also see #20
  • Loading branch information
trickypr authored Apr 26, 2024
1 parent 7e02b35 commit 1d8ae34
Show file tree
Hide file tree
Showing 36 changed files with 1,173 additions and 80 deletions.
2 changes: 1 addition & 1 deletion apps/content/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"svelte-preprocess": "^5.1.3",
"svelte-sequential-preprocessor": "^2.0.1",
"ts-loader": "^9.5.1",
"typescript": "^5.3.3",
"typescript": "^5.4.5",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
Expand Down
1 change: 1 addition & 0 deletions apps/content/src/browser/browserImports.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { lazyESModuleGetters } from '../shared/lazy.js'
export const browserImports = lazyESModuleGetters({
AppConstants: 'resource://gre/modules/AppConstants.sys.mjs',
E10SUtils: 'resource://gre/modules/E10SUtils.sys.mjs',
EBrowserActions: 'resource://app/modules/EBrowserActions.sys.mjs',
EPageActions: 'resource://app/modules/EPageActions.sys.mjs',
NetUtil: 'resource://gre/modules/NetUtil.sys.mjs',
PageThumbs: 'resource://gre/modules/PageThumbs.sys.mjs',
Expand Down
62 changes: 62 additions & 0 deletions apps/content/src/browser/components/BrowserAction.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<!-- 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/. -->

<script>
import { writable } from 'svelte/store'
// @ts-check
import ToolbarButton from './ToolbarButton.svelte'
const actionPanel = import('./browserAction.js').then(
(module) => module.actionPanel,
)
/** @type {import("resource://app/modules/EBrowserActions.sys.mjs").IBrowserAction} */
export let action
/** @type {WebsiteView} */
export let browserView
let launchTarget
let open = writable(false)
const iconSrc = action.getIcon(16)
const url = action.getPopupUrl()
</script>

<ToolbarButton
bind:buttonElement={launchTarget}
on:click={async (e) => {
open = true
const { clickModifiersFromEvent } = await import('./browserAction.js')
action.getEmiter().emit('click', {
tabId: browserView.browserId,
clickData: clickModifiersFromEvent(e),
})
}}
>
<img src={$iconSrc} />
</ToolbarButton>

{#if $url}
{#await actionPanel then ap}
<xul:panel
use:ap={{ url: $url, launchTarget, open }}
on:close={() => (open = false)}
class="popup"
id={`page-action-panel__${action.getExtensionId()}--${
browserView.browserId
}`}
></xul:panel>
{/await}
{/if}

<style>
.popup {
--panel-padding: 0;
--panel-border-radius: 1rem;
--panel-border-color: transperent;
--panel-shadow-margin: 1rem;
--panel-background: transperent;
}
</style>
4 changes: 3 additions & 1 deletion apps/content/src/browser/components/ToolbarButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

<script>
export let disabled = false
/** @type {HTMLButtonElement} */
export let buttonElement = undefined
</script>

<button on:click {disabled}>
<button on:click {disabled} bind:this={buttonElement}>
<slot />
</button>

Expand Down
8 changes: 8 additions & 0 deletions apps/content/src/browser/components/WebsiteView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<script>
// @ts-check
import { onMount } from 'svelte'
import { derived } from 'svelte/store'
import RiRefreshLine from 'svelte-remixicon/RiRefreshLine.svelte'
import RiArrowLeftLine from 'svelte-remixicon/RiArrowLeftLine.svelte'
Expand All @@ -16,12 +17,16 @@
import ToolbarSpacer from './ToolbarSpacer.svelte'
import UrlBox from './UrlBox.svelte'
import HamburgurMenu from './HamburgurMenu.svelte'
import { browserImports } from '../browserImports'
import BrowserAction from './BrowserAction.svelte'
/** @type {WebsiteView} */
export let view
/** @type {HTMLDivElement} */
let browserContainer
const actions = browserImports.EBrowserActions.actions
const canGoBack = WebsiteViewApi.locationProperty(
view,
(browser) => browser.canGoBack,
Expand Down Expand Up @@ -64,6 +69,9 @@

<ToolbarSpacer />

{#each Object.entries($actions) as [extId, action] (extId)}
<BrowserAction browserView={view} {action} />
{/each}
<HamburgurMenu />
</div>

Expand Down
159 changes: 159 additions & 0 deletions apps/content/src/browser/components/browserAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/* 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/. */
// @ts-check
/// <reference types="@browser/link" />
import { browserImports } from '../browserImports.js'
import { createBrowser } from '../utils/browserElement.js'

const ABOUT_BLANK = browserImports.NetUtil.newURI('about:blank')
const EXTENSION_CONTENT_SCRIPT =
'chrome://extensions/content/ext-browser-content.js'

/**
* @template {XULPanel} Node
* @template {{ url: string, launchTarget: HTMLElement, open: boolean }} Parameters
* @template {{ 'on:close': (e: CustomEvent<null>) => void }} Attributes
*
* @param {Node} panel
* @param {Parameters} param1
*
* @returns {import('svelte/action').ActionReturn<Parameters, Attributes>}
*/
export function actionPanel(panel, { url, launchTarget }) {
let extensionUri = browserImports.NetUtil.newURI(url)
let browser = createBrowser(ABOUT_BLANK, {
disableglobalhistory: 'true',
messagemanagergroup: 'webext-browsers',
'webextension-view-type': 'popup',
})
let messageReceiver = new MessageReceiver(browser)
panel.appendChild(browser)

function setupBrowser() {
browser.messageManager.addMessageListener(
'Extension:BrowserResized',
messageReceiver,
)
browser.messageManager.loadFrameScript(
EXTENSION_CONTENT_SCRIPT,
false,
true,
)
browser.messageManager.sendAsyncMessage('Extension:InitBrowser', {
allowScriptsToClose: true,
maxWidth: 800,
maxHeight: 600,
})
}

function triggerOpen(/** @type {boolean} */ open) {
if (!open) {
panel.hidePopup()
return
}

panel.openPopup(launchTarget, 'bottomright topright')
browser.source = extensionUri.spec
try {
browser.loadURI(extensionUri, {
triggeringPrincipal:
Services.scriptSecurityManager.getSystemPrincipal(),
})
} catch (e) {
console.debug('Debug info for changing url')
console.debug(browser, extensionUri)
console.error(e)
}

return () => {}
}

function handleClose() {
panel.dispatchEvent(new CustomEvent('close', { detail: null }))
browser.source = ABOUT_BLANK.spec
try {
browser.loadURI(ABOUT_BLANK, {})
} catch (e) {
console.debug('Debug info for changing url')
console.debug(browser, extensionUri)
console.error(e)
}

return () => {}
}

setupBrowser()
browser.addEventListener('DidChangeBrowserRemoteness', setupBrowser)
panel.addEventListener('popuphidden', handleClose)

return {
update({ url: newUrl, launchTarget: lt, open }) {
if (newUrl != url) extensionUri = browserImports.NetUtil.newURI(newUrl)
launchTarget = lt

triggerOpen(open)
},

destroy() {
browser.messageManager?.removeMessageListener(
'Extension:BrowserResized',
messageReceiver,
)
browser.removeEventListener('DidChangeBrowserRemoteness', setupBrowser)
browser.remove()

panel.removeEventListener('popuphidden', handleClose)
},
}
}

/**
* @param {MouseEvent} event
*/
export function clickModifiersFromEvent(event) {
const map = {
shiftKey: 'Shift',
altKey: 'Alt',
metaKey: 'Command',
ctrlKey: 'Ctrl',
}

const modifiers = Object.keys(map)
.filter((key) => event[key])
.map((key) => map[key])

if (event.ctrlKey && browserImports.AppConstants.platform === 'macosx') {
modifiers.push('MacCtrl')
}

return modifiers
}

/**
* @implements {MessageListener}
*/
class MessageReceiver {
/** @type {XULBrowserElement} */
browser

/** @param {XULBrowserElement} browser */
constructor(browser) {
this.browser = browser
}

/**
* @param {ReceiveMessageArgument} data
*/
receiveMessage(data) {
if (data.name === 'Extension:BrowserResized') {
const { width, height } = data.data
if (this.browser) {
this.browser.style.width = `${width}px`
this.browser.style.height = `${height}px`
}

return
}
}
}
2 changes: 2 additions & 0 deletions apps/content/src/browser/utils/browserElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const DEFAULT_BROWSER_ATTRIBUTES = {
/**
* @param {nsIURIType} uri
* @param {Record<string, string>} [attributes={}]
*
* @returns {XULBrowserElement}
*/
export function createBrowser(uri, attributes = {}) {
let originAttributes = browserImports.E10SUtils.predictOriginAttributes({
Expand Down
4 changes: 3 additions & 1 deletion apps/content/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{
"$schema": "https://json.schemastore.org/tsconfig.json",
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "es2022",
"allowArbitraryExtensions": true,
"paths": {},
"skipLibCheck": true
"skipLibCheck": true,
"types": ["@browser/link"]
},
"include": ["src/**/*"],
"exclude": ["node_modules/*", "static/*"]
Expand Down
7 changes: 7 additions & 0 deletions apps/extensions/lib/ext-browser.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
{
"browserAction": {
"url": "chrome://bextensions/content/parent/ext-browserAction.js",
"schema": "chrome://bextensions/content/schemas/browser_action.json",
"scopes": ["addon_parent"],
"manifest": ["browser_action"],
"paths": [["browserAction"]]
},
"pageAction": {
"url": "chrome://bextensions/content/parent/ext-pageAction.js",
"schema": "chrome://extensions/content/schemas/page_action.json",
Expand Down
31 changes: 29 additions & 2 deletions apps/extensions/lib/parent/ext-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const { lazyESModuleGetters } = typedImportUtils

const lazy = lazyESModuleGetters({
WindowTracker: 'resource://app/modules/BrowserWindowTracker.sys.mjs',
EBrowserActions: 'resource://app/modules/EBrowserActions.sys.mjs',
EPageActions: 'resource://app/modules/EPageActions.sys.mjs',
ExtensionParent: 'resource://gre/modules/ExtensionParent.sys.mjs',
})
Expand All @@ -36,8 +37,10 @@ class TabTracker extends TabTrackerBase {
}

/**
* @template {import('@browser/tabs').WindowTab | null} T
* @param {number} tabId
* @param {import('@browser/tabs').WindowTab} default_
* @param {T} default_
* @returns {T}
*/
getTab(tabId, default_) {
const { tab } = lazy.WindowTracker.getWindowWithBrowserId(tabId) || {
Expand All @@ -47,6 +50,28 @@ class TabTracker extends TabTrackerBase {
return tab
}

/**
* @param {import('resource://gre/modules/Extension.sys.mjs').Extension} extension
* @param {import('@browser/tabs').WindowTab} tab
* @param {Window} window
*
* @returns {tabs__tabs.Tab}
*/
serializeTab(extension, tab, window) {
// TODO: Active tab & host permissions
const hasTabPermission = extension.hasPermission('tabs')

return {
id: tab.view.browserId,
index: window.windowTabs().findIndex((wTab) => wTab === tab),
active: window.activeTab() === tab,
highlighted: false, // TODO
title: (hasTabPermission && tab.view.title) || undefined,
url: (hasTabPermission && tab.view.uri.asciiSpec) || undefined,
windowId: window.windowId,
}
}

/**
* @param {XULBrowserElement} browser
*/
Expand All @@ -61,4 +86,6 @@ class TabTracker extends TabTrackerBase {
}
}

Object.assign(global, { tabTracker: new TabTracker() })
/** @global */
let tabTracker = new TabTracker()
Object.assign(global, { tabTracker })
Loading

0 comments on commit 1d8ae34

Please sign in to comment.