Created
May 27, 2021 16:30
-
-
Save jasonLaster/1e60f6cf21c96e6077c0e8398ef14359 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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