chrome-devtools-frontend 1.0.1632065 → 1.0.1635876
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/front_end/core/host/UserMetrics.ts +5 -2
- package/front_end/core/root/ExperimentNames.ts +1 -0
- package/front_end/core/root/Runtime.ts +5 -0
- package/front_end/core/sdk/SourceMapCache.ts +13 -11
- package/front_end/core/sdk/SourceMapManager.ts +7 -3
- package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +84 -22
- package/front_end/entrypoints/main/MainImpl.ts +8 -0
- package/front_end/generated/InspectorBackendCommands.ts +3 -4
- package/front_end/generated/SupportedCSSProperties.js +272 -2
- package/front_end/generated/protocol-mapping.d.ts +0 -10
- package/front_end/generated/protocol-proxy-api.d.ts +0 -8
- package/front_end/generated/protocol.ts +5 -7
- package/front_end/models/ai_assistance/AiConversation.ts +16 -11
- package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +13 -2
- package/front_end/models/ai_assistance/agents/AiAgent.ts +65 -11
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +42 -2
- package/front_end/models/ai_assistance/agents/GreenDevAgent.ts +68 -5
- package/front_end/models/ai_assistance/agents/GreenDevAgentAntigravityCliSocketClient.ts +53 -0
- package/front_end/models/ai_assistance/agents/GreenDevAgentGeminiCliSocketClient.ts +117 -0
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +30 -1
- package/front_end/models/ai_assistance/ai_assistance.ts +4 -0
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +4 -2
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +5 -2
- package/front_end/models/extensions/RecorderExtensionEndpoint.ts +9 -1
- package/front_end/models/extensions/RecorderPluginManager.ts +1 -0
- package/front_end/models/greendev/Prototypes.ts +17 -5
- package/front_end/models/trace/handlers/FramesHandler.ts +19 -13
- package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +3 -0
- package/front_end/panels/ai_assistance/components/ChatInput.ts +4 -2
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +3 -1
- package/front_end/panels/application/preloading/components/PreloadingString.ts +0 -8
- package/front_end/panels/common/ExtensionServer.ts +34 -11
- package/front_end/panels/elements/CSSRuleValidator.ts +37 -34
- package/front_end/panels/elements/CSSRuleValidatorHelper.ts +8 -6
- package/front_end/panels/elements/ElementsTreeElement.ts +8 -2
- package/front_end/panels/elements/components/CSSHintDetailsView.ts +5 -5
- package/front_end/panels/js_timeline/js_timeline-meta.ts +30 -0
- package/front_end/panels/protocol_monitor/JSONEditor.ts +4 -4
- package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +2 -2
- package/front_end/panels/recorder/RecorderController.ts +50 -1
- package/front_end/panels/recorder/extensions/ExtensionManager.ts +1 -0
- package/front_end/panels/recorder/models/RecordingPlayer.ts +12 -3
- package/front_end/panels/recorder/testing/RecorderHelpers.ts +2 -0
- package/front_end/panels/settings/SettingsScreen.ts +3 -2
- package/front_end/panels/sources/FilteredUISourceCodeListProvider.ts +5 -2
- package/front_end/panels/timeline/timeline-meta.ts +10 -6
- package/front_end/panels/whats_new/ReleaseNoteText.ts +9 -9
- package/front_end/panels/whats_new/resources/WNDT.md +9 -9
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +8 -5
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +35 -33
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/TargetManager.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/TargetManager.js +0 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/TargetManager.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/WebMCP.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/WebMCP.js +0 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/WebMCP.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts +8 -8
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js +8 -8
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/generated/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/generated/injected.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/generated/injected.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/generated/injected.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/CustomQuerySelector.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/CustomQuerySelector.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/CustomQuerySelector.js +2 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/CustomQuerySelector.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/injected.d.ts +2 -7
- package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/injected.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node/PuppeteerNode.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/node/PuppeteerNode.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/Mutex.d.ts +10 -7
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/Mutex.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/Mutex.js +16 -12
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/Mutex.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/types.d.ts +8 -5
- package/front_end/third_party/puppeteer/package/package.json +5 -7
- package/front_end/third_party/puppeteer/package/src/cdp/TargetManager.ts +0 -1
- package/front_end/third_party/puppeteer/package/src/cdp/WebMCP.ts +0 -3
- package/front_end/third_party/puppeteer/package/src/common/Debug.ts +11 -11
- package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/injected/CustomQuerySelector.ts +3 -2
- package/front_end/third_party/puppeteer/package/src/node/PuppeteerNode.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
- package/front_end/third_party/puppeteer/package/src/util/Mutex.ts +17 -12
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/ui/components/markdown_view/MarkdownLinksMap.ts +7 -0
- package/front_end/ui/legacy/InspectorDrawerView.ts +2 -1
- package/front_end/ui/legacy/InspectorView.ts +1 -1
- package/front_end/ui/legacy/PlusButton.ts +269 -0
- package/front_end/ui/legacy/ViewManager.ts +38 -11
- package/front_end/ui/legacy/components/source_frame/FontView.ts +11 -3
- package/front_end/ui/legacy/components/source_frame/ImageView.ts +16 -0
- package/front_end/ui/legacy/components/source_frame/imageView.css +13 -0
- package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -1
- package/front_end/ui/legacy/legacy.ts +2 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +21 -0
- package/mcp/mcp.ts +1 -1
- package/package.json +1 -10
|
@@ -1580,7 +1580,7 @@ export declare interface CookieData {
|
|
|
1580
1580
|
/**
|
|
1581
1581
|
* Cookie SameSite type.
|
|
1582
1582
|
*/
|
|
1583
|
-
sameSite?:
|
|
1583
|
+
sameSite?: CookieSameSite_2;
|
|
1584
1584
|
/**
|
|
1585
1585
|
* Cookie expiration date, session cookie if not set
|
|
1586
1586
|
*/
|
|
@@ -1641,7 +1641,7 @@ export declare interface CookieParam {
|
|
|
1641
1641
|
/**
|
|
1642
1642
|
* Cookie SameSite type.
|
|
1643
1643
|
*/
|
|
1644
|
-
sameSite?:
|
|
1644
|
+
sameSite?: CookieSameSite_2;
|
|
1645
1645
|
/**
|
|
1646
1646
|
* Cookie expiration date, session cookie if not set
|
|
1647
1647
|
*/
|
|
@@ -1704,7 +1704,8 @@ export declare type CookiePriority = 'Low' | 'Medium' | 'High';
|
|
|
1704
1704
|
*
|
|
1705
1705
|
* @public
|
|
1706
1706
|
*/
|
|
1707
|
-
|
|
1707
|
+
declare type CookieSameSite_2 = 'Strict' | 'Lax' | 'None' | 'Default';
|
|
1708
|
+
export type {CookieSameSite_2 as CookieSameSite};
|
|
1708
1709
|
|
|
1709
1710
|
/**
|
|
1710
1711
|
* Represents the source scheme of the origin that originally set the cookie. A value of
|
|
@@ -1889,6 +1890,7 @@ declare interface CustomQuerySelector {
|
|
|
1889
1890
|
|
|
1890
1891
|
/**
|
|
1891
1892
|
* This class mimics the injected {@link CustomQuerySelectorRegistry}.
|
|
1893
|
+
*
|
|
1892
1894
|
*/
|
|
1893
1895
|
declare class CustomQuerySelectorRegistry {
|
|
1894
1896
|
#private;
|
|
@@ -1900,6 +1902,7 @@ declare class CustomQuerySelectorRegistry {
|
|
|
1900
1902
|
|
|
1901
1903
|
declare namespace CustomQuerySelectors {
|
|
1902
1904
|
export type {CustomQuerySelector};
|
|
1905
|
+
export {CustomQuerySelectorRegistry};
|
|
1903
1906
|
}
|
|
1904
1907
|
|
|
1905
1908
|
/**
|
|
@@ -8249,7 +8252,7 @@ declare namespace Puppeteer_2 {
|
|
|
8249
8252
|
ConnectOptions,
|
|
8250
8253
|
ConsoleMessageLocation,
|
|
8251
8254
|
ConsoleMessageType,
|
|
8252
|
-
CookieSameSite,
|
|
8255
|
+
CookieSameSite_2 as CookieSameSite,
|
|
8253
8256
|
CookiePriority,
|
|
8254
8257
|
CookieSourceScheme,
|
|
8255
8258
|
CookiePartitionKey,
|
|
@@ -8388,7 +8391,7 @@ export declare type PuppeteerLifeCycleEvent =
|
|
|
8388
8391
|
* fetching and downloading browsers.
|
|
8389
8392
|
*
|
|
8390
8393
|
* If you're using Puppeteer in a Node environment, this is the class you'll get
|
|
8391
|
-
* when you run `
|
|
8394
|
+
* when you run `import puppeteer from 'puppeteer'`.
|
|
8392
8395
|
*
|
|
8393
8396
|
* @remarks
|
|
8394
8397
|
* The most common method to use is {@link PuppeteerNode.launch | launch}, which
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "puppeteer-core",
|
|
3
|
-
"version": "25.0
|
|
3
|
+
"version": "25.1.0",
|
|
4
4
|
"description": "A high-level API to control headless Chrome over the DevTools Protocol",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"puppeteer",
|
|
@@ -148,17 +148,15 @@
|
|
|
148
148
|
"author": "The Chromium Authors",
|
|
149
149
|
"license": "Apache-2.0",
|
|
150
150
|
"dependencies": {
|
|
151
|
-
"@puppeteer/browsers": "3.0.
|
|
151
|
+
"@puppeteer/browsers": "3.0.4",
|
|
152
152
|
"chromium-bidi": "16.0.1",
|
|
153
|
-
"
|
|
154
|
-
"devtools-protocol": "0.0.1608973",
|
|
153
|
+
"devtools-protocol": "0.0.1624250",
|
|
155
154
|
"typed-query-selector": "^2.12.2",
|
|
156
|
-
"webdriver-bidi-protocol": "0.4.
|
|
157
|
-
"ws": "^8.
|
|
155
|
+
"webdriver-bidi-protocol": "0.4.2",
|
|
156
|
+
"ws": "^8.21.0"
|
|
158
157
|
},
|
|
159
158
|
"devDependencies": {
|
|
160
159
|
"@types/chrome": "0.1.40",
|
|
161
|
-
"@types/debug": "4.1.13",
|
|
162
160
|
"@types/node": "^18.17.15",
|
|
163
161
|
"@types/ws": "8.18.1",
|
|
164
162
|
"mitt": "3.0.1",
|
|
@@ -548,7 +548,6 @@ export class TargetManager
|
|
|
548
548
|
}
|
|
549
549
|
|
|
550
550
|
await session.send('Network.emulateNetworkConditionsByRule', {
|
|
551
|
-
// @ts-expect-error offline cannot be undefined before M149.
|
|
552
551
|
offline: this.#blocklist.length > 0 ? true : undefined,
|
|
553
552
|
matchedNetworkConditions,
|
|
554
553
|
});
|
|
@@ -407,7 +407,6 @@ export class WebMCP extends EventEmitter<{
|
|
|
407
407
|
tool: WebMCPTool,
|
|
408
408
|
input: object,
|
|
409
409
|
): Promise<{invocationId: string}> {
|
|
410
|
-
// @ts-expect-error WebMCP is not yet in the Protocol types.
|
|
411
410
|
return await this.#client.send('WebMCP.invokeTool', {
|
|
412
411
|
frameId: tool.frame._id,
|
|
413
412
|
toolName: tool.name,
|
|
@@ -428,7 +427,6 @@ export class WebMCP extends EventEmitter<{
|
|
|
428
427
|
this.#client.on('WebMCP.toolsAdded', this.#onToolsAdded);
|
|
429
428
|
this.#client.on('WebMCP.toolsRemoved', this.#onToolsRemoved);
|
|
430
429
|
this.#client.on('WebMCP.toolInvoked', this.#onToolInvoked);
|
|
431
|
-
// @ts-expect-error M148 has non-final status type, update expected in M149
|
|
432
430
|
this.#client.on('WebMCP.toolResponded', this.#onToolResponded);
|
|
433
431
|
}
|
|
434
432
|
|
|
@@ -439,7 +437,6 @@ export class WebMCP extends EventEmitter<{
|
|
|
439
437
|
this.#client.off('WebMCP.toolsAdded', this.#onToolsAdded);
|
|
440
438
|
this.#client.off('WebMCP.toolsRemoved', this.#onToolsRemoved);
|
|
441
439
|
this.#client.off('WebMCP.toolInvoked', this.#onToolInvoked);
|
|
442
|
-
// @ts-expect-error M148 has non-final status type, update expected in M149
|
|
443
440
|
this.#client.off('WebMCP.toolResponded', this.#onToolResponded);
|
|
444
441
|
this.#client = client;
|
|
445
442
|
this.#bindListeners();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type
|
|
7
|
+
import type {debuglog} from 'node:util';
|
|
8
8
|
|
|
9
9
|
import {isNode} from '../environment.js';
|
|
10
10
|
|
|
@@ -15,13 +15,13 @@ declare global {
|
|
|
15
15
|
/**
|
|
16
16
|
* @internal
|
|
17
17
|
*/
|
|
18
|
-
let debugModule: typeof
|
|
18
|
+
let debugModule: typeof debuglog | null = null;
|
|
19
19
|
/**
|
|
20
20
|
* @internal
|
|
21
21
|
*/
|
|
22
|
-
export async function importDebug(): Promise<typeof
|
|
22
|
+
export async function importDebug(): Promise<typeof debuglog> {
|
|
23
23
|
if (!debugModule) {
|
|
24
|
-
debugModule = (await import('
|
|
24
|
+
debugModule = (await import('node:util')).debuglog;
|
|
25
25
|
}
|
|
26
26
|
return debugModule;
|
|
27
27
|
}
|
|
@@ -30,16 +30,16 @@ export async function importDebug(): Promise<typeof Debug> {
|
|
|
30
30
|
* A debug function that can be used in any environment.
|
|
31
31
|
*
|
|
32
32
|
* @remarks
|
|
33
|
-
* If used in Node, it falls back to
|
|
34
|
-
* {@link https://
|
|
33
|
+
* If used in Node, it falls back to Node's built-in
|
|
34
|
+
* {@link https://nodejs.org/api/util.html#utildebuglogsection-callback | util.debuglog}. In the browser it
|
|
35
35
|
* uses `console.log`.
|
|
36
36
|
*
|
|
37
|
-
* In Node, use the `
|
|
37
|
+
* In Node, use the `NODE_DEBUG` environment variable to control logging:
|
|
38
38
|
*
|
|
39
39
|
* ```
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* NODE_DEBUG=* // logs all channels
|
|
41
|
+
* NODE_DEBUG=foo // logs the `foo` channel
|
|
42
|
+
* NODE_DEBUG=foo* // logs any channels starting with `foo`
|
|
43
43
|
* ```
|
|
44
44
|
*
|
|
45
45
|
* In the browser, set `window.__PUPPETEER_DEBUG` to a string:
|
|
@@ -70,7 +70,7 @@ export const debug = (prefix: string): ((...args: unknown[]) => void) => {
|
|
|
70
70
|
if (captureLogs) {
|
|
71
71
|
capturedLogs.push(prefix + logArgs);
|
|
72
72
|
}
|
|
73
|
-
(await importDebug())(prefix)(logArgs);
|
|
73
|
+
((await importDebug())(prefix) as (...args: any[]) => void)(...logArgs);
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @internal
|
|
7
7
|
*/
|
|
8
|
-
export const source = "\"use strict\";var
|
|
8
|
+
export const source = "\"use strict\";var N=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var l=(t,e)=>{for(var r in e)N(t,r,{get:e[r],enumerable:!0})},G=(t,e,r,o)=>{if(e&&typeof e==\"object\"||typeof e==\"function\")for(let s of B(e))!Y.call(t,s)&&s!==r&&N(t,s,{get:()=>e[s],enumerable:!(o=X(e,s))||o.enumerable});return t};var J=t=>G(N({},\"__esModule\",{value:!0}),t);var pe={};l(pe,{default:()=>he});module.exports=J(pe);var x=class extends Error{constructor(e,r){super(e,r),this.name=this.constructor.name}get[Symbol.toStringTag](){return this.constructor.name}},p=class extends x{};var c=class t{static create(e){return new t(e)}static async race(e){let r=new Set;try{let o=e.map(s=>s instanceof t?(s.#s&&r.add(s),s.valueOrThrow()):s);return await Promise.race(o)}finally{for(let o of r)o.reject(new Error(\"Timeout cleared\"))}}#e=!1;#r=!1;#o;#t;#a=new Promise(e=>{this.#t=e});#s;#i;constructor(e){e&&e.timeout>0&&(this.#i=new p(e.message),this.#s=setTimeout(()=>{this.reject(this.#i)},e.timeout))}#l(e){clearTimeout(this.#s),this.#o=e,this.#t()}resolve(e){this.#r||this.#e||(this.#e=!0,this.#l(e))}reject(e){this.#r||this.#e||(this.#r=!0,this.#l(e))}resolved(){return this.#e}finished(){return this.#e||this.#r}value(){return this.#o}#n;valueOrThrow(){return this.#n||(this.#n=(async()=>{if(await this.#a,this.#r)throw this.#o;return this.#o})()),this.#n}};var L=new Map,W=t=>{let e=L.get(t);return e||(e=new Function(`return ${t}`)(),L.set(t,e),e)};var E={};l(E,{ariaQuerySelector:()=>z,ariaQuerySelectorAll:()=>b});var z=(t,e)=>globalThis.__ariaQuerySelector(t,e),b=async function*(t,e){yield*await globalThis.__ariaQuerySelectorAll(t,e)};var v={};l(v,{cssQuerySelector:()=>K,cssQuerySelectorAll:()=>Z});var K=(t,e)=>t.querySelector(e),Z=function(t,e){return t.querySelectorAll(e)};var A={};l(A,{CustomQuerySelectorRegistry:()=>y,customQuerySelectors:()=>P});var y=class{#e=new Map;register(e,r){if(!r.queryOne&&r.queryAll){let o=r.queryAll;r.queryOne=(s,i)=>{for(let n of o(s,i))return n;return null}}else if(r.queryOne&&!r.queryAll){let o=r.queryOne;r.queryAll=(s,i)=>{let n=o(s,i);return n?[n]:[]}}else if(!r.queryOne||!r.queryAll)throw new Error(\"At least one query method must be defined.\");this.#e.set(e,{querySelector:r.queryOne,querySelectorAll:r.queryAll})}unregister(e){this.#e.delete(e)}get(e){return this.#e.get(e)}clear(){this.#e.clear()}},P=new y;var R={};l(R,{pierceQuerySelector:()=>ee,pierceQuerySelectorAll:()=>te});var ee=(t,e)=>{let r=null,o=s=>{let i=document.createTreeWalker(s,NodeFilter.SHOW_ELEMENT);do{let n=i.currentNode;n.shadowRoot&&o(n.shadowRoot),!(n instanceof ShadowRoot)&&n!==s&&!r&&n.matches(e)&&(r=n)}while(!r&&i.nextNode())};return t instanceof Document&&(t=t.documentElement),o(t),r},te=(t,e)=>{let r=[],o=s=>{let i=document.createTreeWalker(s,NodeFilter.SHOW_ELEMENT);do{let n=i.currentNode;n.shadowRoot&&o(n.shadowRoot),!(n instanceof ShadowRoot)&&n!==s&&n.matches(e)&&r.push(n)}while(i.nextNode())};return t instanceof Document&&(t=t.documentElement),o(t),r};var u=(t,e)=>{if(!t)throw new Error(e)};var w=class{#e;#r;#o;#t;constructor(e,r){this.#e=e,this.#r=r}async start(){let e=this.#t=c.create(),r=await this.#e();if(r){e.resolve(r);return}this.#o=new MutationObserver(async()=>{let o=await this.#e();o&&(e.resolve(o),await this.stop())}),this.#o.observe(this.#r,{childList:!0,subtree:!0,attributes:!0})}async stop(){u(this.#t,\"Polling never started.\"),this.#t.finished()||this.#t.reject(new Error(\"Polling stopped\")),this.#o&&(this.#o.disconnect(),this.#o=void 0)}result(){return u(this.#t,\"Polling never started.\"),this.#t.valueOrThrow()}},T=class{#e;#r;constructor(e){this.#e=e}async start(){let e=this.#r=c.create(),r=await this.#e();if(r){e.resolve(r);return}let o=async()=>{if(e.finished())return;let s=await this.#e();if(!s){window.requestAnimationFrame(o);return}e.resolve(s),await this.stop()};window.requestAnimationFrame(o)}async stop(){u(this.#r,\"Polling never started.\"),this.#r.finished()||this.#r.reject(new Error(\"Polling stopped\"))}result(){return u(this.#r,\"Polling never started.\"),this.#r.valueOrThrow()}},S=class{#e;#r;#o;#t;constructor(e,r){this.#e=e,this.#r=r}async start(){let e=this.#t=c.create(),r=await this.#e();if(r){e.resolve(r);return}this.#o=setInterval(async()=>{let o=await this.#e();o&&(e.resolve(o),await this.stop())},this.#r)}async stop(){u(this.#t,\"Polling never started.\"),this.#t.finished()||this.#t.reject(new Error(\"Polling stopped\")),this.#o&&(clearInterval(this.#o),this.#o=void 0)}result(){return u(this.#t,\"Polling never started.\"),this.#t.valueOrThrow()}};var _={};l(_,{PCombinator:()=>H,pQuerySelector:()=>fe,pQuerySelectorAll:()=>$});var a=class{static async*map(e,r){for await(let o of e)yield await r(o)}static async*flatMap(e,r){for await(let o of e)yield*r(o)}static async collect(e){let r=[];for await(let o of e)r.push(o);return r}static async first(e){for await(let r of e)return r}};var C={};l(C,{textQuerySelectorAll:()=>m});var re=new Set([\"checkbox\",\"image\",\"radio\"]),oe=t=>t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement||t instanceof HTMLInputElement&&!re.has(t.type),se=new Set([\"SCRIPT\",\"STYLE\"]),f=t=>!se.has(t.nodeName)&&!document.head?.contains(t),I=new WeakMap,F=t=>{for(;t;)I.delete(t),t instanceof ShadowRoot?t=t.host:t=t.parentNode},j=new WeakSet,ne=new MutationObserver(t=>{for(let e of t)F(e.target)}),d=t=>{let e=I.get(t);if(e||(e={full:\"\",immediate:[]},!f(t)))return e;let r=\"\";if(oe(t))e.full=t.value,e.immediate.push(t.value),t.addEventListener(\"input\",o=>{F(o.target)},{once:!0,capture:!0});else{for(let o=t.firstChild;o;o=o.nextSibling){if(o.nodeType===Node.TEXT_NODE){e.full+=o.nodeValue??\"\",r+=o.nodeValue??\"\";continue}r&&e.immediate.push(r),r=\"\",o.nodeType===Node.ELEMENT_NODE&&(e.full+=d(o).full)}r&&e.immediate.push(r),t instanceof Element&&t.shadowRoot&&(e.full+=d(t.shadowRoot).full),j.has(t)||(ne.observe(t,{childList:!0,characterData:!0,subtree:!0}),j.add(t))}return I.set(t,e),e};var m=function*(t,e){let r=!1;for(let o of t.childNodes)if(o instanceof Element&&f(o)){let s;o.shadowRoot?s=m(o.shadowRoot,e):s=m(o,e);for(let i of s)yield i,r=!0}r||t instanceof Element&&f(t)&&d(t).full.includes(e)&&(yield t)};var k={};l(k,{checkVisibility:()=>le,pierce:()=>g,pierceAll:()=>O});var ie=[\"hidden\",\"collapse\"],le=(t,e)=>{if(!t)return e===!1;if(e===void 0)return t;let r=t.nodeType===Node.TEXT_NODE?t.parentElement:t,o=window.getComputedStyle(r),s=o&&!ie.includes(o.visibility)&&!ae(r);return e===s?t:!1};function ae(t){let e=t.getBoundingClientRect();return e.width===0||e.height===0}var ce=t=>\"shadowRoot\"in t&&t.shadowRoot instanceof ShadowRoot;function*g(t){ce(t)?yield t.shadowRoot:yield t}function*O(t){t=g(t).next().value,yield t;let e=[document.createTreeWalker(t,NodeFilter.SHOW_ELEMENT)];for(let r of e){let o;for(;o=r.nextNode();)o.shadowRoot&&(yield o.shadowRoot,e.push(document.createTreeWalker(o.shadowRoot,NodeFilter.SHOW_ELEMENT)))}}var D={};l(D,{xpathQuerySelectorAll:()=>q});var q=function*(t,e,r=-1){let s=(t.ownerDocument||document).evaluate(e,t,null,XPathResult.ORDERED_NODE_ITERATOR_TYPE),i=[],n;for(;(n=s.iterateNext())&&(i.push(n),!(r&&i.length===r)););for(let h=0;h<i.length;h++)n=i[h],yield n,i[h]=null};var ue=/[-\\w\\P{ASCII}*]/u,H=(r=>(r.Descendent=\">>>\",r.Child=\">>>>\",r))(H||{}),V=t=>\"querySelectorAll\"in t,Q=class{#e;#r=[];#o=void 0;elements;constructor(e,r){this.elements=[e],this.#e=r,this.#t()}async run(){for(typeof this.#o==\"string\"&&this.#o.trimStart()===\":scope\"&&this.#t();this.#o!==void 0;this.#t()){let e=this.#o;typeof e==\"string\"?e[0]&&ue.test(e[0])?this.elements=a.flatMap(this.elements,async function*(r){V(r)&&(yield*r.querySelectorAll(e))}):this.elements=a.flatMap(this.elements,async function*(r){if(!r.parentElement){if(!V(r))return;yield*r.querySelectorAll(e);return}let o=0;for(let s of r.parentElement.children)if(++o,s===r)break;yield*r.parentElement.querySelectorAll(`:scope>:nth-child(${o})${e}`)}):this.elements=a.flatMap(this.elements,async function*(r){switch(e.name){case\"text\":yield*m(r,e.value);break;case\"xpath\":yield*q(r,e.value);break;case\"aria\":yield*b(r,e.value);break;default:let o=P.get(e.name);if(!o)throw new Error(`Unknown selector type: ${e.name}`);yield*o.querySelectorAll(r,e.value)}})}}#t(){if(this.#r.length!==0){this.#o=this.#r.shift();return}if(this.#e.length===0){this.#o=void 0;return}let e=this.#e.shift();switch(e){case\">>>>\":{this.elements=a.flatMap(this.elements,g),this.#t();break}case\">>>\":{this.elements=a.flatMap(this.elements,O),this.#t();break}default:this.#r=e,this.#t();break}}},M=class{#e=new WeakMap;calculate(e,r=[]){if(e===null)return r;e instanceof ShadowRoot&&(e=e.host);let o=this.#e.get(e);if(o)return[...o,...r];let s=0;for(let n=e.previousSibling;n;n=n.previousSibling)++s;let i=this.calculate(e.parentNode,[s]);return this.#e.set(e,i),[...i,...r]}},U=(t,e)=>{if(t.length+e.length===0)return 0;let[r=-1,...o]=t,[s=-1,...i]=e;return r===s?U(o,i):r<s?-1:1},de=async function*(t){let e=new Set;for await(let o of t)e.add(o);let r=new M;yield*[...e.values()].map(o=>[o,r.calculate(o)]).sort(([,o],[,s])=>U(o,s)).map(([o])=>o)},$=function(t,e){let r=JSON.parse(e);if(r.some(o=>{let s=0;return o.some(i=>(typeof i==\"string\"?++s:s=0,s>1))}))throw new Error(\"Multiple deep combinators found in sequence.\");return de(a.flatMap(r,o=>{let s=new Q(t,o);return s.run(),s.elements}))},fe=async function(t,e){for await(let r of $(t,e))return r;return null};var me=Object.freeze({...E,...A,...R,..._,...C,...k,...D,...v,Deferred:c,createFunction:W,createTextContent:d,IntervalPoller:S,isSuitableNodeForTextMatching:f,MutationPoller:w,RAFPoller:T}),he=me;\n";
|
|
@@ -17,8 +17,9 @@ export interface CustomQuerySelector {
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* This class mimics the injected {@link CustomQuerySelectorRegistry}.
|
|
20
|
+
*
|
|
20
21
|
*/
|
|
21
|
-
class CustomQuerySelectorRegistry {
|
|
22
|
+
export class CustomQuerySelectorRegistry {
|
|
22
23
|
#selectors = new Map<string, CustomQuerySelector>();
|
|
23
24
|
|
|
24
25
|
register(name: string, handler: CustomQueryHandler): void {
|
|
@@ -54,7 +55,7 @@ class CustomQuerySelectorRegistry {
|
|
|
54
55
|
return this.#selectors.get(name);
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
clear() {
|
|
58
|
+
clear(): void {
|
|
58
59
|
this.#selectors.clear();
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -29,7 +29,7 @@ import type {ChromeReleaseChannel, LaunchOptions} from './LaunchOptions.js';
|
|
|
29
29
|
* fetching and downloading browsers.
|
|
30
30
|
*
|
|
31
31
|
* If you're using Puppeteer in a Node environment, this is the class you'll get
|
|
32
|
-
* when you run `
|
|
32
|
+
* when you run `import puppeteer from 'puppeteer'`.
|
|
33
33
|
*
|
|
34
34
|
* @remarks
|
|
35
35
|
* The most common method to use is {@link PuppeteerNode.launch | launch}, which
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @internal
|
|
9
9
|
*/
|
|
10
10
|
export const PUPPETEER_REVISIONS = Object.freeze({
|
|
11
|
-
chrome: '
|
|
12
|
-
'chrome-headless-shell': '
|
|
13
|
-
firefox: '
|
|
11
|
+
chrome: '149.0.7827.22',
|
|
12
|
+
'chrome-headless-shell': '149.0.7827.22',
|
|
13
|
+
firefox: 'stable_151.0',
|
|
14
14
|
});
|
|
@@ -6,22 +6,27 @@
|
|
|
6
6
|
import {Deferred} from './Deferred.js';
|
|
7
7
|
import {disposeSymbol} from './disposable.js';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
class MutexGuard {
|
|
13
|
+
#mutex: Mutex;
|
|
14
|
+
#onRelease?: () => void;
|
|
15
|
+
constructor(mutex: Mutex, onRelease?: () => void) {
|
|
16
|
+
this.#mutex = mutex;
|
|
17
|
+
this.#onRelease = onRelease;
|
|
18
|
+
}
|
|
19
|
+
[disposeSymbol](): void {
|
|
20
|
+
this.#onRelease?.();
|
|
21
|
+
return this.#mutex.release();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
9
25
|
/**
|
|
10
26
|
* @internal
|
|
11
27
|
*/
|
|
12
28
|
export class Mutex {
|
|
13
|
-
static Guard =
|
|
14
|
-
#mutex: Mutex;
|
|
15
|
-
#onRelease?: () => void;
|
|
16
|
-
constructor(mutex: Mutex, onRelease?: () => void) {
|
|
17
|
-
this.#mutex = mutex;
|
|
18
|
-
this.#onRelease = onRelease;
|
|
19
|
-
}
|
|
20
|
-
[disposeSymbol](): void {
|
|
21
|
-
this.#onRelease?.();
|
|
22
|
-
return this.#mutex.release();
|
|
23
|
-
}
|
|
24
|
-
};
|
|
29
|
+
static Guard = MutexGuard;
|
|
25
30
|
|
|
26
31
|
#locked = false;
|
|
27
32
|
#acquirers: Array<() => void> = [];
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Use of this source code is governed by a BSD-style license that can be
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
|
+
import * as Greendev from '../../../models/greendev/greendev.js';
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* To use links in markdown, add key here with the link and
|
|
7
9
|
* use the added key in markdown.
|
|
@@ -106,6 +108,11 @@ export const getMarkdownLink = (key: string): string => {
|
|
|
106
108
|
}
|
|
107
109
|
const link = markdownLinks.get(key);
|
|
108
110
|
if (!link) {
|
|
111
|
+
if (Greendev.Prototypes.instance().isEnabled('beyondStylingAntigravity') ||
|
|
112
|
+
Greendev.Prototypes.instance().isEnabled('beyondStylingGemini')) {
|
|
113
|
+
return key;
|
|
114
|
+
}
|
|
115
|
+
|
|
109
116
|
throw new Error(`Markdown link with key '${key}' is not available, please check MarkdownLinksMap.ts`);
|
|
110
117
|
}
|
|
111
118
|
return link;
|
|
@@ -130,7 +130,8 @@ export class InspectorDrawerView {
|
|
|
130
130
|
this.#drawerMinimizedSetting =
|
|
131
131
|
Common.Settings.Settings.instance().createLocalSetting('inspector.drawer-minimized', false);
|
|
132
132
|
this.tabbedLocation = ViewManager.instance().createTabbedLocation(
|
|
133
|
-
options.revealDrawer, 'drawer-view', true, true,
|
|
133
|
+
options.revealDrawer, 'drawer-view', true, true,
|
|
134
|
+
{isLocationVisible: options.isVisible, tabbedPaneFactory: () => new DrawerTabbedPane()});
|
|
134
135
|
this.#moreTabsButton = this.tabbedLocation.enableMoreTabsButton();
|
|
135
136
|
this.#moreTabsButton.setTitle(i18nString(UIStrings.moreTools));
|
|
136
137
|
this.tabbedPane = this.tabbedLocation.tabbedPane() as DrawerTabbedPane;
|
|
@@ -228,7 +228,7 @@ export class InspectorView extends VBox implements ViewLocationResolver {
|
|
|
228
228
|
this.tabbedLocation = ViewManager.instance().createTabbedLocation(
|
|
229
229
|
Host.InspectorFrontendHost.InspectorFrontendHostInstance.bringToFront.bind(
|
|
230
230
|
Host.InspectorFrontendHost.InspectorFrontendHostInstance),
|
|
231
|
-
'panel', true, true, Root.Runtime.Runtime.queryParam('panel'));
|
|
231
|
+
'panel', true, true, {defaultTab: Root.Runtime.Runtime.queryParam('panel')});
|
|
232
232
|
|
|
233
233
|
this.tabbedPane = this.tabbedLocation.tabbedPane();
|
|
234
234
|
this.tabbedPane.setMinimumSize(MIN_MAIN_PANEL_WIDTH, 0);
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// Copyright 2026 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
// Side-effect import: registers the `<devtools-menu-button>` custom element
|
|
6
|
+
// used by `PLUS_BUTTON_VIEW` below. The named imports are type-only.
|
|
7
|
+
import './ContextMenu.js';
|
|
8
|
+
|
|
9
|
+
import * as Host from '../../core/host/host.js';
|
|
10
|
+
import * as i18n from '../../core/i18n/i18n.js';
|
|
11
|
+
import type * as Platform from '../../core/platform/platform.js';
|
|
12
|
+
import {Directives, html, render} from '../lit/lit.js';
|
|
13
|
+
|
|
14
|
+
import type {ContextMenu, MenuButton} from './ContextMenu.js';
|
|
15
|
+
import type {View} from './View.js';
|
|
16
|
+
import {ViewLocationValues} from './ViewRegistration.js';
|
|
17
|
+
|
|
18
|
+
const UIStrings = {
|
|
19
|
+
/**
|
|
20
|
+
* @description Default tooltip / accessible name of the "plus" button shown
|
|
21
|
+
* after the visible tabs in a tab strip. Clicking it opens a menu listing
|
|
22
|
+
* tools that are not currently shown as a visible tab.
|
|
23
|
+
*/
|
|
24
|
+
moreTools: 'More tools',
|
|
25
|
+
} as const;
|
|
26
|
+
const str_ = i18n.i18n.registerUIStrings('ui/legacy/PlusButton.ts', UIStrings);
|
|
27
|
+
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
28
|
+
|
|
29
|
+
/** Declarative configuration for the plus button. */
|
|
30
|
+
export interface PlusButtonOptions {
|
|
31
|
+
title?: Platform.UIString.LocalizedString;
|
|
32
|
+
jslogContext?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Minimal `TabbedPane` surface read by the populator. Defined as an
|
|
37
|
+
* interface so test doubles can satisfy it without an `as unknown as
|
|
38
|
+
* TabbedPane` double-cast.
|
|
39
|
+
*/
|
|
40
|
+
export interface PlusButtonTabbedPane {
|
|
41
|
+
element: HTMLElement;
|
|
42
|
+
hiddenTabs(): ReadonlyArray<{id: string, title: string, jslogContext?: string}>;
|
|
43
|
+
hasTab(id: string): boolean;
|
|
44
|
+
firstHiddenTabIndex(): number;
|
|
45
|
+
moveTab(tabId: string, newIndex: number): void;
|
|
46
|
+
selectTab(tabId: string, userGesture?: boolean, forceFocus?: boolean): boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface PlusButtonMenuContext {
|
|
50
|
+
tabbedPane: PlusButtonTabbedPane;
|
|
51
|
+
location: string;
|
|
52
|
+
/**
|
|
53
|
+
* Production callers pass `() => location.views.values()` (NOT
|
|
54
|
+
* `manager.viewsForLocation(location)`) so views moved in via
|
|
55
|
+
* `appendView` are reflected immediately. Called fresh on every
|
|
56
|
+
* menu open.
|
|
57
|
+
*/
|
|
58
|
+
views: () => Iterable<View>;
|
|
59
|
+
manager: {
|
|
60
|
+
viewsForLocation(location: string): View[],
|
|
61
|
+
moveView(viewId: string, locationName: string): void,
|
|
62
|
+
};
|
|
63
|
+
showView: (view: View) => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface AddToolEntry {
|
|
67
|
+
title: string;
|
|
68
|
+
jslogContext: string;
|
|
69
|
+
isPreviewFeature: boolean;
|
|
70
|
+
action: () => void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface OverflowTabModel {
|
|
74
|
+
id: string;
|
|
75
|
+
title: string;
|
|
76
|
+
jslogContext?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface PlusButtonMenuModel {
|
|
80
|
+
overflowTabs: readonly OverflowTabModel[];
|
|
81
|
+
addToolEntries: readonly AddToolEntry[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Presenter (MVP) for the plus-button menu. {@link buildModel} is called
|
|
86
|
+
* fresh on every menu open so newly-registered views — or views that
|
|
87
|
+
* just left the visible tab strip — are reflected immediately.
|
|
88
|
+
*/
|
|
89
|
+
export class PlusButtonPresenter {
|
|
90
|
+
readonly #context: PlusButtonMenuContext;
|
|
91
|
+
|
|
92
|
+
constructor(context: PlusButtonMenuContext) {
|
|
93
|
+
this.#context = context;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
buildModel(): PlusButtonMenuModel {
|
|
97
|
+
const {tabbedPane, location, views, manager} = this.#context;
|
|
98
|
+
const overflowTabs: readonly OverflowTabModel[] =
|
|
99
|
+
tabbedPane.hiddenTabs().map(tab => ({id: tab.id, title: tab.title, jslogContext: tab.jslogContext}));
|
|
100
|
+
|
|
101
|
+
const addToolEntries: AddToolEntry[] = [];
|
|
102
|
+
// Seed dedup sets from the overflowed tabs so an addable entry
|
|
103
|
+
// (e.g. a closeable view in the other main location) sharing an id
|
|
104
|
+
// or title with an overflowed tab is not listed twice.
|
|
105
|
+
const seenIds = new Set<string>(overflowTabs.map(tab => tab.id));
|
|
106
|
+
const seenTitles = new Set<string>(overflowTabs.map(tab => tab.title));
|
|
107
|
+
|
|
108
|
+
for (const view of views()) {
|
|
109
|
+
// Skip views that already have a tab. Hidden tabs are already listed
|
|
110
|
+
// in the overflow section above, and visible tabs are accessible
|
|
111
|
+
// directly in the tab strip.
|
|
112
|
+
if (tabbedPane.hasTab(view.viewId())) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// Transient views are not user-addable.
|
|
116
|
+
if (view.isTransient()) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (seenIds.has(view.viewId()) || seenTitles.has(view.title())) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
seenIds.add(view.viewId());
|
|
123
|
+
seenTitles.add(view.title());
|
|
124
|
+
const isIssuesPane = view.viewId() === 'issues-pane';
|
|
125
|
+
addToolEntries.push({
|
|
126
|
+
title: view.title(),
|
|
127
|
+
jslogContext: view.viewId(),
|
|
128
|
+
isPreviewFeature: view.isPreviewFeature(),
|
|
129
|
+
action: () => {
|
|
130
|
+
if (isIssuesPane) {
|
|
131
|
+
// Distinct from `HAMBURGER_MENU` so plus-button opens are
|
|
132
|
+
// not conflated with three-dot-menu opens in the dashboard.
|
|
133
|
+
Host.userMetrics.issuesPanelOpenedFrom(Host.UserMetrics.IssueOpener.MORE_TOOLS_MENU);
|
|
134
|
+
}
|
|
135
|
+
this.#context.showView(view);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Offer cross-location moves between the two main surfaces: the
|
|
141
|
+
// panel plus button lists drawer views and vice versa.
|
|
142
|
+
const otherLocation = location === ViewLocationValues.PANEL ? ViewLocationValues.DRAWER_VIEW :
|
|
143
|
+
location === ViewLocationValues.DRAWER_VIEW ? ViewLocationValues.PANEL :
|
|
144
|
+
null;
|
|
145
|
+
if (otherLocation) {
|
|
146
|
+
for (const view of manager.viewsForLocation(otherLocation)) {
|
|
147
|
+
// Non-closeable views (e.g. Console) cannot be moved between
|
|
148
|
+
// locations, so they're excluded here. They still appear in the
|
|
149
|
+
// overflow section when their own location's tab strip overflows.
|
|
150
|
+
if (view.isTransient() || !view.isCloseable() || seenIds.has(view.viewId()) || seenTitles.has(view.title())) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
seenIds.add(view.viewId());
|
|
154
|
+
seenTitles.add(view.title());
|
|
155
|
+
const viewId = view.viewId();
|
|
156
|
+
addToolEntries.push({
|
|
157
|
+
title: view.title(),
|
|
158
|
+
jslogContext: viewId,
|
|
159
|
+
isPreviewFeature: view.isPreviewFeature(),
|
|
160
|
+
action: () => manager.moveView(viewId, location),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
addToolEntries.sort((a, b) => a.title.localeCompare(b.title));
|
|
166
|
+
|
|
167
|
+
return {overflowTabs, addToolEntries};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Renders the plus-button menu by asking {@link PlusButtonPresenter}
|
|
173
|
+
* for a model and pushing it into `contextMenu`. Overflowed tabs (in
|
|
174
|
+
* tab order) come first, followed by deduplicated "add tool" entries
|
|
175
|
+
* sorted alphabetically.
|
|
176
|
+
*/
|
|
177
|
+
export function populatePlusButtonMenu(contextMenu: ContextMenu, context: PlusButtonMenuContext): void {
|
|
178
|
+
const model = new PlusButtonPresenter(context).buildModel();
|
|
179
|
+
const hasOverflow = model.overflowTabs.length > 0;
|
|
180
|
+
|
|
181
|
+
// When there are no overflowed tabs, surface the add-tool entries in
|
|
182
|
+
// the default section so they are not visually demoted to a footer.
|
|
183
|
+
for (const tab of model.overflowTabs) {
|
|
184
|
+
contextMenu.defaultSection().appendItem(
|
|
185
|
+
tab.title, () => revealOverflowTab(context.tabbedPane, tab.id), {jslogContext: tab.jslogContext ?? tab.id});
|
|
186
|
+
}
|
|
187
|
+
const addToolSection = hasOverflow ? contextMenu.footerSection() : contextMenu.defaultSection();
|
|
188
|
+
for (const entry of model.addToolEntries) {
|
|
189
|
+
addToolSection.appendItem(
|
|
190
|
+
entry.title, entry.action, {isPreviewFeature: entry.isPreviewFeature, jslogContext: entry.jslogContext});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Reveals an overflowed tab and persists its new position via
|
|
196
|
+
* `moveTab(firstHidden - 1)` so the tab stays in the visible region
|
|
197
|
+
* after a reload — independent of any runtime `currentTab` /
|
|
198
|
+
* `lastSelectedOverflowTab` priority logic. The previously-last-visible
|
|
199
|
+
* tab is pushed to the start of the overflow region, matching the
|
|
200
|
+
* intuition that the newly opened tab replaces the one the user
|
|
201
|
+
* implicitly stopped using.
|
|
202
|
+
*
|
|
203
|
+
* Exported only for testing.
|
|
204
|
+
*/
|
|
205
|
+
export function revealOverflowTab(tabbedPane: PlusButtonTabbedPane, tabId: string): void {
|
|
206
|
+
const firstHidden = tabbedPane.firstHiddenTabIndex();
|
|
207
|
+
if (firstHidden > 0) {
|
|
208
|
+
// `firstHidden - 1` is the index of the last currently-visible tab.
|
|
209
|
+
tabbedPane.moveTab(tabId, firstHidden - 1);
|
|
210
|
+
}
|
|
211
|
+
tabbedPane.selectTab(tabId, /* userGesture */ true, /* forceFocus */ true);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
interface PlusButtonViewInput {
|
|
215
|
+
title: string;
|
|
216
|
+
jslogContext: string;
|
|
217
|
+
populateMenuCall: (menu: ContextMenu) => void;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Standard `(input, output, target)` view function so `Lit.render` is
|
|
222
|
+
* called inside a view (per `@devtools/no-lit-render-outside-of-view`).
|
|
223
|
+
* `output.button` is captured via `ref` to avoid a `querySelector`
|
|
224
|
+
* round-trip in {@link installPlusButton}.
|
|
225
|
+
*
|
|
226
|
+
* `slot` is set declaratively in the template so the attribute is
|
|
227
|
+
* present on the very first connection — the first `slotchange` then
|
|
228
|
+
* sees the button as the trailing-slot target and no extra layout pass
|
|
229
|
+
* is needed.
|
|
230
|
+
*/
|
|
231
|
+
export const PLUS_BUTTON_VIEW =
|
|
232
|
+
(input: PlusButtonViewInput, output: {button?: MenuButton}, target: HTMLElement): void => {
|
|
233
|
+
render(
|
|
234
|
+
html`
|
|
235
|
+
<devtools-menu-button
|
|
236
|
+
${Directives.ref(el => {
|
|
237
|
+
output.button = el as MenuButton | undefined;
|
|
238
|
+
})}
|
|
239
|
+
slot="trailing-button"
|
|
240
|
+
.iconName=${'plus'}
|
|
241
|
+
.title=${input.title}
|
|
242
|
+
.jslogContext=${input.jslogContext}
|
|
243
|
+
.populateMenuCall=${input.populateMenuCall}>
|
|
244
|
+
</devtools-menu-button>`,
|
|
245
|
+
target);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Renders a `<devtools-menu-button>` configured as the plus button into
|
|
250
|
+
* `tabbedPane`'s `trailing-button` slot and returns the slotted host.
|
|
251
|
+
* The returned `MenuButton` is used by the next CL to toggle visibility
|
|
252
|
+
* (e.g. when the drawer is minimized).
|
|
253
|
+
*/
|
|
254
|
+
export function installPlusButton(context: PlusButtonMenuContext, options: PlusButtonOptions = {}): MenuButton {
|
|
255
|
+
const output: {button?: MenuButton} = {};
|
|
256
|
+
// `render` is synchronous and the `ref` directive fires during render,
|
|
257
|
+
// so `output.button` is assigned by the time the view returns.
|
|
258
|
+
PLUS_BUTTON_VIEW(
|
|
259
|
+
{
|
|
260
|
+
title: options.title ?? i18nString(UIStrings.moreTools),
|
|
261
|
+
jslogContext: options.jslogContext ?? '',
|
|
262
|
+
populateMenuCall: menu => populatePlusButtonMenu(menu, context),
|
|
263
|
+
},
|
|
264
|
+
output, context.tabbedPane.element);
|
|
265
|
+
if (!output.button) {
|
|
266
|
+
throw new Error('installPlusButton: ref directive did not capture <devtools-menu-button>');
|
|
267
|
+
}
|
|
268
|
+
return output.button;
|
|
269
|
+
}
|