diff --git a/.ci/pipeline.yml b/.ci/pipeline.yml
index c79d2b51..5dbe559c 100644
--- a/.ci/pipeline.yml
+++ b/.ci/pipeline.yml
@@ -19,4 +19,14 @@ jobs:
pool:
vmImage: 'vs2017-win2016'
steps:
+ - powershell: |
+ # This step can be removed once Edge is included on the test VM image.
+ # The download URI comes from: https://www.microsoftedgeinsider.com/en-us/download
+ $InstallerDownloadURI = "https://go.microsoft.com/fwlink/?linkid=2084649&Channel=Canary&language=en&Consent=1"
+ $InstallerExe = "$ENV:BUILD_ARTIFACTSTAGINGDIRECTORY\MicrosoftEdgeSetup.exe"
+ Invoke-WebRequest -Uri $InstallerDownloadURI -OutFile $InstallerExe
+ Start-Process $InstallerExe -Wait -ArgumentList '/silent /install'
+ # Cleanup installer
+ Remove-Item $InstallerExe
+ displayName: Install Edge Canary
- template: common-validation.yml
diff --git a/demos/webview/WebView2Sample/WebView2Sample.vcxproj b/demos/webview/WebView2Sample/WebView2Sample.vcxproj
index 89a73b35..896b2b95 100644
--- a/demos/webview/WebView2Sample/WebView2Sample.vcxproj
+++ b/demos/webview/WebView2Sample/WebView2Sample.vcxproj
@@ -103,6 +103,7 @@
true
_DEBUG;_WINDOWS;%(PreprocessorDefinitions)
true
+ MultiThreaded
true
diff --git a/demos/webview/WebView2Sample/WebView2Sample.vcxproj.filters b/demos/webview/WebView2Sample/WebView2Sample.vcxproj.filters
index 8e4c2d8d..fa928368 100644
--- a/demos/webview/WebView2Sample/WebView2Sample.vcxproj.filters
+++ b/demos/webview/WebView2Sample/WebView2Sample.vcxproj.filters
@@ -21,6 +21,5 @@
-
\ No newline at end of file
diff --git a/src/binder.ts b/src/binder.ts
index 0b150871..3fbe312d 100644
--- a/src/binder.ts
+++ b/src/binder.ts
@@ -43,7 +43,7 @@ const packageJson = require('../../package.json');
export interface IBinderDelegate {
acquireDap(target: ITarget): Promise;
// Returns whether we should disable child session treatment.
- initAdapter(debugAdapter: DebugAdapter, target: ITarget): Promise;
+ initAdapter(debugAdapter: DebugAdapter, target: ITarget, launcher: ILauncher): Promise;
releaseDap(target: ITarget): void;
}
@@ -84,7 +84,7 @@ export class Binder implements IDisposable {
launcher.onTargetListChanged(
() => {
const targets = this.targetList();
- this._attachToNewTargets(targets);
+ this._attachToNewTargets(targets, launcher);
this._detachOrphanThreads(targets);
this._onTargetListChangedEmitter.fire();
},
@@ -322,7 +322,7 @@ export class Binder implements IDisposable {
return result;
}
- async attach(target: ITarget) {
+ async attach(target: ITarget, launcher: ILauncher) {
if (!target.canAttach()) return;
const cdp = await target.attach();
if (!cdp) return;
@@ -357,7 +357,7 @@ export class Binder implements IDisposable {
cdp.Runtime.runIfWaitingForDebugger({});
return {};
};
- if (await this._delegate.initAdapter(debugAdapter, target)) {
+ if (await this._delegate.initAdapter(debugAdapter, target, launcher)) {
startThread();
} else {
dap.on('attach', startThread);
@@ -386,7 +386,7 @@ export class Binder implements IDisposable {
this._releaseTarget(target);
}
- _attachToNewTargets(targets: ITarget[]) {
+ _attachToNewTargets(targets: ITarget[], launcher: ILauncher) {
for (const target of targets.values()) {
if (!target.waitingForDebugger()) {
continue;
@@ -394,7 +394,7 @@ export class Binder implements IDisposable {
const thread = this._threads.get(target);
if (!thread) {
- this.attach(target);
+ this.attach(target, launcher);
}
}
}
diff --git a/src/test/test.ts b/src/test/test.ts
index e3a2ab28..aae02101 100644
--- a/src/test/test.ts
+++ b/src/test/test.ts
@@ -19,10 +19,10 @@ import {
INodeLaunchConfiguration,
nodeAttachConfigDefaults,
nodeLaunchConfigDefaults,
+ AnyChromiumLaunchConfiguration,
} from '../configuration';
import Dap from '../dap/api';
import DapConnection from '../dap/connection';
-import { ChromeLauncher } from '../targets/browser/chromeLauncher';
import { ITarget } from '../targets/targets';
import { GoldenText } from './goldenText';
import { Logger } from './logger';
@@ -32,6 +32,7 @@ import { TelemetryReporter } from '../telemetry/telemetryReporter';
import { ILogger } from '../common/logging';
import { Logger as AdapterLogger } from '../common/logging/logger';
import { createTopLevelSessionContainer, createGlobalContainer } from '../ioc';
+import { BrowserLauncher } from '../targets/browser/browserLauncher';
export const kStabilizeNames = ['id', 'threadId', 'sourceReference', 'variablesReference'];
@@ -112,7 +113,11 @@ export interface ITestHandle {
readonly assertLog: AssertLog;
load(): Promise;
- _init(adapter: DebugAdapter, target: ITarget): Promise;
+ _init(
+ adapter: DebugAdapter,
+ target: ITarget,
+ launcher: BrowserLauncher,
+ ): Promise;
}
export class TestP implements ITestHandle {
@@ -196,7 +201,11 @@ export class TestP implements ITestHandle {
return this._root.workspacePath(relative);
}
- async _init(adapter: DebugAdapter) {
+ async _init(
+ adapter: DebugAdapter,
+ _target: ITarget,
+ launcher: BrowserLauncher,
+ ) {
adapter.breakpointManager.setPredictorDisabledForTest(true);
adapter.sourceContainer.setSourceMapTimeouts({
load: 0,
@@ -206,7 +215,8 @@ export class TestP implements ITestHandle {
});
this._adapter = adapter;
- this._connection = this._root._browserLauncher.connectionForTest()!;
+ this._root._browserLauncher = launcher;
+ this._connection = this._root._browserLauncher?.connectionForTest()!;
const result = await this._connection.rootSession().Target.attachToBrowserTarget({});
const testSession = this._connection.createSession(result!.sessionId);
const { sessionId } = (await testSession.Target.attachToTarget({
@@ -306,7 +316,7 @@ export class TestRoot {
private _workerCallback: (session: ITestHandle) => void;
private _launchCallback: (session: ITestHandle) => void;
- _browserLauncher: ChromeLauncher;
+ _browserLauncher: BrowserLauncher | undefined;
readonly binder: Binder;
private _onSessionCreatedEmitter = new EventEmitter();
@@ -337,7 +347,7 @@ export class TestRoot {
const services = createTopLevelSessionContainer(
createGlobalContainer({ storagePath, isVsCode: true }),
);
- this._browserLauncher = services.get(ChromeLauncher);
+
this.binder = new Binder(
this,
this._root.adapterConnection,
@@ -359,13 +369,17 @@ export class TestRoot {
return p._session.adapterConnection;
}
- async initAdapter(adapter: DebugAdapter, target: ITarget): Promise {
+ async initAdapter(
+ adapter: DebugAdapter,
+ target: ITarget,
+ launcher: BrowserLauncher,
+ ): Promise {
const p = this._targetToP.get(target);
if (!p) {
return true;
}
- const boot = await p._init(adapter, target);
+ const boot = await p._init(adapter, target, launcher);
if (target.parent()) this._workerCallback(p);
else this._launchCallback(p);
this._onSessionCreatedEmitter.fire(p);
@@ -489,7 +503,7 @@ export class TestRoot {
async disconnect(): Promise {
return new Promise(cb => {
this.initialize.then(() => {
- const connection = this._browserLauncher.connectionForTest();
+ const connection = this._browserLauncher?.connectionForTest();
if (connection) {
const disposable = connection.onDisconnected(() => {
cb();
diff --git a/src/test/testRunner.ts b/src/test/testRunner.ts
index 198a1157..580fc0b3 100644
--- a/src/test/testRunner.ts
+++ b/src/test/testRunner.ts
@@ -87,7 +87,16 @@ export async function run(): Promise {
runner.addFile(join(__dirname, 'console/consoleAPITest'));
runner.addFile(join(__dirname, 'extension/nodeConfigurationProvidersTests'));
- for (const file of glob.sync('**/*.test.js', { cwd: __dirname })) {
+ const options = { cwd: __dirname };
+ const files = glob.sync('**/*.test.js', options);
+
+ // Only run tests on supported platforms
+ // https://nodejs.org/api/process.html#process_process_platform
+ if (process.platform === 'win32') {
+ files.push(...glob.sync('**/*.test.win.js', options));
+ }
+
+ for (const file of files) {
runner.addFile(join(__dirname, file));
}
}
diff --git a/src/test/webview/webview-breakpoints-launched-script.txt b/src/test/webview/webview-breakpoints-launched-script.txt
new file mode 100644
index 00000000..7e68970d
--- /dev/null
+++ b/src/test/webview/webview-breakpoints-launched-script.txt
@@ -0,0 +1,16 @@
+{
+ allThreadsStopped : false
+ description : Paused on debugger statement
+ reason : pause
+ threadId :
+}
+ @ ${workspaceFolder}/web/script.js:10:1
+{
+ allThreadsStopped : false
+ description : Paused on breakpoint
+ reason : breakpoint
+ threadId :
+}
+bar @ ${workspaceFolder}/web/script.js:6:3
+foo @ ${workspaceFolder}/web/script.js:2:3
+ @ ${workspaceFolder}/web/script.js:11:1
diff --git a/src/test/webview/webview.breakpoints.test.win.ts b/src/test/webview/webview.breakpoints.test.win.ts
new file mode 100644
index 00000000..30367bcc
--- /dev/null
+++ b/src/test/webview/webview.breakpoints.test.win.ts
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ *--------------------------------------------------------*/
+
+import { ITestHandle } from '../test';
+import Dap from '../../dap/api';
+import { itIntegrates } from '../testIntegrationUtils';
+import { IChromeLaunchConfiguration } from '../../configuration';
+import { DebugType } from '../../common/contributionUtils';
+
+describe('webview breakpoints', () => {
+ async function waitForPause(p: ITestHandle, cb?: (threadId: string) => Promise) {
+ const { threadId } = p.log(await p.dap.once('stopped'));
+ await p.logger.logStackTrace(threadId);
+ if (cb) await cb(threadId);
+ return p.dap.continue({ threadId });
+ }
+
+ itIntegrates('launched script', async ({ r }) => {
+ // Breakpoint in separate script set after launch.
+ const p = await r.launchUrl('script.html', ({
+ type: DebugType.Edge,
+ runtimeExecutable: r.workspacePath('webview/win/WebView2Sample.exe'),
+ useWebView: true,
+ // WebView2Sample.exe launches about:blank
+ urlFilter: 'about:blank',
+ // TODO: Test.launchUrl should support AnyLaunchConfiguration
+ } as unknown) as IChromeLaunchConfiguration);
+ p.load();
+ await waitForPause(p, async () => {
+ const source: Dap.Source = {
+ path: p.workspacePath('web/script.js'),
+ };
+ await p.dap.setBreakpoints({ source, breakpoints: [{ line: 6 }] });
+ });
+ await waitForPause(p);
+ p.assertLog();
+ });
+});
diff --git a/testWorkspace/webview/win/.gitignore b/testWorkspace/webview/win/.gitignore
new file mode 100644
index 00000000..cd325741
--- /dev/null
+++ b/testWorkspace/webview/win/.gitignore
@@ -0,0 +1,2 @@
+# User directories created by WebView application
+WebView2Sample.exe.WebView2*/
diff --git a/testWorkspace/webview/win/README.md b/testWorkspace/webview/win/README.md
new file mode 100644
index 00000000..b8130524
--- /dev/null
+++ b/testWorkspace/webview/win/README.md
@@ -0,0 +1,2 @@
+The WebView2Sample.exe is a Release x64 build.
+The source code can be found in `demos\webview`.
diff --git a/testWorkspace/webview/win/WebView2Loader.dll b/testWorkspace/webview/win/WebView2Loader.dll
new file mode 100644
index 00000000..1d68afbd
Binary files /dev/null and b/testWorkspace/webview/win/WebView2Loader.dll differ
diff --git a/testWorkspace/webview/win/WebView2Sample.exe b/testWorkspace/webview/win/WebView2Sample.exe
new file mode 100644
index 00000000..0c12d6d7
Binary files /dev/null and b/testWorkspace/webview/win/WebView2Sample.exe differ
diff --git a/testWorkspace/webview/win/WebView2Sample.pdb b/testWorkspace/webview/win/WebView2Sample.pdb
new file mode 100644
index 00000000..144e803d
Binary files /dev/null and b/testWorkspace/webview/win/WebView2Sample.pdb differ