Bug 1408124 - Update Perf component to obey private browsing state draft
authorGreg Tatum <gtatum@mozilla.com>
Tue, 07 Nov 2017 14:53:24 -0600
changeset 695086 043a247f303b4b557ee7aba22f5d0967ebea4f2d
parent 695085 57a2cecb27f3fc0274776dd5b2e546cc9a8df71d
child 695087 28699ad7b691cbb1b33b9ef38aeecc4829181aa3
child 695099 69079a001ddc255791a72199e726be89bb2a9e3e
push id88335
push usergtatum@mozilla.com
push dateWed, 08 Nov 2017 18:51:46 +0000
bugs1408124
milestone58.0a1
Bug 1408124 - Update Perf component to obey private browsing state MozReview-Commit-ID: IVKz0d8olpT
devtools/client/performance/new/components/Perf.js
devtools/client/performance/new/panel.js
devtools/client/performance/new/test/head.js
--- a/devtools/client/performance/new/components/Perf.js
+++ b/devtools/client/performance/new/components/Perf.js
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const { createClass, PropTypes, DOM } = require("devtools/client/shared/vendor/react");
+const { PropTypes, Component, DOM } = require("devtools/client/shared/vendor/react");
 const { div, button } = DOM;
 
 /**
  * The recordingState is one of the following:
  **/
 
 // The initial state before we've queried the PerfActor
 const NOT_YET_KNOWN = "not-yet-known";
@@ -20,50 +20,95 @@ const REQUEST_TO_START_RECORDING = "requ
 const REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER =
   "request-to-get-profile-and-stop-profiler";
 // An async request has been sent to stop the profiler.
 const REQUEST_TO_STOP_PROFILER = "request-to-stop-profiler";
 // The profiler notified us that our request to start it actually started it.
 const RECORDING = "recording";
 // Some other code with access to the profiler started it.
 const OTHER_IS_RECORDING = "other-is-recording";
