chrome-devtools-frontend 1.0.1706026 → 1.0.1708524
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/.agents/skills/code-review/SKILL.md +121 -0
- package/front_end/core/sdk/DOMModel.ts +13 -6
- package/front_end/core/sdk/NetworkRequest.ts +43 -7
- package/front_end/core/sdk/SourceMap.ts +4 -13
- package/front_end/core/sdk/SourceMapScopesInfo.ts +134 -86
- package/front_end/core/text_utils/ContentData.ts +18 -1
- package/front_end/entrypoints/heap_snapshot_worker/ContextAnalyzer.ts +102 -4
- package/front_end/generated/InspectorBackendCommands.ts +7 -1
- package/front_end/generated/SupportedCSSProperties.ts +11 -2
- package/front_end/generated/protocol-mapping.d.ts +31 -0
- package/front_end/generated/protocol-proxy-api.d.ts +32 -0
- package/front_end/generated/protocol.ts +11 -0
- package/front_end/models/ai_assistance/AiAgent2.ts +1 -0
- package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.ts +26 -3
- package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +15 -16
- package/front_end/models/ai_assistance/skills/Skill.ts +1 -2
- package/front_end/models/ai_assistance/skills/SkillRegistry.ts +2 -0
- package/front_end/models/ai_assistance/skills/lighthouse.md +33 -0
- package/front_end/models/ai_assistance/tools/GetLighthouseAudits.ts +6 -6
- package/front_end/models/ai_assistance/tools/RunLighthouse.ts +8 -5
- package/front_end/models/bindings/CompilerScriptMapping.ts +102 -45
- package/front_end/models/bindings/DebuggerLanguagePlugins.ts +25 -33
- package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +33 -39
- package/front_end/models/bindings/StylesSourceMapping.ts +3 -0
- package/front_end/models/extensions/ExtensionEndpoint.ts +27 -6
- package/front_end/models/extensions/LanguageExtensionEndpoint.ts +106 -21
- package/front_end/models/extensions/RecorderExtensionEndpoint.ts +4 -2
- package/front_end/models/javascript_metadata/NativeFunctions.ts +6 -2
- package/front_end/models/persistence/NetworkPersistenceManager.ts +4 -3
- package/front_end/models/source_map_scopes/NamesResolver.ts +0 -8
- package/front_end/models/stack_trace/StackTraceImpl.ts +138 -47
- package/front_end/models/stack_trace/StackTraceModel.ts +117 -84
- package/front_end/models/stack_trace/Trie.ts +27 -0
- package/front_end/panels/accessibility/ARIAAttributesView.ts +6 -6
- package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +2 -2
- package/front_end/panels/accessibility/AccessibilityNodeView.ts +6 -9
- package/front_end/panels/accessibility/AccessibilitySidebarView.ts +83 -69
- package/front_end/panels/accessibility/AccessibilitySubPane.ts +24 -9
- package/front_end/panels/accessibility/SourceOrderView.ts +7 -3
- package/front_end/panels/accessibility/accessibilitySidebarView.css +5 -3
- package/front_end/panels/application/serviceWorkersView.css +1 -1
- package/front_end/panels/common/CommentThreadWidget.ts +30 -1
- package/front_end/panels/common/CommentsOverlayWidget.ts +42 -38
- package/front_end/panels/common/common-meta.ts +21 -9
- package/front_end/panels/console/ConsoleInsightTeaser.ts +25 -19
- package/front_end/panels/console/ConsoleViewMessage.ts +51 -21
- package/front_end/panels/console/console-meta.ts +1 -5
- package/front_end/panels/elements/DOMTreeWidget.ts +207 -586
- package/front_end/panels/elements/ElementsPanel.ts +1 -5
- package/front_end/panels/elements/StylePropertyTreeElement.ts +100 -5
- package/front_end/panels/lighthouse/LighthouseController.ts +9 -4
- package/front_end/panels/network/NetworkConfigView.ts +231 -187
- package/front_end/panels/network/RequestPayloadView.ts +22 -14
- package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +0 -1
- package/front_end/panels/recorder/RecordingView.ts +42 -47
- package/front_end/panels/recorder/StepEditor.ts +1 -0
- package/front_end/panels/recorder/recordingView.css +4 -4
- package/front_end/panels/sources/CSSPlugin.ts +11 -0
- package/front_end/panels/sources/CoveragePlugin.ts +34 -23
- package/front_end/panels/sources/NavigatorView.ts +97 -52
- package/front_end/panels/sources/Plugin.ts +15 -1
- package/front_end/panels/sources/SourcesView.ts +47 -80
- package/front_end/panels/sources/TabbedEditorContainer.ts +27 -2
- package/front_end/panels/sources/UISourceCodeFrame.ts +11 -2
- package/front_end/panels/sources/navigatorTree.css +2 -2
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/comments/CommentAnchorResolver.ts +17 -0
- package/front_end/ui/components/suggestion_input/SuggestionInput.ts +10 -1
- package/front_end/ui/components/text_editor/AiCodeCompletionTeaser.ts +1 -1
- package/front_end/ui/components/text_editor/AiCodeGenerationTeaser.ts +2 -2
- package/front_end/ui/components/text_editor/aiCodeCompletionTeaser.css +0 -11
- package/front_end/ui/components/text_editor/aiCodeGenerationTeaser.css +0 -11
- package/front_end/ui/legacy/SoftDropDown.ts +17 -4
- package/front_end/ui/legacy/StackPane.ts +141 -0
- package/front_end/ui/legacy/TextPrompt.ts +37 -7
- package/front_end/ui/legacy/Toolbar.ts +7 -5
- package/front_end/ui/legacy/Widget.ts +3 -3
- package/front_end/ui/legacy/components/inline_editor/PositionAreaEditor.ts +85 -0
- package/front_end/ui/legacy/components/inline_editor/positionAreaEditor.css +61 -0
- package/front_end/ui/legacy/components/utils/Linkifier.ts +21 -45
- package/front_end/ui/legacy/inspectorCommon.css +11 -0
- package/front_end/ui/legacy/legacy.ts +2 -0
- package/front_end/ui/legacy/softContextMenu.css +2 -0
- package/front_end/ui/settings/ConsoleSettings.ts +7 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-review
|
|
3
|
+
description: Generic checklist for reviewing DevTools CLs (your own before upload, or someone else's on Gerrit). Covers test correctness (tautological/vacuous tests, cleanup, leaks), code clarity, CL hygiene, and points to the specialized skills to consult for imports, UI, testing, strings, models, and verification. Use when asked to review a CL, a diff, a patch, or to self-review before `git cl upload`.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Code Review
|
|
7
|
+
|
|
8
|
+
This checklist comes from recurring reviewer feedback on landed DevTools CLs.
|
|
9
|
+
Use it for self-review before upload and for reviewing other people's CLs.
|
|
10
|
+
|
|
11
|
+
## 1. Gather context
|
|
12
|
+
|
|
13
|
+
- **Local change:** `git diff origin/main...HEAD` (or `git show HEAD` for a
|
|
14
|
+
single-commit branch). To learn about the branch and upload workflow, see the
|
|
15
|
+
`devtools-version-control` skill.
|
|
16
|
+
- **Gerrit CL:** strip the leading `)]}'` line from each response to get JSON:
|
|
17
|
+
- Diff: `git cl diff` (for the current branch), or fetch the patch set.
|
|
18
|
+
- Inline comments:
|
|
19
|
+
`curl -s https://chromium-review.googlesource.com/changes/devtools%2Fdevtools-frontend~<CL_NUMBER>/comments | tail -n +2`
|
|
20
|
+
- Messages:
|
|
21
|
+
`.../changes/devtools%2Fdevtools-frontend~<CL_NUMBER>/messages`
|
|
22
|
+
- Read the CL description first. Then check that the diff does what the
|
|
23
|
+
description says, and nothing else.
|
|
24
|
+
|
|
25
|
+
## 2. Consult the specialized skills
|
|
26
|
+
|
|
27
|
+
Load the skill that matches what the diff touches, and apply its rules:
|
|
28
|
+
|
|
29
|
+
| If the diff touches… | Use skill |
|
|
30
|
+
| :--- | :--- |
|
|
31
|
+
| Any `import` statement, or a new cross-module dependency | `devtools-imports` |
|
|
32
|
+
| `UI.Widget`, lit-html views, components, CSS | `devtools-ui-widgets` |
|
|
33
|
+
| Migration of legacy imperative DOM to widgets or Lit | `ui-eng-vision-orchestrator` (and its sub-skills) |
|
|
34
|
+
| New or changed tests: choosing unit, API, or E2E | `devtools-testing-guidance` |
|
|
35
|
+
| Tests that use `describeWithEnvironment` or `describeWithMockConnection`, or foundation modules | `foundation-test-migration` |
|
|
36
|
+
| Ported Chromium web tests (`web_tests/http/tests/devtools`) | `migrate-chromium-test` |
|
|
37
|
+
| Flaky or disabled tests, or `it.skip` / `it.skipOnPlatforms` | `fix-tests` |
|
|
38
|
+
| `UIStrings` / user-facing text | `devtools-ux-writing-refactor` |
|
|
39
|
+
| Rendering user-controlled strings (URLs, names, console text) | `devtools-unicode-escaping` |
|
|
40
|
+
| `front_end/models/*`, `BUILD.gn`, `devtools_grd_files.gni`, entrypoints | `devtools-model-management` |
|
|
41
|
+
| Merging modules or consolidating `BUILD.gn` | `merging-devtools-module` |
|
|
42
|
+
| `Settings` registrations and descriptors | `devtools-setting-migration` |
|
|
43
|
+
| Stack traces, source maps, `DebuggerWorkspaceBinding` | `devtools-source-maps` |
|
|
44
|
+
| Building, running tests, or lint | `devtools-verification` |
|
|
45
|
+
|
|
46
|
+
## 3. Test correctness (the most common review feedback)
|
|
47
|
+
|
|
48
|
+
- **Tautological tests:** A test must exercise real production code. If a
|
|
49
|
+
mock does the thing the test asserts (for example, a mocked `DOM.undo` that
|
|
50
|
+
calls `removeSection()` directly, followed by an assertion that the section
|
|
51
|
+
was removed), the test checks the mock and nothing else. Mock the *boundary*
|
|
52
|
+
instead (for example, have the CDP stub emit `CSSModel.Events.StyleSheetChanged`)
|
|
53
|
+
and let production code react to it.
|
|
54
|
+
- **Vacuous tests:** Look for local promises or data that are never connected
|
|
55
|
+
to DevTools logic, or stubs (like `initialize`) that skip the code path under
|
|
56
|
+
test. These tests pass because nothing happens.
|
|
57
|
+
- **Assertions that check the setup:** For example,
|
|
58
|
+
`assert.isNull(pane.node())` right after `pane.setNodeForTest(null)`. Each
|
|
59
|
+
assertion should verify the behavior that the test name describes.
|
|
60
|
+
- **Test lives where it belongs:** A test in `Foo.test.ts` should exercise
|
|
61
|
+
`Foo`. Move logic-only tests to the model's test file (for example,
|
|
62
|
+
`CSSMatchedStyles.test.ts`). Otherwise, render the UI and assert on the DOM.
|
|
63
|
+
- **Test covers the scenario it claims:** For example, an "iframe event
|
|
64
|
+
listeners" test must select a node *inside* the iframe. It is not enough
|
|
65
|
+
that it passes on `<body>` of the main frame.
|
|
66
|
+
- **Right suite:** Ask whether an E2E test could be a unit test, or whether the
|
|
67
|
+
E2E coverage is intentional for a user story. See `devtools-testing-guidance`.
|
|
68
|
+
- **E2E selectors match real UI:** Labels and `aria-label`s must match the
|
|
69
|
+
actual strings, for example "Show user agent shadow DOM" and not "User agent
|
|
70
|
+
shadow DOM". Check that the setting being toggled actually affects the
|
|
71
|
+
scenario.
|
|
72
|
+
- **Fail fast:** Throw or assert when a required value is missing. Don't let
|
|
73
|
+
the test time out later.
|
|
74
|
+
|
|
75
|
+
## 4. Test hygiene and cleanup
|
|
76
|
+
|
|
77
|
+
- Pair every `beforeEach` singleton creation with a cleanup in `afterEach`
|
|
78
|
+
(for example, `CSSWorkspaceBinding.removeInstance()` or
|
|
79
|
+
`WorkspaceImpl.removeInstance()`) so that state does not leak between tests.
|
|
80
|
+
- Restore fake timers in `try { … } finally { clock.restore(); }`, or use the
|
|
81
|
+
sandbox or cleanup that the test runner provides. Keep the scope of fake
|
|
82
|
+
timers small.
|
|
83
|
+
- Don't stub the same method twice without calling `restore()` first. Sinon
|
|
84
|
+
throws `Attempted to wrap … which is already wrapped` when the object is the
|
|
85
|
+
same instance.
|
|
86
|
+
- Remove guards that were copied from helpers for no reason (for example,
|
|
87
|
+
`if (!stub.called) …` in a test that stubs the method only once).
|
|
88
|
+
|
|
89
|
+
## 5. Code clarity
|
|
90
|
+
|
|
91
|
+
- Don't take detours to reach a value. If a module is imported, call
|
|
92
|
+
`TextUtils.TextRange.TextRange.fromObject(...)` directly instead of going
|
|
93
|
+
through `rule.style.range.constructor`.
|
|
94
|
+
- Look for dead code, leftover debug code, commented-out blocks, and unrelated
|
|
95
|
+
formatting churn.
|
|
96
|
+
- Check that the change stays in scope. Flag unrelated refactors that should be
|
|
97
|
+
split into a separate CL.
|
|
98
|
+
- Check for lifecycle issues: event listeners and observers that are added
|
|
99
|
+
without being removed, and async work that continues after a widget is
|
|
100
|
+
detached.
|
|
101
|
+
|
|
102
|
+
## 6. CL hygiene
|
|
103
|
+
|
|
104
|
+
- The description explains *why*, including external dependencies (for
|
|
105
|
+
example, "V8 will stop emitting X; this must land first").
|
|
106
|
+
- Include a `Bug:` or `Fixed:` trailer, or `Bug: None`. Keep lines under 72
|
|
107
|
+
characters.
|
|
108
|
+
- For migrations and stacked series, list the legacy tests or files covered,
|
|
109
|
+
and number the series (for example, `(13/16)`).
|
|
110
|
+
- Disabled tests reference a bug.
|
|
111
|
+
- Presubmit, lint, and format pass. See `devtools-verification`.
|
|
112
|
+
|
|
113
|
+
## 7. Writing review comments
|
|
114
|
+
|
|
115
|
+
- Be specific: cite file and line, explain the concrete failure mode (such as
|
|
116
|
+
a leak, a hang, or "passes vacuously"), and suggest a fix. Include a code
|
|
117
|
+
snippet when it helps.
|
|
118
|
+
- Separate blocking issues from nits and questions (for example, "Should we
|
|
119
|
+
move this to a unit test?").
|
|
120
|
+
- When a reviewer's concern is not an actual bug, reply with the rationale.
|
|
121
|
+
Don't make a silent change.
|
|
@@ -1139,7 +1139,7 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
|
|
|
1139
1139
|
}
|
|
1140
1140
|
|
|
1141
1141
|
isCustomElement(): boolean {
|
|
1142
|
-
if (this.nodeType() !== NodeType.ELEMENT_NODE || this.isXMLNode()) {
|
|
1142
|
+
if (this.nodeType() !== NodeType.ELEMENT_NODE || this.isXMLNode() || Boolean(this.pseudoType())) {
|
|
1143
1143
|
return false;
|
|
1144
1144
|
}
|
|
1145
1145
|
const localName = this.localName() || this.nodeName().toLowerCase();
|
|
@@ -1600,11 +1600,18 @@ export class DOMDocument extends DOMNode {
|
|
|
1600
1600
|
this.#frameId = frameId ?? null;
|
|
1601
1601
|
|
|
1602
1602
|
const resourceTreeModel = this.domModel().target().model(ResourceTreeModel);
|
|
1603
|
-
const frame = this.#frameId ? resourceTreeModel?.frameForId(this.#frameId) :
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1603
|
+
const frame = this.#frameId ? resourceTreeModel?.frameForId(this.#frameId) : null;
|
|
1604
|
+
if (frame) {
|
|
1605
|
+
this.#securityOrigin = frame.securityOrigin();
|
|
1606
|
+
} else if (resourceTreeModel?.mainFrame) {
|
|
1607
|
+
// If the target has an active frame tree, a DOMDocument without a matching
|
|
1608
|
+
// frame is a detached document and must be isolated with an opaque origin.
|
|
1609
|
+
this.#securityOrigin = SecurityOrigin.createUniqueOpaque();
|
|
1610
|
+
} else {
|
|
1611
|
+
// TODO(b/567434846): Migrate synthetic unit tests to attach frames so this
|
|
1612
|
+
// fallback to parsing documentURL can be removed.
|
|
1613
|
+
this.#securityOrigin = SecurityOrigin.create(this.#documentURL);
|
|
1614
|
+
}
|
|
1608
1615
|
}
|
|
1609
1616
|
|
|
1610
1617
|
get documentURL(): Platform.DevToolsPath.UrlString {
|
|
@@ -179,6 +179,7 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
|
|
|
179
179
|
#blockedReason: Protocol.Network.BlockedReason|undefined = undefined;
|
|
180
180
|
#renderBlockingBehavior?: Protocol.Network.RenderBlockingBehavior;
|
|
181
181
|
#initiatorSecurityOrigin?: SecurityOrigin;
|
|
182
|
+
#requestURLSecurityOrigin?: SecurityOrigin;
|
|
182
183
|
#corsErrorStatus: Protocol.Network.CorsErrorStatus|undefined = undefined;
|
|
183
184
|
statusCode = 0;
|
|
184
185
|
statusText = '';
|
|
@@ -193,6 +194,7 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
|
|
|
193
194
|
#resourceType: Common.ResourceType.ResourceType = Common.ResourceType.resourceTypes.Other;
|
|
194
195
|
#contentData: Promise<TextUtils.ContentData.ContentDataOrError>|null = null;
|
|
195
196
|
#streamingContentData: Promise<TextUtils.StreamingContentData.StreamingContentDataOrError>|null = null;
|
|
197
|
+
#resolvedStreamingContentData: TextUtils.StreamingContentData.StreamingContentData|null = null;
|
|
196
198
|
readonly #frames: WebSocketFrame[] = [];
|
|
197
199
|
#responseHeaderValues: Record<string, string|undefined> = {};
|
|
198
200
|
#responseHeadersText = '';
|
|
@@ -402,10 +404,17 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
|
|
|
402
404
|
* (`imported-har://${authority}`) to ensure recorded network traffic never collides with
|
|
403
405
|
* live web origins.
|
|
404
406
|
*
|
|
407
|
+
* The result is cached, so repeated calls return the same instance until the URL or the
|
|
408
|
+
* imported HAR flag changes. This keeps opaque origins (such as `data:` URLs) same-origin
|
|
409
|
+
* with themselves.
|
|
410
|
+
*
|
|
405
411
|
* @see {@link initiatorSecurityOrigin} to obtain the origin of the document that initiated the request.
|
|
406
412
|
*/
|
|
407
413
|
requestURLSecurityOrigin(): SecurityOrigin {
|
|
408
|
-
|
|
414
|
+
if (!this.#requestURLSecurityOrigin) {
|
|
415
|
+
this.#requestURLSecurityOrigin = this.#resolveSecurityOrigin(this.#url);
|
|
416
|
+
}
|
|
417
|
+
return this.#requestURLSecurityOrigin;
|
|
409
418
|
}
|
|
410
419
|
|
|
411
420
|
/**
|
|
@@ -422,6 +431,9 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
|
|
|
422
431
|
* For imported HAR files, the origin is mapped to an isolated virtual domain
|
|
423
432
|
* (`imported-har://${authority}`) matching the imported initiating document.
|
|
424
433
|
*
|
|
434
|
+
* The result is cached, so repeated calls return the same instance until the imported HAR
|
|
435
|
+
* flag changes. This keeps opaque origins same-origin with themselves.
|
|
436
|
+
*
|
|
425
437
|
* @see {@link requestURLSecurityOrigin} to obtain the origin of the target resource URL being requested.
|
|
426
438
|
*/
|
|
427
439
|
initiatorSecurityOrigin(): SecurityOrigin {
|
|
@@ -462,6 +474,7 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
|
|
|
462
474
|
this.#parsedQueryParameters = undefined;
|
|
463
475
|
this.#name = undefined;
|
|
464
476
|
this.#path = undefined;
|
|
477
|
+
this.#requestURLSecurityOrigin = undefined;
|
|
465
478
|
}
|
|
466
479
|
|
|
467
480
|
get documentURL(): Platform.DevToolsPath.UrlString {
|
|
@@ -1207,7 +1220,12 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
|
|
|
1207
1220
|
}
|
|
1208
1221
|
|
|
1209
1222
|
setIsImportedHar(isImportedHar: boolean): void {
|
|
1223
|
+
if (this.#isImportedHar === isImportedHar) {
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1210
1226
|
this.#isImportedHar = isImportedHar;
|
|
1227
|
+
this.#requestURLSecurityOrigin = undefined;
|
|
1228
|
+
this.#initiatorSecurityOrigin = undefined;
|
|
1211
1229
|
}
|
|
1212
1230
|
|
|
1213
1231
|
setEarlyHintsHeaders(headers: NameValue[]): void {
|
|
@@ -1488,9 +1506,11 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
|
|
|
1488
1506
|
}
|
|
1489
1507
|
// Note that this is save: "streamResponseBody()" always creates base64-based ContentData and
|
|
1490
1508
|
// for "contentData()" we'll never call "addChunk".
|
|
1491
|
-
|
|
1509
|
+
const streamingContentData = TextUtils.StreamingContentData.StreamingContentData.from(
|
|
1492
1510
|
contentData,
|
|
1493
1511
|
);
|
|
1512
|
+
this.#resolvedStreamingContentData = streamingContentData;
|
|
1513
|
+
return streamingContentData;
|
|
1494
1514
|
});
|
|
1495
1515
|
|
|
1496
1516
|
return this.#streamingContentData;
|
|
@@ -1510,6 +1530,18 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
|
|
|
1510
1530
|
isRegex: boolean,
|
|
1511
1531
|
): Promise<TextUtils.ContentProvider.SearchMatch[]> {
|
|
1512
1532
|
if (!this.#contentDataProvider) {
|
|
1533
|
+
const cachedContentData = this.finished && !this.failed ?
|
|
1534
|
+
(await this.#contentData ?? this.#resolvedStreamingContentData?.content()) :
|
|
1535
|
+
undefined;
|
|
1536
|
+
if (cachedContentData && !TextUtils.ContentData.ContentData.isError(cachedContentData) &&
|
|
1537
|
+
cachedContentData.isTextContent) {
|
|
1538
|
+
return TextUtils.TextUtils.performSearchInContentData(
|
|
1539
|
+
cachedContentData,
|
|
1540
|
+
query,
|
|
1541
|
+
caseSensitive,
|
|
1542
|
+
isRegex,
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1513
1545
|
return await NetworkManager.searchInRequest(
|
|
1514
1546
|
this,
|
|
1515
1547
|
query,
|
|
@@ -1940,11 +1972,15 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
|
|
|
1940
1972
|
}
|
|
1941
1973
|
this.endTime = timestamp;
|
|
1942
1974
|
if (data) {
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1975
|
+
if (this.#resolvedStreamingContentData) {
|
|
1976
|
+
this.#resolvedStreamingContentData.addChunk(data);
|
|
1977
|
+
} else {
|
|
1978
|
+
void this.#streamingContentData?.then(contentData => {
|
|
1979
|
+
if (!TextUtils.StreamingContentData.isError(contentData)) {
|
|
1980
|
+
contentData.addChunk(data);
|
|
1981
|
+
}
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1948
1984
|
}
|
|
1949
1985
|
}
|
|
1950
1986
|
|
|
@@ -12,7 +12,7 @@ import {scopeTreeForScript} from './ScopeTreeCache.js';
|
|
|
12
12
|
import type {Script} from './Script.js';
|
|
13
13
|
import {buildOriginalScopes, decodePastaRanges, type NamedFunctionRange} from './SourceMapFunctionRanges.js';
|
|
14
14
|
import {decodeRangeMappings} from './SourceMapRangeMappings.js';
|
|
15
|
-
import {scriptRelativePosition, SourceMapScopesInfo
|
|
15
|
+
import {type RawFrameTranslation, scriptRelativePosition, SourceMapScopesInfo} from './SourceMapScopesInfo.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Type of the base source map JSON object, which contains the sources and the mappings at the very least, plus
|
|
@@ -867,19 +867,10 @@ export class SourceMap {
|
|
|
867
867
|
return this.#scopesInfo?.findOriginalFunctionScope(position) ?? null;
|
|
868
868
|
}
|
|
869
869
|
|
|
870
|
-
|
|
870
|
+
/** See {@link SourceMapScopesInfo.translateRawFrame}. `null` if no scopes information is available. */
|
|
871
|
+
translateRawFrame(generatedLine: number, generatedColumn: number): RawFrameTranslation|null {
|
|
871
872
|
this.#ensureSourceMapProcessed();
|
|
872
|
-
return this.#scopesInfo?.
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
hasInlinedFrames(generatedLine: number, generatedColumn: number): boolean {
|
|
876
|
-
this.#ensureSourceMapProcessed();
|
|
877
|
-
return this.#scopesInfo?.hasInlinedFrames(generatedLine, generatedColumn) ?? false;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
translateCallSite(generatedLine: number, generatedColumn: number): TranslatedFrame[] {
|
|
881
|
-
this.#ensureSourceMapProcessed();
|
|
882
|
-
return this.#scopesInfo?.translateCallSite(generatedLine, generatedColumn) ?? [];
|
|
873
|
+
return this.#scopesInfo?.translateRawFrame(generatedLine, generatedColumn) ?? null;
|
|
883
874
|
}
|
|
884
875
|
}
|
|
885
876
|
|
|
@@ -16,13 +16,16 @@ export class SourceMapScopesInfo {
|
|
|
16
16
|
readonly #sourceMap: SourceMap;
|
|
17
17
|
readonly #originalScopes: Array<ScopesCodec.OriginalScope[]|null>;
|
|
18
18
|
readonly #generatedRanges: ScopesCodec.GeneratedRange[];
|
|
19
|
+
/** Whether the scope information was derived from the AST and mappings (see {@link createFromAst}). */
|
|
20
|
+
readonly #isFromAst: boolean;
|
|
19
21
|
|
|
20
22
|
#cachedVariablesAndBindingsPresent: boolean|null = null;
|
|
21
23
|
|
|
22
|
-
constructor(sourceMap: SourceMap, scopeInfo: ScopesCodec.ScopeInfo) {
|
|
24
|
+
constructor(sourceMap: SourceMap, scopeInfo: ScopesCodec.ScopeInfo, {isFromAst = false}: {isFromAst?: boolean} = {}) {
|
|
23
25
|
this.#sourceMap = sourceMap;
|
|
24
26
|
this.#originalScopes = scopeInfo.scopes;
|
|
25
27
|
this.#generatedRanges = scopeInfo.ranges;
|
|
28
|
+
this.#isFromAst = isFromAst;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
/**
|
|
@@ -114,7 +117,8 @@ export class SourceMapScopesInfo {
|
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
return new SourceMapScopesInfo(sourceMap, {scopes: scopesBySourceUrl, ranges: rootRange ? [rootRange] : []}
|
|
120
|
+
return new SourceMapScopesInfo(sourceMap, {scopes: scopesBySourceUrl, ranges: rootRange ? [rootRange] : []},
|
|
121
|
+
{isFromAst: true});
|
|
118
122
|
|
|
119
123
|
/**
|
|
120
124
|
* Finds the correct place in the tree to insert the new scope.
|
|
@@ -219,39 +223,17 @@ export class SourceMapScopesInfo {
|
|
|
219
223
|
}
|
|
220
224
|
}
|
|
221
225
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return this.#isOutlinedFrame(rangeChain);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
#isOutlinedFrame(rangeChain: ScopesCodec.GeneratedRange[]): boolean {
|
|
231
|
-
for (let i = rangeChain.length - 1; i >= 0; --i) {
|
|
232
|
-
if (rangeChain[i].isStackFrame) {
|
|
233
|
-
return rangeChain[i].isHidden;
|
|
234
|
-
}
|
|
226
|
+
#generatedFrameKind(rangeChain: ScopesCodec.GeneratedRange[]): GeneratedFrameKind {
|
|
227
|
+
const functionRange = rangeChain.findLast(range => range.isStackFrame);
|
|
228
|
+
if (!functionRange) {
|
|
229
|
+
// Top-level code.
|
|
230
|
+
return GeneratedFrameKind.VISIBLE;
|
|
235
231
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* @returns true, iff the range surrounding the provided position contains multiple
|
|
241
|
-
* inlined original functions.
|
|
242
|
-
*/
|
|
243
|
-
hasInlinedFrames(generatedLine: number, generatedColumn: number): boolean {
|
|
244
|
-
const rangeChain = this.#findGeneratedRangeChain(generatedLine, generatedColumn);
|
|
245
|
-
for (let i = rangeChain.length - 1; i >= 0; --i) {
|
|
246
|
-
if (rangeChain[i].isStackFrame) {
|
|
247
|
-
// We stop looking for inlined original functions once we reach the current frame.
|
|
248
|
-
return false;
|
|
249
|
-
}
|
|
250
|
-
if (rangeChain[i].callSite) {
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
232
|
+
if (!functionRange.originalScope) {
|
|
233
|
+
// For scope information derived from the AST, we merely failed to map the function.
|
|
234
|
+
return this.#isFromAst ? GeneratedFrameKind.VISIBLE : GeneratedFrameKind.HIDDEN;
|
|
253
235
|
}
|
|
254
|
-
return
|
|
236
|
+
return functionRange.isHidden ? GeneratedFrameKind.OUTLINED : GeneratedFrameKind.VISIBLE;
|
|
255
237
|
}
|
|
256
238
|
|
|
257
239
|
/**
|
|
@@ -419,54 +401,53 @@ export class SourceMapScopesInfo {
|
|
|
419
401
|
/**
|
|
420
402
|
* Returns the authored function scope of the function containing the provided generated position.
|
|
421
403
|
*/
|
|
422
|
-
findOriginalFunctionScope(
|
|
404
|
+
findOriginalFunctionScope(position: ScopesCodec.Position):
|
|
423
405
|
{scope: ScopesCodec.OriginalScope, url?: Platform.DevToolsPath.UrlString}|null {
|
|
406
|
+
const rangeChain = this.#findGeneratedRangeChain(position.line, position.column);
|
|
407
|
+
const functionScope =
|
|
408
|
+
this.#findFunctionScopeInOriginalScopeChain(this.#innerMostOriginalScope(rangeChain, position));
|
|
409
|
+
return functionScope ? {scope: functionScope, url: this.#sourceURLOfScope(functionScope)} : null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Returns the inner-most original scope containing the generated `position`. `rangeChain` must be the generated
|
|
414
|
+
* range chain of `position`.
|
|
415
|
+
*/
|
|
416
|
+
#innerMostOriginalScope(rangeChain: ScopesCodec.GeneratedRange[],
|
|
417
|
+
position: ScopesCodec.Position): ScopesCodec.OriginalScope|undefined {
|
|
424
418
|
// There are 2 approaches:
|
|
425
419
|
// 1) Find the inner-most generated range containing the provided generated position
|
|
426
|
-
// and use
|
|
420
|
+
// and use its OriginalScope.
|
|
427
421
|
// 2) Use the mappings to turn the generated position into an original position.
|
|
428
422
|
// Then find the inner-most original scope containing that original position.
|
|
429
|
-
// Then walk it outwards until we hit a function.
|
|
430
423
|
//
|
|
431
424
|
// Both approaches should yield the same result (assuming the mappings are spec compliant
|
|
432
425
|
// w.r.t. generated ranges). But in the case of "pasta" scopes and extension provided
|
|
433
426
|
// scope info, we only have the OriginalScope parts and mappings without GeneratedRanges.
|
|
434
|
-
|
|
435
|
-
let originalInnerMostScope: ScopesCodec.OriginalScope|undefined;
|
|
436
|
-
|
|
437
427
|
if (this.#generatedRanges.length > 0) {
|
|
438
|
-
|
|
439
|
-
originalInnerMostScope = rangeChain.at(-1)?.originalScope;
|
|
440
|
-
} else {
|
|
441
|
-
// No GeneratedRanges. Try to use mappings.
|
|
442
|
-
const entry = this.#sourceMap.findEntry(line, column);
|
|
443
|
-
if (entry?.sourceIndex === undefined) {
|
|
444
|
-
return null;
|
|
445
|
-
}
|
|
446
|
-
originalInnerMostScope =
|
|
447
|
-
this.#findOriginalScopeChain(
|
|
448
|
-
{sourceIndex: entry.sourceIndex, line: entry.sourceLineNumber, column: entry.sourceColumnNumber})
|
|
449
|
-
.at(-1);
|
|
428
|
+
return rangeChain.at(-1)?.originalScope;
|
|
450
429
|
}
|
|
451
430
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const functionScope = this.#findFunctionScopeInOriginalScopeChain(originalInnerMostScope);
|
|
457
|
-
if (!functionScope) {
|
|
458
|
-
return null;
|
|
431
|
+
// No GeneratedRanges. Try to use mappings.
|
|
432
|
+
const entry = this.#sourceMap.findEntry(position.line, position.column);
|
|
433
|
+
if (entry?.sourceIndex === undefined) {
|
|
434
|
+
return undefined;
|
|
459
435
|
}
|
|
436
|
+
return this
|
|
437
|
+
.#findOriginalScopeChain(
|
|
438
|
+
{sourceIndex: entry.sourceIndex, line: entry.sourceLineNumber, column: entry.sourceColumnNumber})
|
|
439
|
+
.at(-1);
|
|
440
|
+
}
|
|
460
441
|
|
|
442
|
+
/** @returns the URL of the original source that `scope` belongs to. */
|
|
443
|
+
#sourceURLOfScope(scope: ScopesCodec.OriginalScope): Platform.DevToolsPath.UrlString|undefined {
|
|
461
444
|
// Find the root scope for some given original source, to get the source url.
|
|
462
|
-
let rootScope: ScopesCodec.OriginalScope =
|
|
445
|
+
let rootScope: ScopesCodec.OriginalScope = scope;
|
|
463
446
|
while (rootScope.parent) {
|
|
464
447
|
rootScope = rootScope.parent;
|
|
465
448
|
}
|
|
466
449
|
const sourceIndex = this.#originalScopes.findIndex(scopes => scopes?.includes(rootScope));
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
return functionScope ? {scope: functionScope, url} : null;
|
|
450
|
+
return sourceIndex !== -1 ? this.#sourceMap.sourceURLForSourceIndex(sourceIndex) : undefined;
|
|
470
451
|
}
|
|
471
452
|
|
|
472
453
|
/**
|
|
@@ -513,33 +494,54 @@ export class SourceMapScopesInfo {
|
|
|
513
494
|
}
|
|
514
495
|
|
|
515
496
|
/**
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
* @returns An empty array if no mapping at the call-site was found, or the resulting frames
|
|
519
|
-
* in top-to-bottom order in case of inlining.
|
|
520
|
-
* @throws If this range is marked "hidden". Outlining needs to be handled externally as
|
|
521
|
-
* outlined function segments in stack traces can span across bundles.
|
|
497
|
+
* Translates a single "raw frame" or call-site, including outlined functions. It's the caller's responsibility to
|
|
498
|
+
* merge outlined frames with their caller(s) (see {@link GeneratedFrameKind}).
|
|
522
499
|
*/
|
|
523
|
-
|
|
500
|
+
translateRawFrame(generatedLine: number, generatedColumn: number): RawFrameTranslation {
|
|
524
501
|
const rangeChain = this.#findGeneratedRangeChain(generatedLine, generatedColumn);
|
|
525
|
-
|
|
526
|
-
|
|
502
|
+
const kind = this.#generatedFrameKind(rangeChain);
|
|
503
|
+
if (kind === GeneratedFrameKind.HIDDEN) {
|
|
504
|
+
return {kind, frames: []};
|
|
527
505
|
}
|
|
506
|
+
const frame = this.#translateTopFrame(generatedLine, generatedColumn, rangeChain);
|
|
507
|
+
return {kind, frames: frame ? [frame, ...this.#translateInlinedCallers(rangeChain)] : []};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* The top-most frame is translated the same, regardless of whether we have inlined functions: The name is the
|
|
512
|
+
* original function surrounding the generated position, and the location is the mapped generated position.
|
|
513
|
+
*
|
|
514
|
+
* If the generated position has no mapping, the generated ranges may still tell which authored function (or
|
|
515
|
+
* which file, for top-level code) the position belongs to. The frame then has no position.
|
|
516
|
+
*/
|
|
517
|
+
#translateTopFrame(generatedLine: number, generatedColumn: number,
|
|
518
|
+
rangeChain: ScopesCodec.GeneratedRange[]): TranslatedFrame|null {
|
|
519
|
+
const position = {line: generatedLine, column: generatedColumn};
|
|
520
|
+
const innerMostScope = this.#innerMostOriginalScope(rangeChain, position);
|
|
521
|
+
const functionScope = this.#findFunctionScopeInOriginalScopeChain(innerMostScope);
|
|
522
|
+
const name = functionScope ? (functionScope.name ?? '') : undefined;
|
|
528
523
|
|
|
529
524
|
const mapping = this.#sourceMap.findEntry(generatedLine, generatedColumn);
|
|
530
|
-
if (mapping?.sourceIndex
|
|
531
|
-
return
|
|
525
|
+
if (mapping?.sourceIndex !== undefined) {
|
|
526
|
+
return {
|
|
527
|
+
line: mapping.sourceLineNumber,
|
|
528
|
+
column: mapping.sourceColumnNumber,
|
|
529
|
+
name,
|
|
530
|
+
url: mapping.sourceURL,
|
|
531
|
+
functionStart: functionScope?.start,
|
|
532
|
+
};
|
|
532
533
|
}
|
|
534
|
+
if (!innerMostScope) {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
return {name, url: this.#sourceURLOfScope(functionScope ?? innerMostScope), functionStart: functionScope?.start};
|
|
538
|
+
}
|
|
533
539
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
url: mapping.sourceURL,
|
|
540
|
-
}];
|
|
541
|
-
|
|
542
|
-
// Walk the range chain inside out until we find a generated function and for each inlined function add a frame.
|
|
540
|
+
/**
|
|
541
|
+
* Walk the range chain inside out until we find a generated function and for each inlined function add a frame.
|
|
542
|
+
*/
|
|
543
|
+
#translateInlinedCallers(rangeChain: ScopesCodec.GeneratedRange[]): TranslatedFrame[] {
|
|
544
|
+
const result: TranslatedFrame[] = [];
|
|
543
545
|
for (let i = rangeChain.length - 1; i >= 0 && !rangeChain[i].isStackFrame; --i) {
|
|
544
546
|
const range = rangeChain[i];
|
|
545
547
|
if (!range.callSite) {
|
|
@@ -547,11 +549,13 @@ export class SourceMapScopesInfo {
|
|
|
547
549
|
}
|
|
548
550
|
|
|
549
551
|
const originalScopeChain = this.#findOriginalScopeChain(range.callSite);
|
|
552
|
+
const functionScope = this.#findFunctionScopeInOriginalScopeChain(originalScopeChain.at(-1));
|
|
550
553
|
result.push({
|
|
551
554
|
line: range.callSite.line,
|
|
552
555
|
column: range.callSite.column,
|
|
553
|
-
name:
|
|
556
|
+
name: functionScope ? (functionScope.name ?? '') : undefined,
|
|
554
557
|
url: this.#sourceMap.sourceURLForSourceIndex(range.callSite.sourceIndex),
|
|
558
|
+
functionStart: functionScope?.start,
|
|
555
559
|
});
|
|
556
560
|
}
|
|
557
561
|
|
|
@@ -559,17 +563,61 @@ export class SourceMapScopesInfo {
|
|
|
559
563
|
}
|
|
560
564
|
}
|
|
561
565
|
|
|
566
|
+
/**
|
|
567
|
+
* Describes how the generated function surrounding a generated position shows up in stack traces.
|
|
568
|
+
*
|
|
569
|
+
* Compilers tend to introduce functions (and calls to them) that don't exist in the authored code. The scopes
|
|
570
|
+
* proposal distinguishes two cases:
|
|
571
|
+
*
|
|
572
|
+
* 1) The generated function contains authored code, e.g. a block scope that was turned into a function. The
|
|
573
|
+
* generated range is marked "hidden", but links to the original scope via its definition. We can pause
|
|
574
|
+
* in such a function, but the frame is merged with its caller(s) in stack traces: The outlined code
|
|
575
|
+
* logically belongs to the (authored) function that transitively calls it.
|
|
576
|
+
*
|
|
577
|
+
* 2) The generated function doesn't represent any authored code, e.g. a compiler helper. The generated range
|
|
578
|
+
* has no definition. The scopes spec is being updated to say that such a range can be ignored in stack
|
|
579
|
+
* traces, so we drop these frames.
|
|
580
|
+
*/
|
|
581
|
+
export const enum GeneratedFrameKind {
|
|
582
|
+
/** A regular (possibly with inlined functions) generated function, or top-level code. */
|
|
583
|
+
VISIBLE = 'VISIBLE',
|
|
584
|
+
/** A generated function marked as "hidden" that has a definition (case 1). */
|
|
585
|
+
OUTLINED = 'OUTLINED',
|
|
586
|
+
/** A generated function without a definition (case 2). */
|
|
587
|
+
HIDDEN = 'HIDDEN',
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** See {@link SourceMapScopesInfo.translateRawFrame}. */
|
|
591
|
+
export interface RawFrameTranslation {
|
|
592
|
+
kind: GeneratedFrameKind;
|
|
593
|
+
/**
|
|
594
|
+
* [top, ...inlinedCallers] in top-to-bottom order. Empty for {@link GeneratedFrameKind.HIDDEN} frames, or if
|
|
595
|
+
* neither the mappings nor the generated ranges know anything about the generated position.
|
|
596
|
+
*
|
|
597
|
+
* For {@link GeneratedFrameKind.OUTLINED} frames, the top frame is named after the authored function the outlined
|
|
598
|
+
* code belongs to.
|
|
599
|
+
*/
|
|
600
|
+
frames: TranslatedFrame[];
|
|
601
|
+
}
|
|
602
|
+
|
|
562
603
|
/**
|
|
563
604
|
* Represents a stack frame in original terms. It closely aligns with StackTrace.StackTrace.Frame,
|
|
564
605
|
* but since we can't import that type here we mirror it here somewhat.
|
|
565
606
|
*
|
|
566
|
-
* Equivalent to Pick<StackTrace.StackTrace.Frame, 'line'|'column'|'name'|'url'
|
|
607
|
+
* Equivalent to Pick<StackTrace.StackTrace.Frame, 'line'|'column'|'name'|'url'>, except that the position is
|
|
608
|
+
* optional.
|
|
567
609
|
*/
|
|
568
610
|
export interface TranslatedFrame {
|
|
569
|
-
|
|
570
|
-
|
|
611
|
+
/**
|
|
612
|
+
* `line` and `column` are undefined iff the generated position has no mapping. The frame then only identifies
|
|
613
|
+
* the authored function: `name`, `functionStart` and the `url` of the function's source.
|
|
614
|
+
*/
|
|
615
|
+
line?: number;
|
|
616
|
+
column?: number;
|
|
571
617
|
name?: string;
|
|
572
618
|
url?: Platform.DevToolsPath.UrlString;
|
|
619
|
+
/** Start of the original function scope containing this frame's position. Undefined for top-level code. */
|
|
620
|
+
functionStart?: ScopesCodec.Position;
|
|
573
621
|
}
|
|
574
622
|
|
|
575
623
|
export function findExpression(range: ScopesCodec.GeneratedRange|undefined, index: number, line = 0,
|
|
@@ -19,6 +19,23 @@ const objectUrlRegistry = new FinalizationRegistry<string>(url => {
|
|
|
19
19
|
*/
|
|
20
20
|
const MAX_BLOB_SIZE_BYTES = 10 * 1024 * 1024;
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Returns `charset` if `TextDecoder` supports it, and 'utf-8' otherwise.
|
|
24
|
+
* The charset usually comes straight from a `Content-Type` header, and
|
|
25
|
+
* `TextDecoder` has no non-throwing way to check a label.
|
|
26
|
+
*/
|
|
27
|
+
function supportedCharsetOrUtf8(charset: string|undefined): string {
|
|
28
|
+
if (!charset) {
|
|
29
|
+
return 'utf-8';
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
new TextDecoder(charset);
|
|
33
|
+
return charset;
|
|
34
|
+
} catch {
|
|
35
|
+
return 'utf-8';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
22
39
|
/**
|
|
23
40
|
* This class is a small wrapper around either raw binary or text data.
|
|
24
41
|
* As the binary data can actually contain textual data, we also store the
|
|
@@ -44,7 +61,7 @@ export class ContentData {
|
|
|
44
61
|
#imagePreviewUrl?: string;
|
|
45
62
|
|
|
46
63
|
constructor(data: string, isBase64: boolean, mimeType: string, charset?: string) {
|
|
47
|
-
this.charset = charset
|
|
64
|
+
this.charset = supportedCharsetOrUtf8(charset);
|
|
48
65
|
if (isBase64) {
|
|
49
66
|
this.#contentAsBase64 = data;
|
|
50
67
|
} else {
|