Skip to content

Commit

Permalink
✨ Partial implementation of tabs.update()
Browse files Browse the repository at this point in the history
  • Loading branch information
trickypr committed Apr 13, 2024
1 parent a915ebb commit 5c039c6
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 3 deletions.
2 changes: 2 additions & 0 deletions apps/content/src/browser/windowApi/WebsiteView.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import mitt from 'mitt'
import { readable } from 'svelte/store'

import { browserImports } from '../browserImports.js'
import { createBrowser } from '../utils/browserElement.js'
import { eventBus } from './eventBus.js'

Expand Down Expand Up @@ -51,6 +52,7 @@ export function create(uri) {
registerViewThemeListener(view)
})

view.events.on('goTo', (e) => goTo(view, browserImports.NetUtil.newURI(e)))
view.events.on('locationChange', (e) => (view.uri = e.aLocation))
view.events.on(
'loadingChange',
Expand Down
3 changes: 3 additions & 0 deletions apps/content/src/browser/windowApi/WindowTabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export const activeTabId = writable(0)
*/
export const selectedTabIds = writable([])

window.activeTabId = activeTabId
window.selectedTabIds = selectedTabIds

/**
* @param {number[]} ids
*/
Expand Down
62 changes: 62 additions & 0 deletions apps/extensions/lib/parent/ext-tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,68 @@ this.tabs = class extends ExtensionAPIPersistent {
}
}
},

async update(tabId, updateProperties) {
const windows = lazy.WindowTracker.registeredWindows.values()
for (const window of windows) {
const tabs = window.windowTabs()
const hasTab = tabs.some((tab) => tab.view.browserId === tabId)

if (!hasTab) {
continue
}

let errors = null
let retTab

window.windowTabs.update((tabs) =>
tabs.map((tab) => {
if (tab.view.browserId === tabId) {
if (updateProperties.active) {
window.activeTabId.set(tab.view.windowBrowserId)
}

if (
updateProperties.highlighted &&
window.activeTabId() !== tab.view.windowBrowserId
) {
window.selectedTabIds.update((tabs) => {
if (tabs.includes(tab.view.windowBrowserId)) return tabs
return [...tabs, tab.view.windowBrowserId]
})
}

if (updateProperties.url) {
let url = context.uri.resolve(updateProperties.url)

if (
!context.checkLoadURL(url, { dontReportErrors: true })
) {
errors = `Invalid url: ${url}`
return tab
}

tab.view.events.emit('goTo', url)
}

retTab = tab
}

return tab
}),
)

if (errors) {
return Promise.reject({ message: errors })
}

if (retTab) {
return serialize(extension)([retTab, window])
}

return
}
},
},
}
}
Expand Down
6 changes: 6 additions & 0 deletions apps/extensions/lib/schemaTypes/tabs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,17 @@ declare module tabs__tabs {
url?: string | string[]
windowId?: number
}
type UpdateInfo = {
active?: boolean
highlighted?: boolean
url?: string
}
type TabStatus = 'loading' | 'complete'
type ApiGetterReturn = {
tabs: {
query: (queryInfo: QueryInfo) => Promise<Tab[]>
remove: (tabIds: number | number[]) => unknown
update: (tabId: number, updateProperties: UpdateInfo) => unknown
}
}
}
33 changes: 33 additions & 0 deletions apps/extensions/lib/schemas/tabs.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@
}
}
},
{
"type": "object",
"id": "UpdateInfo",
"properties": {
"active": {
"type": "boolean",
"optional": true
},
"highlighted": {
"type": "boolean",
"optional": true
},
"url": {
"type": "string",
"optional": true
}
}
},
{
"type": "string",
"id": "TabStatus",
Expand Down Expand Up @@ -118,6 +136,21 @@
]
}
]
},
{
"name": "update",
"type": "function",
"async": true,
"parameters": [
{
"name": "tabId",
"type": "integer"
},
{
"name": "updateProperties",
"$ref": "UpdateInfo"
}
]
}
]
}
Expand Down
8 changes: 5 additions & 3 deletions apps/modules/lib/ExtensionTestUtils.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -202,16 +202,18 @@ class ExtensionTestUtilsImpl {
} else if (kind == 'test-log') {
console.info(msg)
} else if (kind == 'test-result') {
testCount += 1
assert.ok(pass, msg)
} else if (kind == 'test-done') {
testCount += 1
assert.truthy(pass, msg)
}
}

extension.on('test-result', handleTestResults)
extension.on('test-eq', handleTestResults)
extension.on('test-log', handleTestResults)
extension.on('test-done', (...args) =>
console.warn('Not Implemented', ...args),
)
extension.on('test-done', handleTestResults)

extension.on('test-message', (...args) => console.log('message', ...args))