-
-const Perf = createClass({
-  displayName: "Perf",
+// Profiling is not available when in private browsing mode.
+const LOCKED_FOR_PRIVATE_BROWSING = "locked-for-private-browsing";
 
-  propTypes: {
-    perfFront: PropTypes.object.isRequired,
-    receiveProfile: PropTypes.func.isRequired
-  },
+class Perf extends Component {
+  static get propTypes() {
+    return {
+      perfFront: PropTypes.object.isRequired,
+      receiveProfile: PropTypes.func.isRequired
+    };
+  }
 
-  getInitialState() {
-    return {
+  constructor(props) {
+    super(props);
+    this.state = {
       recordingState: NOT_YET_KNOWN,
       recordingUnexpectedlyStopped: false,
+      // The following is either "null" for unknown, or a boolean value.
+      isSupportedPlatform: null
     };
-  },
-
-  getRecordingStateForTesting() {
-    return this.state.recordingState;
-  },
+    this.startRecording = this.startRecording.bind(this);
+    this.getProfileAndStopProfiler = this.getProfileAndStopProfiler.bind(this);
+    this.stopProfilerAndDiscardProfile = this.stopProfilerAndDiscardProfile.bind(this);
+    this.handleProfilerStarting = this.handleProfilerStarting.bind(this);
+    this.handleProfilerStopping = this.handleProfilerStopping.bind(this);
+    this.handlePrivateBrowsingStarting = this.handlePrivateBrowsingStarting.bind(this);
+    this.handlePrivateBrowsingEnding = this.handlePrivateBrowsingEnding.bind(this);
+  }
 
   componentDidMount() {
+    const { perfFront } = this.props;
+
+    // Ask for the initial state of the profiler.
+    Promise.all([
+      perfFront.isActive(),
+      perfFront.isSupportedPlatform(),
+      perfFront.isLockedForPrivateBrowsing(),
+    ]).then((results) => {
+      const [
+        isActive,
+        isSupportedPlatform,
+        isLockedForPrivateBrowsing
+      ] = results;
+
+      let recordingState = this.state.recordingState;
+      // It's theoretically possible we got an event that already let us know about
+      // the current state of the profiler.
+      if (recordingState === NOT_YET_KNOWN && isSupportedPlatform) {
+        if (isLockedForPrivateBrowsing) {
+          recordingState = LOCKED_FOR_PRIVATE_BROWSING;
+        } else {
+          recordingState = isActive
+            ? OTHER_IS_RECORDING
+            : AVAILABLE_TO_RECORD;
+        }
+      }
+      this.setState({ isSupportedPlatform, recordingState });
+    });
+
     // We don't yet know what state the profile is in, find out.
     this.props.perfFront.isActive().then(isActive => {
       if (this.state.recordingState === NOT_YET_KNOWN) {
         this.setState({
           recordingState: isActive ? OTHER_IS_RECORDING : AVAILABLE_TO_RECORD
         });
       }
     });
 
     // Handle when the profiler changes state. It might be us, it might be someone else.
     this.props.perfFront.on("profiler-started", this.handleProfilerStarting);
     this.props.perfFront.on("profiler-stopped", this.handleProfilerStopping);
-  },
+    this.props.perfFront.on("profile-locked-for-private-browsing",
+      this.handlePrivateBrowsingStarting);
+    this.props.perfFront.on("profile-unlocked-from-private-browsing",
+      this.handlePrivateBrowsingEnding);
+  }
+
+  getRecordingStateForTesting() {
+    return this.state.recordingState;
+  }
 
   handleProfilerStarting() {
     switch (this.state.recordingState) {
       case NOT_YET_KNOWN:
         // We couldn't have started it yet, so it must have been someone
         // else. (fallthrough)
       case AVAILABLE_TO_RECORD:
         // We aren't recording, someone else started it up. (fallthrough)
@@ -82,152 +127,218 @@ const Perf = createClass({
       case REQUEST_TO_START_RECORDING:
         // Wait for the profiler to tell us that it has started.
         this.setState({
           recordingState: RECORDING,
           recordingUnexpectedlyStopped: false
         });
         break;
 
+      case LOCKED_FOR_PRIVATE_BROWSING:
       case OTHER_IS_RECORDING:
       case RECORDING:
         // These state cases don't make sense to happen, and means we have a logical
         // fallacy somewhere.
         throw new Error(
           "The profiler started recording, when it shouldn't have " +
           `been able to. Current state: "${this.state.recordingState}"`);
       default:
         throw new Error("Unhandled recording state");
     }
-  },
+  }
 
   handleProfilerStopping() {
     switch (this.state.recordingState) {
       case NOT_YET_KNOWN:
       case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
       case REQUEST_TO_STOP_PROFILER:
       case OTHER_IS_RECORDING:
         this.setState({
           recordingState: AVAILABLE_TO_RECORD,
           recordingUnexpectedlyStopped: false
         });
         break;
 
       case REQUEST_TO_START_RECORDING:
-        // Highly unlikely, but someone stopped the recorder, this is fine. Do nothing.
+        // Highly unlikely, but someone stopped the recorder, this is fine.
+        // Do nothing (fallthrough).
+      case LOCKED_FOR_PRIVATE_BROWSING:
+        // The profiler is already locked, so we know about this already.
         break;
 
       case RECORDING:
         this.setState({
           recordingState: AVAILABLE_TO_RECORD,
           recordingUnexpectedlyStopped: true
         });
         break;
 
       case AVAILABLE_TO_RECORD:
         throw new Error(
           "The profiler stopped recording, when it shouldn't have been able to.");
       default:
         throw new Error("Unhandled recording state");
     }
-  },
+  }
+
+  handlePrivateBrowsingStarting() {
+    switch (this.state.recordingState) {
+      case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
+        // This one is a tricky case. Go ahead and act like nothing went wrong, maybe
+        // it will resolve correctly? (fallthrough)
+      case REQUEST_TO_STOP_PROFILER:
+      case AVAILABLE_TO_RECORD:
+      case OTHER_IS_RECORDING:
+      case NOT_YET_KNOWN:
+        this.setState({
+          recordingState: LOCKED_FOR_PRIVATE_BROWSING,
+          recordingUnexpectedlyStopped: false
+        });
+        break;
+
+      case REQUEST_TO_START_RECORDING:
+      case RECORDING:
+        this.setState({
+          recordingState: LOCKED_FOR_PRIVATE_BROWSING,
+          recordingUnexpectedlyStopped: true
+        });
+        break;
+
+      case LOCKED_FOR_PRIVATE_BROWSING:
+        // Do nothing
+        break;
+
+      default:
+        throw new Error("Unhandled recording state");
+    }
+  }
+
+  handlePrivateBrowsingEnding() {
+    // No matter the state, go ahead and set this as ready to record. This should
+    // be the only logical state to go into.
+    this.setState({
+      recordingState: AVAILABLE_TO_RECORD,
+      recordingUnexpectedlyStopped: false
+    });
+  }
 
   startRecording() {
     this.setState({
       recordingState: REQUEST_TO_START_RECORDING,
       // Reset this error state since it's no longer valid.
       recordingUnexpectedlyStopped: false,
     });
     this.props.perfFront.startProfiler();
-  },
+  }
 
-  getProfileAndStopProfiler: async function () {
+  async getProfileAndStopProfiler() {
     this.setState({ recordingState: REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER });
     const profile = await this.props.perfFront.getProfileAndStopProfiler();
     this.setState({ recordingState: AVAILABLE_TO_RECORD });
     console.log("getProfileAndStopProfiler");
     this.props.receiveProfile(profile);
-  },
+  }
 
-  stopProfilerAndDiscardProfile: async function () {
+  async stopProfilerAndDiscardProfile() {
     this.setState({ recordingState: REQUEST_TO_STOP_PROFILER });
     this.props.perfFront.stopProfilerAndDiscardProfile();
-  },
+  }
 
   render() {
-    const { recordingState } = this.state;
+    const { recordingState, isSupportedPlatform } = this.state;
+
+    // Handle the cases of platform support.
+    switch (isSupportedPlatform) {
+      case null:
+        // We don't know yet if this is a supported platform, wait for a response.
+        return null;
+      case false:
+        return renderButton({
+          label: "Start recording",
+          disabled: true,
+          additionalMessage: "Your platform is not supported. The Gecko Profiler only " +
+                             "supports Tier-1 platforms."
+        });
+      case true:
+        // Continue on and render the panel.
+        break;
+    }
+
     // TODO - L10N all of the messages.
     switch (recordingState) {
       case NOT_YET_KNOWN:
         return null;
 
       case AVAILABLE_TO_RECORD:
         return renderButton({
-          recordingState,
           onClick: this.startRecording,
           label: "Start recording",
           additionalMessage: this.state.recordingUnexpectedlyStopped
             ? div({}, "The recording was stopped by another tool.")
             : null
         });
 
       case REQUEST_TO_STOP_PROFILER:
         return renderButton({
-          recordingState,
           label: "Stopping the recording",
           disabled: true
         });
 
       case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
         return renderButton({
-          recordingState,
           label: "Stopping the recording, and capturing the profile",
           disabled: true
         });
 
       case REQUEST_TO_START_RECORDING:
       case RECORDING:
         return renderButton({
-          recordingState,
           label: "Stop and grab the recording",
           onClick: this.getProfileAndStopProfiler,
           disabled: this.state.recordingState === REQUEST_TO_START_RECORDING
         });
 
       case OTHER_IS_RECORDING:
         return renderButton({
-          recordingState,
           label: "Stop and discard the other recording",
           onClick: this.stopProfilerAndDiscardProfile,
           disabled: this.state.recordingState === REQUEST_TO_START_RECORDING,
           additionalMessage: "Another tool is currently recording."
         });
 
+      case LOCKED_FOR_PRIVATE_BROWSING:
+        return renderButton({
+          label: "Start recording",
+          disabled: true,
+          additionalMessage: "The profiler is disabled when Private Browsing is " +
+                             "enabled. Close all Private Windows to re-enable the " +
+                             "profiler."
+        });
+
       default:
         throw new Error("Unhandled recording state");
     }
   }
-});
+}
 
 module.exports = Perf;
 
 function renderButton(props) {
-  const { disabled, label, onClick, additionalMessage, recordingState } = props;
+  const { disabled, label, onClick, additionalMessage } = props;
   const nbsp = "\u00A0";
 
   return div(
     { className: "perf" },
     div({ className: "perf-additional-message" }, additionalMessage || nbsp),
     div(
       {},
       button(
         {
           className: "devtools-button perf-button",
           "data-standalone": true,
-          "data-state": recordingState,
           disabled,
           onClick
         },
         label
       )
     )
   );
 }
