feat(tabs): batch-close tabs by id list
Testing / remote-protocol-compat (0.9.5) (push) Successful in 44s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 45s
Testing / test (push) Successful in 52s
Build & Publish Package / publish (push) Successful in 32s
Package Extension / package-extension (push) Successful in 35s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 44s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 45s
Testing / test (push) Successful in 52s
Build & Publish Package / publish (push) Successful in 32s
Package Extension / package-extension (push) Successful in 35s
Closing many tabs previously meant one IPC round-trip per tab (tab.close() in a loop). Add a single batched path so callers can close N tabs in one command, reusing the existing large-operation throttle so the browser UI stays responsive. - extension: tabs.close accepts tabIds: number[]; new branch feeds the array through processInBatches/chrome.tabs.remove - sdk: tabs_close(tab_ids=...) takes tab IDs or Tab objects; the payload always carries "tabIds" (null when unused) - tests: cover id-list and Tab-object batch close in test_api.py - bump 0.10.3 -> 0.10.4 (pyproject.toml, manifest.json)
This commit is contained in:
+23
-3
@@ -308,9 +308,29 @@ class BrowserCLI:
|
|||||||
]
|
]
|
||||||
return [self._make_tab(t) for t in (self._cmd("tabs.list", {}) or [])]
|
return [self._make_tab(t) for t in (self._cmd("tabs.list", {}) or [])]
|
||||||
|
|
||||||
def tabs_close(self, tab_id: int | None = None, *, inactive: bool = False, duplicates: bool = False) -> int:
|
def tabs_close(
|
||||||
"""Close tab(s). Returns the number of tabs closed."""
|
self,
|
||||||
result = self._cmd("tabs.close", {"tabId": tab_id, "inactive": inactive, "duplicates": duplicates})
|
tab_id: int | None = None,
|
||||||
|
*,
|
||||||
|
tab_ids: Iterable[int | Tab] | None = None,
|
||||||
|
inactive: bool = False,
|
||||||
|
duplicates: bool = False,
|
||||||
|
) -> int:
|
||||||
|
"""Close tab(s). Returns the number of tabs closed.
|
||||||
|
|
||||||
|
Pass ``tab_ids`` to close many tabs in a single round-trip instead of
|
||||||
|
calling :meth:`close_tab` per tab. Accepts tab IDs or :class:`Tab`
|
||||||
|
objects. The extension throttles large batches automatically.
|
||||||
|
"""
|
||||||
|
ids = None
|
||||||
|
if tab_ids is not None:
|
||||||
|
ids = [t.id if isinstance(t, Tab) else t for t in tab_ids]
|
||||||
|
result = self._cmd("tabs.close", {
|
||||||
|
"tabId": tab_id,
|
||||||
|
"tabIds": ids,
|
||||||
|
"inactive": inactive,
|
||||||
|
"duplicates": duplicates,
|
||||||
|
})
|
||||||
return result.get("closed", 1) if isinstance(result, dict) else 1
|
return result.get("closed", 1) if isinstance(result, dict) else 1
|
||||||
|
|
||||||
def tabs_move(
|
def tabs_move(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "browser-cli",
|
"name": "browser-cli",
|
||||||
"version": "0.10.3",
|
"version": "0.10.4",
|
||||||
"description": "Control your browser from the terminal or Python SDK",
|
"description": "Control your browser from the terminal or Python SDK",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"tabs",
|
"tabs",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export class TabsMutationCommands extends CommandGroup {
|
|||||||
"tabs.screenshot": (a: TabsScreenshotArgs) => this.tabsScreenshot(a),
|
"tabs.screenshot": (a: TabsScreenshotArgs) => this.tabsScreenshot(a),
|
||||||
};
|
};
|
||||||
|
|
||||||
private async tabsClose({ tabId, inactive, duplicates, gentleMode, __job }: TabsCloseArgs = {}) {
|
private async tabsClose({ tabId, tabIds, inactive, duplicates, gentleMode, __job }: TabsCloseArgs = {}) {
|
||||||
return runLargeOperation("tabs.close", async () => {
|
return runLargeOperation("tabs.close", async () => {
|
||||||
let toClose: number[] = [];
|
let toClose: number[] = [];
|
||||||
if (duplicates) {
|
if (duplicates) {
|
||||||
@@ -33,6 +33,8 @@ export class TabsMutationCommands extends CommandGroup {
|
|||||||
} else if (inactive) {
|
} else if (inactive) {
|
||||||
const all = await chrome.tabs.query({});
|
const all = await chrome.tabs.query({});
|
||||||
toClose = all.filter(t => !t.active).map(t => t.id);
|
toClose = all.filter(t => !t.active).map(t => t.id);
|
||||||
|
} else if (tabIds?.length) {
|
||||||
|
toClose = tabIds.filter(id => id != null);
|
||||||
} else if (tabId) {
|
} else if (tabId) {
|
||||||
toClose = [tabId];
|
toClose = [tabId];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export interface NavOpenWaitArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Tabs ──────────────────────────────────────────────────────────────────────
|
// ── Tabs ──────────────────────────────────────────────────────────────────────
|
||||||
export interface TabsCloseArgs { tabId?: number; inactive?: boolean; duplicates?: boolean; gentleMode?: string; __job?: Job; }
|
export interface TabsCloseArgs { tabId?: number; tabIds?: number[]; inactive?: boolean; duplicates?: boolean; gentleMode?: string; __job?: Job; }
|
||||||
export interface TabsMoveArgs { tabId?: number; groupId?: number; windowId?: number; index?: number; forward?: boolean; backward?: boolean; }
|
export interface TabsMoveArgs { tabId?: number; groupId?: number; windowId?: number; index?: number; forward?: boolean; backward?: boolean; }
|
||||||
export interface TabIdArgs { tabId?: number; }
|
export interface TabIdArgs { tabId?: number; }
|
||||||
export interface TabsActiveInWindowArgs { windowId?: number; }
|
export interface TabsActiveInWindowArgs { windowId?: number; }
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "browser-cli"
|
name = "browser-cli"
|
||||||
version = "0.10.3"
|
version = "0.10.4"
|
||||||
description = "Control your real running browser from the terminal or Python SDK"
|
description = "Control your real running browser from the terminal or Python SDK"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
+22
-3
@@ -305,7 +305,26 @@ class TestTabs:
|
|||||||
b.tabs_close(tab_id=10)
|
b.tabs_close(tab_id=10)
|
||||||
mock_send.assert_called_once_with(
|
mock_send.assert_called_once_with(
|
||||||
"tabs.close",
|
"tabs.close",
|
||||||
{"tabId": 10, "inactive": False, "duplicates": False},
|
{"tabId": 10, "tabIds": None, "inactive": False, "duplicates": False},
|
||||||
|
profile=None, remote=None, key=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_tabs_close_by_ids(self, b, mock_send):
|
||||||
|
mock_send.return_value = {"closed": 3}
|
||||||
|
assert b.tabs_close(tab_ids=[10, 20, 30]) == 3
|
||||||
|
mock_send.assert_called_once_with(
|
||||||
|
"tabs.close",
|
||||||
|
{"tabId": None, "tabIds": [10, 20, 30], "inactive": False, "duplicates": False},
|
||||||
|
profile=None, remote=None, key=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_tabs_close_by_ids_accepts_tab_objects(self, b, mock_send):
|
||||||
|
tabs = [b._make_tab({**TAB_DATA, "id": 10}), b._make_tab({**TAB_DATA, "id": 20})]
|
||||||
|
mock_send.return_value = {"closed": 2}
|
||||||
|
assert b.tabs_close(tab_ids=tabs) == 2
|
||||||
|
mock_send.assert_called_once_with(
|
||||||
|
"tabs.close",
|
||||||
|
{"tabId": None, "tabIds": [10, 20], "inactive": False, "duplicates": False},
|
||||||
profile=None, remote=None, key=None,
|
profile=None, remote=None, key=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -446,8 +465,8 @@ class TestTabs:
|
|||||||
call("tabs.status", {"tabId": 10}, profile=None, remote=None, key=None),
|
call("tabs.status", {"tabId": 10}, profile=None, remote=None, key=None),
|
||||||
call("tabs.query", {"search": "Example"}, profile=None, remote=None, key=None),
|
call("tabs.query", {"search": "Example"}, profile=None, remote=None, key=None),
|
||||||
call("tabs.query", {"search": "Missing"}, profile=None, remote=None, key=None),
|
call("tabs.query", {"search": "Missing"}, profile=None, remote=None, key=None),
|
||||||
call("tabs.close", {"tabId": 10, "inactive": False, "duplicates": False}, profile=None, remote=None, key=None),
|
call("tabs.close", {"tabId": 10, "tabIds": None, "inactive": False, "duplicates": False}, profile=None, remote=None, key=None),
|
||||||
call("tabs.close", {"tabId": 10, "inactive": False, "duplicates": False}, profile=None, remote=None, key=None),
|
call("tabs.close", {"tabId": 10, "tabIds": None, "inactive": False, "duplicates": False}, profile=None, remote=None, key=None),
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_find_tabs_alias(self, b, mock_send):
|
def test_find_tabs_alias(self, b, mock_send):
|
||||||
|
|||||||
Reference in New Issue
Block a user