Expand Down
162 changes: 162 additions & 0 deletions apps/tests/integrations/extensions/tabs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,165 @@ await TestManager.withBrowser(
})
},
)

await TestManager.withBrowser(
['https://example.com/', 'https://google.com'],
async (window) => {
await spinLock(() =>
window
?.windowTabs()
.map(
(tab) =>
tab.view.browser?.mInitialized &&
tab.view.websiteState === 'complete',
)
.reduce((p, c) => p && c, true),
)

await TestManager.test('tabs - Update - Active', async (test) => {
const extension = ExtensionTestUtils.loadExtension(
{
manifest: {
permissions: ['tabs'],
},
async background() {
/** @type {import('resource://app/modules/ExtensionTestUtils.sys.mjs').TestBrowser} */
const b = this.browser

b.test.onMessage.addListener(async (msg) => {
const windowId = Number(msg)

const windowResults = await b.tabs.query({
windowId,
})
b.test.assertEq(
['https://example.com/', 'https://www.google.com/'].join(','),
[windowResults[0].url, windowResults[1].url].join(','),
'Window is correctly setup',
)
b.test.assertTrue(
windowResults[0].active,
'First tab should be active',
)

await b.tabs.update(windowResults[1].id || -1, { active: true })

const resultsAfter = await b.tabs.query({
windowId,
})
b.test.assertFalse(
resultsAfter[0].active,
'First tab should be inactive',
)
b.test.assertTrue(
resultsAfter[1].active,
'Second tab should be active',
)
b.test.sendMessage('done')
})
},
},
test,
)

await extension
.testCount(4)
.startup()
.then((e) => e.sendMsg(window.windowId.toString()))
.then((e) => e.awaitMsg('done'))
.then((e) => e.unload())
})

await TestManager.test('tabs - Update - Url', async (test) => {
const extension = ExtensionTestUtils.loadExtension(
{
manifest: {
permissions: ['tabs'],
},
async background() {
/** @type {import('resource://app/modules/ExtensionTestUtils.sys.mjs').TestBrowser} */
const b = this.browser

b.test.onMessage.addListener(async (msg) => {
const windowId = Number(msg)

let windowResults = await b.tabs.query({
windowId,
})

try {
await b.tabs.update(windowResults[0].id || -1, {
url: 'chrome://browser/content/gtests.html',
})
b.test.notifyFail('Failed to reject chrome:// url')
} catch (e) {
b.test.notifyPass('Failed to change url to chrome://')
}

try {
await b.tabs.update(windowResults[0].id || -1, {
url: 'file://some/local/path.txt',
})
b.test.notifyFail('Failed to reject file:// url')
} catch (e) {
b.test.notifyPass('Failed to change url to file://')
}

try {
await b.tabs.update(windowResults[0].id || -1, {
url: 'about:addons',
})
b.test.notifyFail('Failed to reject priviged about: url')
} catch (e) {
b.test.notifyPass('Failed to change url to priviged about:')
}

try {
await b.tabs.update(windowResults[0].id || -1, {
url: 'javascript:console.log("Hello world!")',
})
b.test.notifyFail('Failed to reject javascript: url')
} catch (e) {
b.test.notifyPass('Failed to change url to javascript:')
}

try {
await b.tabs.update(windowResults[0].id || -1, {
url: 'data:text/vnd-example+xyz;foo=bar;base64,R0lGODdh',
})
b.test.notifyFail('Failed to reject data: url')
} catch (e) {
b.test.notifyPass('Failed to change url to data:')
}

await b.tabs.update(windowResults[0].id || -1, {
url: 'about:blank',
})
await new Promise((res) => setTimeout(res, 200))

windowResults = await b.tabs.query({
windowId,
})

b.test.assertEq(
'about:blank',
windowResults[0].url,
'URL update works',
)

b.test.sendMessage('done')
})
},
},
test,
)

await extension
.testCount(6)
.startup()
.then((e) => e.sendMsg(window.windowId.toString()))
.then((e) => e.awaitMsg('done'))
.then((e) => e.unload())
})
},
)
2 changes: 2 additions & 0 deletions libs/link/types/globals/WindowApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ declare interface Window {
activeTab: import('@amadeus-it-group/tansu').ReadableSignal<
import('@browser/tabs').WindowTab | undefined
>
activeTabId: import('@amadeus-it-group/tansu').WritableSignal<number>
selectedTabIds: import('@amadeus-it-group/tansu').WritableSignal<number[]>
}
1 change: 1 addition & 0 deletions libs/link/types/windowApi/WebsiteView.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ declare type OklchTheme = {
}

declare type WebsiteViewEvents = {
goTo: string
loadingChange: boolean
progressPercent: number
changeIcon: string
Expand Down

0 comments on commit 5c039c6

Please sign in to comment.