--- a/devtools/client/performance/new/panel.js
+++ b/devtools/client/performance/new/panel.js
@@ -1,61 +1,56 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const { Task } = require("devtools/shared/task");
 const { PerfFront } = require("devtools/shared/fronts/perf");
 
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/shared/old-event-emitter");
 
-function PerfPanel(iframeWindow, toolbox) {
-  this.panelWin = iframeWindow;
-  this.toolbox = toolbox;
+class PerfPanel {
+  constructor(iframeWindow, toolbox) {
+    this.panelWin = iframeWindow;
+    this.toolbox = toolbox;
 
-  EventEmitter.decorate(this);
-}
+    EventEmitter.decorate(this);
+  }
 
-exports.PerfPanel = PerfPanel;
-
-PerfPanel.prototype = {
   /**
    * Open is effectively an asynchronous constructor.
-   *
-   * @return object
-   *         A promise that is resolved when the Perf tool
-   *         completes opening.
+   * @return {Promise} Resolves when the Perf tool completes opening.
    */
-  open: Task.async(function* () {
+  open() {
     if (this._opening) {
       return this._opening;
     }
     this._opening = new Promise(async (resolve) => {
       this.panelWin.gToolbox = this.toolbox;
       this.panelWin.gTarget = this.target;
       const rootForm = await this.target.root;
       const perfFront = new PerfFront(this.target.client, rootForm);
 
       this.isReady = true;
       this.emit("ready");
       this.panelWin.gInit(perfFront);
       resolve(this);
     });
     return this._opening;
-  }),
+  }
 
-  // DevToolPanel API
+  // DevToolPanel API:
 
   get target() {
     return this.toolbox.target;
-  },
+  }
 
-  destroy: Task.async(function* () {
+  async destroy() {
     // Make sure this panel is not already destroyed.
     if (this._destroyed) {
       return;
     }
     this.emit("destroyed");
     this._destroyed = true;
-  })
-};
+  }
+}
+exports.PerfPanel = PerfPanel;
--- a/devtools/client/performance/new/test/head.js
+++ b/devtools/client/performance/new/test/head.js
@@ -23,17 +23,17 @@ const { BrowserLoader } = Cu.import("res
 function addPerfTest(asyncTestFn) {
   add_task(async () => {
     waitForExplicitFinish();
 
     const [host, window, document] = await createHost(URL_ROOT + "doc_mount_react.html");
     const mountElement = document.querySelector("#root");
     const perfFront = new MockPerfFront();
     const { require: browserRequire } = BrowserLoader({
-      baseURI: "res`ource://devtools/client/performance/new/test/",
+      baseURI: "resource://devtools/client/performance/new/test/",
       window
     });
 
     // Run the test.
     try {
       await asyncTestFn({
         browserRequire,
         document,