Preserve emulation state across Lighthouse runs

A bug in CDP prevents sessions from manipulating the device emulation
if another session detaches.

Calling "Emulation.clearDeviceMetricOverride" will correct the
behavior, so calling it before resetting the previous emulation
settings is an adequate workaround.

This CL also preserves the emulation type, display scale, and device
mode across Lighthouse runs.

Lighthouse issue:
https://github.com/GoogleChrome/lighthouse/issues/14134

Bug: 1337089
Change-Id: I9c54fb82276998d63887f7c2dc7085b9d280d32f
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3710871
Reviewed-by: Paul Irish <paulirish@chromium.org>
Reviewed-by: Connor Clark <cjamcl@chromium.org>
Commit-Queue: Adam Raine <asraine@chromium.org>
diff --git a/front_end/panels/lighthouse/LighthousePanel.ts b/front_end/panels/lighthouse/LighthousePanel.ts
index fd9656e..e544474 100644
--- a/front_end/panels/lighthouse/LighthousePanel.ts
+++ b/front_end/panels/lighthouse/LighthousePanel.ts
@@ -81,7 +81,15 @@
   private rightToolbar!: UI.Toolbar.Toolbar;
   private showSettingsPaneSetting!: Common.Settings.Setting<boolean>;
   private stateBefore?: {
-    emulation: {enabled: boolean, outlineEnabled: boolean, toolbarControlsEnabled: boolean},
+    emulation: {
+      type: EmulationModel.DeviceModeModel.Type,
+      enabled: boolean,
+      outlineEnabled: boolean,
+      toolbarControlsEnabled: boolean,
+      scale: number,
+      device: EmulationModel.EmulatedDevices.EmulatedDevice|null,
+      mode: EmulationModel.EmulatedDevices.Mode|null,
+    },
     network: {conditions: SDK.NetworkManager.Conditions},
   };
   private isLHAttached?: boolean;
@@ -385,7 +393,7 @@
       }
 
     } catch (err) {
-      await this.resetEmulationAndProtocolConnection();
+      await this.restoreEmulationAndProtocolConnection();
       if (err instanceof Error) {
         this.statusView.renderBugReport(err);
       }
@@ -413,12 +421,12 @@
 
       Host.userMetrics.actionTaken(Host.UserMetrics.Action.LighthouseFinished);
 
-      await this.resetEmulationAndProtocolConnection();
+      await this.restoreEmulationAndProtocolConnection();
       this.buildReportUI(lighthouseResponse.lhr, lighthouseResponse.artifacts);
       // Give focus to the new audit button when completed
       this.newButton.element.focus();
     } catch (err) {
-      await this.resetEmulationAndProtocolConnection();
+      await this.restoreEmulationAndProtocolConnection();
       if (err instanceof Error) {
         this.statusView.renderBugReport(err);
       }
@@ -430,7 +438,7 @@
   private async cancelLighthouse(): Promise<void> {
     this.currentLighthouseRun = undefined;
     this.statusView.updateStatus(i18nString(UIStrings.cancelling));
-    await this.resetEmulationAndProtocolConnection();
+    await this.restoreEmulationAndProtocolConnection();
     this.renderStartView();
   }
 
@@ -447,9 +455,13 @@
     const emulationModel = EmulationModel.DeviceModeModel.DeviceModeModel.instance();
     this.stateBefore = {
       emulation: {
+        type: emulationModel.type(),
         enabled: emulationModel.enabledSetting().get(),
         outlineEnabled: emulationModel.deviceOutlineSetting().get(),
         toolbarControlsEnabled: emulationModel.toolbarControlsEnabledSetting().get(),
+        scale: emulationModel.scaleSetting().get(),
+        device: emulationModel.device(),
+        mode: emulationModel.mode(),
       },
       network: {conditions: SDK.NetworkManager.MultitargetNetworkManager.instance().networkConditions()},
     };
@@ -473,7 +485,7 @@
     this.isLHAttached = true;
   }
 
-  private async resetEmulationAndProtocolConnection(): Promise<void> {
+  private async restoreEmulationAndProtocolConnection(): Promise<void> {
     if (!this.isLHAttached) {
       return;
     }
@@ -483,9 +495,25 @@
 
     if (this.stateBefore) {
       const emulationModel = EmulationModel.DeviceModeModel.DeviceModeModel.instance();
-      emulationModel.enabledSetting().set(this.stateBefore.emulation.enabled);
-      emulationModel.deviceOutlineSetting().set(this.stateBefore.emulation.outlineEnabled);
-      emulationModel.toolbarControlsEnabledSetting().set(this.stateBefore.emulation.toolbarControlsEnabled);
+
+      // Detaching a session after overriding device metrics will prevent other sessions from overriding device metrics in the future.
+      // A workaround is to call "Emulation.clearDeviceMetricOverride" which is the result of the next line.
+      // https://bugs.chromium.org/p/chromium/issues/detail?id=1337089
+      emulationModel.emulate(EmulationModel.DeviceModeModel.Type.None, null, null);
+
+      const {type, enabled, outlineEnabled, toolbarControlsEnabled, scale, device, mode} = this.stateBefore.emulation;
+      emulationModel.enabledSetting().set(enabled);
+      emulationModel.deviceOutlineSetting().set(outlineEnabled);
+      emulationModel.toolbarControlsEnabledSetting().set(toolbarControlsEnabled);
+
+      // `emulate` will ignore the `scale` parameter for responsive emulation.
+      // In this case we can just set it here.
+      if (type === EmulationModel.DeviceModeModel.Type.Responsive) {
+        emulationModel.scaleSetting().set(scale);
+      }
+
+      emulationModel.emulate(type, device, mode, scale);
+
       SDK.NetworkManager.MultitargetNetworkManager.instance().setNetworkConditions(this.stateBefore.network.conditions);
       delete this.stateBefore;
     }