Skip to content

Instantly share code, notes, and snippets.

@jasonLaster
Created May 27, 2021 16:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jasonLaster/1e60f6cf21c96e6077c0e8398ef14359 to your computer and use it in GitHub Desktop.
Save jasonLaster/1e60f6cf21c96e6077c0e8398ef14359 to your computer and use it in GitHub Desktop.
diff --git a/devtools/startup/DevToolsStartup.jsm b/devtools/startup/DevToolsStartup.jsm
index 9961c8660c1e..52c49a3d8f3f 100644
--- a/devtools/startup/DevToolsStartup.jsm
+++ b/devtools/startup/DevToolsStartup.jsm
@@ -1387,553 +1387,3 @@ const JsonView = {
}
},
};
-
-var EXPORTED_SYMBOLS = ["DevToolsStartup", "validateProfilerWebChannelUrl"];
-
-// Record Replay stuff.
-
-const { setTimeout, setInterval } = ChromeUtils.import(
- "resource://gre/modules/Timer.jsm"
-);
-const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
-
-XPCOMUtils.defineLazyModuleGetters(this, {
- E10SUtils: "resource://gre/modules/E10SUtils.jsm",
-});
-
-function recordReplayLog(text) {
- dump(`${text}\n`);
-}
-
-ChromeUtils.recordReplayLog = recordReplayLog;
-
-function isLoggedIn() {
- const userPref = Services.prefs.getStringPref("devtools.recordreplay.user");
- if (userPref == "") {
- return;
- }
- const user = JSON.parse(userPref);
-
- // Older versions of Replay did not store the raw object from Auth0 and
- // instead stored backend DB user info, so we need to explicitly look for
- // "sub", not just any parsed object.
- return user == "" ? null : !!user?.sub;
-}
-
-async function saveRecordingUser(user) {
- if (!user) {
- Services.prefs.setStringPref("devtools.recordreplay.user", "");
- return;
- }
-
- Services.prefs.setStringPref(
- "devtools.recordreplay.user",
- JSON.stringify(user)
- );
-}
-
-function createRecordingButton() {
- runTestScript();
-
- let item = {
- id: "record-button",
- type: "button",
- tooltiptext: "record-button.tooltiptext2",
- onClick(evt) {
- if (getConnectionStatus() || !gHasRecordingDriver) {
- return;
- }
-
- const { target: node } = evt;
- const { gBrowser } = node.ownerDocument.defaultView;
- const isRecording = gBrowser.selectedBrowser.hasAttribute(
- "recordExecution"
- ) || isRecordingAllTabs();
-
- if (isRecording) {
- const recording = gBrowserRecordingMap.get(gBrowser.selectedBrowser)
- reloadAndStopRecordingTab(gBrowser);
- openNewReplayTab(recording.recordingId)
- } else {
- reloadAndRecordTab(gBrowser);
- }
- node.refreshStatus();
- },
- onCreated(node) {
- function selectedBrowserHasAttribute(attr) {
- try {
- return node.ownerDocument.defaultView.gBrowser.selectedBrowser.hasAttribute(
- attr
- );
- } catch (e) {
- return false;
- }
- }
-
- node.refreshStatus = () => {
- const recording = selectedBrowserHasAttribute("recordExecution") || isRecordingAllTabs();
-
- node.classList.toggle("recording", recording);
- node.classList.toggle("hidden", isAuthenticationEnabled() && !isLoggedIn() && !isRunningTest());
-
- const status = getConnectionStatus();
- let tooltip = status;
- if (status) {
- node.disabled = true;
- } else if (!gHasRecordingDriver) {
- node.disabled = true;
- tooltip = "missingDriver.label";
- } else if (recording) {
- node.disabled = false;
- tooltip = "stopRecording.label";
- } else {
- node.disabled = false;
- tooltip = "startRecording.label";
- }
-
- const text = StartupBundle.GetStringFromName(tooltip);
- node.setAttribute("tooltiptext", text);
- };
- node.refreshStatus();
-
- Services.prefs.addObserver("devtools.recordreplay.user", () => {
- node.refreshStatus();
- });
- },
- };
- CustomizableUI.createWidget(item);
- CustomizableWidgets.push(item);
-
- item = {
- id: "cloud-recordings-button",
- type: "button",
- tooltiptext: "cloud-recordings-button.tooltiptext2",
- onClick: viewRecordings,
- };
- CustomizableUI.createWidget(item);
- CustomizableWidgets.push(item);
-
- item = {
- id: "replay-signin-button",
- type: "button",
- tooltiptext: "replay-signin-button.tooltiptext2",
- onClick(evt) {
- const { gBrowser } = evt.target.ownerDocument.defaultView;
- const triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
- gBrowser.loadURI("https://replay.io/view?signin=true", { triggeringPrincipal });
- },
- onCreated(node) {
- node.refreshStatus = () => {
- node.classList.toggle("hidden", !isAuthenticationEnabled() || isLoggedIn() || isRunningTest());
- };
- node.refreshStatus();
-
- Services.prefs.addObserver("devtools.recordreplay.user", () => {
- node.refreshStatus();
- });
- },
- };
- CustomizableUI.createWidget(item);
- CustomizableWidgets.push(item);
-
- setConnectionStatusChangeCallback(status => {
- dump(`CloudReplayStatus ${status}\n`);
- refreshAllRecordingButtons();
- });
-}
-
-function refreshAllRecordingButtons() {
- try {
- for (const w of Services.wm.getEnumerator("navigator:browser")) {
- const node = w.document.getElementById("record-button");
- if (node) {
- node.refreshStatus();
- }
- }
- } catch (e) {}
-}
-
-// When state changes which affects the recording buttons, we try to update the
-// buttons immediately, but make sure that the recording button state does not
-// get out of sync with the display state of the button.
-setInterval(refreshAllRecordingButtons, 2000);
-
-function isRunningTest() {
- return !!env.get("RECORD_REPLAY_TEST_SCRIPT");
-}
-
-async function runTestScript() {
- const script = env.get("RECORD_REPLAY_TEST_SCRIPT");
- if (!script) {
- return;
- }
-
- // Make sure we have a window.
- while (!Services.wm.getMostRecentWindow("navigator:browser")) {
- dump(`No window for test script, waiting...\n`);
- await new Promise(resolve => setTimeout(resolve, 100));
- }
-
- const contents = await OS.File.read(script);
- const text = new TextDecoder("utf-8").decode(contents);
- eval(text);
-}
-
-// See also GetRecordReplayDispatchServer in ContentParent.cpp
-function getDispatchServer(url) {
- const address = env.get("RECORD_REPLAY_SERVER");
- if (address) {
- return address;
- }
- return Services.prefs.getStringPref("devtools.recordreplay.cloudServer");
-}
-
-function getRecordReplayPlatform() {
- switch (AppConstants.platform) {
- case "macosx":
- return "macOS";
- case "linux":
- return "linux";
- default:
- throw new Error(`Unrecognized platform ${AppConstants.platform}`);
- }
-}
-
-function DriverName() {
- return `${getRecordReplayPlatform()}-recordreplay.so`;
-}
-
-function DriverJSON() {
- return `${getRecordReplayPlatform()}-recordreplay.json`;
-}
-
-// See also SetupRecordReplayDriver in ContentParent.cpp
-function driverFile() {
- const file = Services.dirsvc.get("UAppData", Ci.nsIFile);
- file.append(DriverName());
- return file;
-}
-
-function driverJSONFile() {
- const file = Services.dirsvc.get("UAppData", Ci.nsIFile);
- file.append(DriverJSON());
- return file;
-}
-
-function crashLogFile() {
- const file = Services.dirsvc.get("UAppData", Ci.nsIFile);
- file.append("crashes.log");
- return file;
-}
-
-// Set the crash log file which the driver will use.
-env.set("RECORD_REPLAY_CRASH_LOG", crashLogFile().path);
-
-async function fetchURL(url) {
- const response = await fetch(url);
- if (response.status < 200 || response.status >= 300) {
- console.error("Error fetching URL", url, response);
- return null;
- }
- return response;
-}
-
-let gHasRecordingDriver;
-
-async function updateRecordingDriver() {
- try {
- fetch;
- } catch (e) {
- dump(`updateRecordingDriver: fetch() not in scope, waiting...\n`);
- setTimeout(updateRecordingDriver, 100);
- return;
- }
-
- // Don't update the driver if one was specified in the environment.
- if (env.get("RECORD_REPLAY_DRIVER")) {
- gHasRecordingDriver = true;
- return;
- }
-
- // Set the driver path for use in the recording process.
- env.set("RECORD_REPLAY_DRIVER", driverFile().path);
-
- let downloadURL;
- try {
- downloadURL = Services.prefs.getStringPref(
- "devtools.recordreplay.driverDownloads"
- );
- } catch (e) {
- downloadURL = "https://replay.io/downloads";
- }
-
- try {
- const driver = driverFile();
- const json = driverJSONFile();
-
- dump(`updateRecordingDriver Starting... [Driver ${driver.path}]\n`);
-
- if (driver.exists() && !gHasRecordingDriver) {
- gHasRecordingDriver = true;
- refreshAllRecordingButtons();
- }
-
- // If we have already downloaded the driver, redownload the server JSON
- // (much smaller than the driver itself) to see if anything has changed.
- if (json.exists() && driver.exists()) {
- const response = await fetchURL(`${downloadURL}/${DriverJSON()}`);
- if (!response) {
- dump(`updateRecordingDriver JSONFetchFailed\n`);
- return;
- }
- const serverJSON = JSON.parse(await response.text());
-
- const file = await OS.File.read(json.path);
- const currentJSON = JSON.parse(new TextDecoder("utf-8").decode(file));
-
- if (serverJSON.version == currentJSON.version) {
- // We've already downloaded the latest driver.
- dump(`updateRecordingDriver AlreadyOnLatestVersion\n`);
- return;
- }
- }
-
- const jsonResponse = await fetchURL(`${downloadURL}/${DriverJSON()}`);
- if (!jsonResponse) {
- dump(`updateRecordingDriver UpdateNeeded JSONFetchFailed\n`);
- return;
- }
- OS.File.writeAtomic(json.path, await jsonResponse.text());
-
- const driverResponse = await fetchURL(`${downloadURL}/${DriverName()}`);
- if (!driverResponse) {
- dump(`updateRecordingDriver UpdateNeeded DriverFetchFailed\n`);
- return;
- }
- OS.File.writeAtomic(driver.path, await driverResponse.arrayBuffer(), {
- // Write to a temporary path before renaming the result, so that any
- // recording processes hopefully won't try to load partial binaries
- // (they will keep trying if the load fails, though).
- tmpPath: driver.path + ".tmp",
-
- // Strip quarantine flag from the downloaded file. Even though this is
- // an update to the browser itself, macOS will still quarantine it and
- // prevent it from being loaded into recording processes.
- noQuarantine: true,
- });
-
- if (!gHasRecordingDriver) {
- gHasRecordingDriver = true;
- refreshAllRecordingButtons();
- }
-
- dump(`updateRecordingDriver Updated\n`);
- } catch (e) {
- dump(`updateRecordingDriver Exception ${e}\n`);
- }
-}
-
-// We check to see if there is a new recording driver every time the browser
-// starts up, and periodically after that.
-setTimeout(updateRecordingDriver, 0);
-setInterval(updateRecordingDriver, 1000 * 60 * 20);
-
-function reloadAndRecordTab(gBrowser) {
- let url = gBrowser.currentURI.spec;
-
- // Don't preprocess recordings if we will be submitting them for testing.
- try {
- if (
- Services.prefs.getBoolPref("devtools.recordreplay.submitTestRecordings")
- ) {
- env.set("RECORD_REPLAY_DONT_PROCESS_RECORDINGS", "1");
- }
- } catch (e) {}
-
- // The recording process uses this env var when printing out the recording ID.
- env.set("RECORD_REPLAY_URL", url);
-
- let remoteType = E10SUtils.getRemoteTypeForURI(
- url,
- /* aMultiProcess */ true,
- /* aRemoteSubframes */ false,
- /* aPreferredRemoteType */ undefined,
- /* aCurrentUri */ null
- );
- if (
- remoteType != E10SUtils.WEB_REMOTE_TYPE &&
- remoteType != E10SUtils.FILE_REMOTE_TYPE
- ) {
- url = "about:blank";
- remoteType = E10SUtils.WEB_REMOTE_TYPE;
- }
-
- gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, {
- recordExecution: getDispatchServer(url),
- newFrameloader: true,
- remoteType,
- });
-
- gBrowser.loadURI(url, {
- triggeringPrincipal: gBrowser.selectedBrowser.contentPrincipal,
- });
-}
-
-// Return whether all tabs are automatically being recorded.
-function isRecordingAllTabs() {
- return env.get("RECORD_ALL_CONTENT")
- || Services.prefs.getBoolPref("devtools.recordreplay.alwaysRecord");
-}
-
-function reloadAndStopRecordingTab(gBrowser) {
- const remoteTab = gBrowser.selectedTab.linkedBrowser.frameLoader.remoteTab;
- if (!remoteTab || !remoteTab.finishRecording()) {
- return;
- }
-
- recordReplayLog(`WaitForFinishedRecording`);
-}
-
-function getBrowserForPid(pid) {
- for (const window of Services.wm.getEnumerator("navigator:browser")) {
- for (const tab of window.gBrowser.tabs) {
- const { remoteTab } = tab.linkedBrowser.frameLoader || {};
- if (remoteTab && remoteTab.osPid === pid) {
- return tab.linkedBrowser;
- }
- }
- }
- throw new Error("Unable to find browser for recording");
-}
-
-function onRecordingStarted(recording) {
- const triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
- // There can occasionally be times when the browser isn't found when the
- // recording begins, so we lazily look it up the first time it is needed.
- let browser = null;
- function getBrowser() {
- // If the browser tab is moved to a new window, the cached browser object isn't
- // valid anymore so we need to find the new one.
- if (!browser || !browser.getTabBrowser()) {
- browser = getBrowserForPid(recording.osPid)
- }
- return browser;
- }
-
- let oldURL;
- let urlLoadOpts;
-
- gBrowserRecordingMap.set(getBrowser().selectedBrowser, recording)
-
- function clearRecordingState() {
- if (isRecordingAllTabs()) {
- return;
- }
- getBrowser().getTabBrowser().updateBrowserRemoteness(getBrowser(), {
- recordExecution: undefined,
- newFrameloader: true,
- remoteType: E10SUtils.WEB_REMOTE_TYPE,
- });
- }
- recording.on("unusable", function(name, data) {
- clearRecordingState();
-
- const { why } = data;
- getBrowser().loadURI(`about:replay?error=${why}`, { triggeringPrincipal });
- });
- recording.on("finished", function(name, data) {
- recordReplayLog(`FinishedRecording ${recordingId}`);
-
- oldURL = getBrowser().currentURI.spec;
- urlLoadOpts = { triggeringPrincipal, oldRecordedURL: oldURL };
-
- clearRecordingState();
- getBrowser().loadURI(oldURL, urlLoadOpts);
-
- const recordingId = data.id;
-
- // When the submitTestRecordings pref is set we don't load the viewer,
- // but show a simple page that the recording was submitted, to make things
- // simpler for QA and provide feedback that the pref was set correctly.
- if (
- Services.prefs.getBoolPref("devtools.recordreplay.submitTestRecordings")
- ) {
- fetch(`https://test-inbox.replay.io/${recordingId}:${oldURL}`);
- const why = `Test recording added: ${recordingId}`;
- getBrowser().loadURI(`about:replay?submitted=${why}`, urlLoadOpts);
- return;
- }
-
- recordReplayLog(`FinishedRecording ${recordingId}`);
- });
-}
-
-function openNewReplayTab(recordingId) {
- recordReplayLog(`OpenNewReplayTab ${recordingId}`);
-
- // Find the dispatcher to connect to.
- const dispatchAddress = getDispatchServer();
-
- let extra = "";
-
- // Specify the dispatch address if it is not the default.
- if (dispatchAddress != "wss://dispatch.replay.io") {
- extra += `&dispatch=${dispatchAddress}`;
- }
-
- // For testing, allow specifying a test script to load in the tab.
- const localTest = env.get("RECORD_REPLAY_LOCAL_TEST");
- if (localTest) {
- extra += `&test=${localTest}`;
- } else if (!isAuthenticationEnabled()) {
- // Adding this urlparam disables checks in the devtools that the user has
- // permission to view the recording.
- extra += `&test=1`;
- }
-
- const tabbrowser = getBrowser().getTabBrowser();
- const currentTabIndex = tabbrowser.visibleTabs.indexOf(tabbrowser.selectedTab);
- const tab = tabbrowser.addTab(
- `${getViewURL()}?id=${recordingId}${extra}`,
- { triggeringPrincipal, index: currentTabIndex === -1 ? undefined : currentTabIndex + 1}
- );
- tabbrowser.selectedTab = tab;
- recordReplayLog(`OpenNewReplayTab finished ${recordingId} `);
-}
-
-Services.obs.addObserver(
- subject => onRecordingStarted(subject.wrappedJSObject, data),
- "recordreplay-recording-started"
-);
-
-Services.ppmm.loadProcessScript("resource://devtools/server/actors/replay/globals.js", true);
-
-function getViewURL() {
- let viewHost = "https://replay.io";
-
- // For testing, allow overriding the host for the view page.
- const hostOverride = env.get("RECORD_REPLAY_VIEW_HOST");
- if (hostOverride) {
- viewHost = hostOverride;
- }
- return `${viewHost}/view`;
-}
-
-function viewRecordings(evt) {
- const { gBrowser } = evt.target.ownerDocument.defaultView;
- const triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
- gBrowser.loadURI(
- Services.prefs.getStringPref("devtools.recordreplay.recordingsUrl"),
- { triggeringPrincipal }
- );
-}
-
-function isAuthenticationEnabled() {
- // Authentication is controlled by a preference but can be disabled by an
- // environment variable.
- return (
- Services.prefs.getBoolPref(
- "devtools.recordreplay.authentication-enabled"
- ) && !env.get("RECORD_REPLAY_DISABLE_AUTHENTICATION")
- );
-}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment