Skip to content

Commit

Permalink
Add some console utilities to retrieve the current profile and save i…
Browse files Browse the repository at this point in the history
…t to disk (#5105)
  • Loading branch information
julienw authored Aug 30, 2024
2 parents ac4ae1f + 6a01689 commit 6d19e07
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 29 deletions.
62 changes: 35 additions & 27 deletions src/actions/receive-profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1000,39 +1000,47 @@ export async function doSymbolicateProfile(
dispatch(doneSymbolicating());
}

// From a BrowserConnectionStatus, this unwraps the included browserConnection
// when possible.
export function unwrapBrowserConnection(
browserConnectionStatus: BrowserConnectionStatus
): BrowserConnection {
switch (browserConnectionStatus.status) {
case 'ESTABLISHED':
// Good. This is the normal case.
break;
// The other cases are error cases.
case 'NOT_FIREFOX':
throw new Error('/from-browser only works in Firefox browsers');
case 'NO_ATTEMPT':
throw new Error(
'retrieveProfileFromBrowser should never be called while browserConnectionStatus is NO_ATTEMPT'
);
case 'WAITING':
throw new Error(
'retrieveProfileFromBrowser should never be called while browserConnectionStatus is WAITING'
);
case 'DENIED':
throw browserConnectionStatus.error;
case 'TIMED_OUT':
throw new Error('Timed out when waiting for reply to WebChannel message');
default:
throw assertExhaustiveCheck(browserConnectionStatus.status);
}

// Now we know that browserConnectionStatus.status === 'ESTABLISHED'.
return browserConnectionStatus.browserConnection;
}

export function retrieveProfileFromBrowser(
browserConnectionStatus: BrowserConnectionStatus,
initialLoad: boolean = false
): ThunkAction<Promise<void>> {
return async (dispatch) => {
try {
switch (browserConnectionStatus.status) {
case 'ESTABLISHED':
// Good. This is the normal case.
break;
// The other cases are error cases.
case 'NOT_FIREFOX':
throw new Error('/from-browser only works in Firefox browsers');
case 'NO_ATTEMPT':
throw new Error(
'retrieveProfileFromBrowser should never be called while browserConnectionStatus is NO_ATTEMPT'
);
case 'WAITING':
throw new Error(
'retrieveProfileFromBrowser should never be called while browserConnectionStatus is WAITING'
);
case 'DENIED':
throw browserConnectionStatus.error;
case 'TIMED_OUT':
throw new Error(
'Timed out when waiting for reply to WebChannel message'
);
default:
throw assertExhaustiveCheck(browserConnectionStatus.status);
}

// Now we know that browserConnectionStatus.status === 'ESTABLISHED'.
const browserConnection = browserConnectionStatus.browserConnection;
const browserConnection = unwrapBrowserConnection(
browserConnectionStatus
);

// XXX update state to show that we're connected to the browser

Expand Down
2 changes: 1 addition & 1 deletion src/components/app/UrlManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class UrlManagerImpl extends React.PureComponent<Props> {
)
) {
updateBrowserConnectionStatus({ status: 'WAITING' });
browserConnectionStatus = await createBrowserConnection('Firefox/123.0');
browserConnectionStatus = await createBrowserConnection();
updateBrowserConnectionStatus(browserConnectionStatus);
}

Expand Down
5 changes: 5 additions & 0 deletions src/test/components/UrlManager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ describe('UrlManager', function () {
}

function setup(urlPath: ?string) {
jest
.spyOn(navigator, 'userAgent', 'get')
.mockReturnValue(
'Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0'
);
if (typeof urlPath === 'string') {
window.location.replace(`http://localhost${urlPath}`);
}
Expand Down
2 changes: 2 additions & 0 deletions src/test/unit/__snapshots__/window-console.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Array [
%cwindow.experimental%c - The object that holds flags of all the experimental features.
%cwindow.togglePseudoLocalization%c - Enable pseudo localizations by passing \\"accented\\" or \\"bidi\\" to this function, or disable using no parameters.
%cwindow.toggleTimelineType%c - Toggle timeline graph type by passing \\"cpu-category\\", \\"category\\", or \\"stack\\".
%cwindow.retrieveRawProfileDataFromBrowser%c - Retrieve the profile attached to the current tab and returns it. Use \\"await\\" to call it.
%cwindow.saveToDisk%c - Saves to a file the parameter passed to it, with an optional filename parameter. You can use that to save the profile returned by \\"retrieveRawProfileDataFromBrowser\\".
The profile format is documented here:
%chttps://github.com/firefox-devtools/profiler/blob/main/docs-developer/processed-profile-format.md%c
Expand Down
59 changes: 58 additions & 1 deletion src/utils/window-console.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
* 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/. */
// @flow
import { stripIndent } from 'common-tags';
import { stripIndent, oneLine } from 'common-tags';
import type { GetState, Dispatch, MixedObject } from 'firefox-profiler/types';
import { selectorsForConsole } from 'firefox-profiler/selectors';
import actions from 'firefox-profiler/actions';
import { shortenUrl } from 'firefox-profiler/utils/shorten-url';
import { createBrowserConnection } from 'firefox-profiler/app-logic/browser-connection';

// Despite providing a good libdef for Object.defineProperty, Flow still
// special-cases the `value` property: if it's missing it throws an error. Using
Expand Down Expand Up @@ -148,6 +149,60 @@ export function addDataToWindowObject(
`);
};

target.retrieveRawProfileDataFromBrowser = async function (): Promise<
MixedObject | ArrayBuffer | null,
> {
// Note that a new connection is created instead of reusing the one in the
// redux state, as an attempt to make it work even in the worst situations.
const browserConnectionStatus = await createBrowserConnection();
const browserConnection = actions.unwrapBrowserConnection(
browserConnectionStatus
);
const rawGeckoProfile = await browserConnection.getProfile({
onThirtySecondTimeout: () => {
console.log(
oneLine`
We were unable to connect to the browser within thirty seconds.
This might be because the profile is big or your machine is slower than usual.
Still waiting...
`
);
},
});

return rawGeckoProfile;
};

target.saveToDisk = async function (
unknownObject: ArrayBuffer | mixed,
filename?: string
) {
if (unknownObject === undefined || unknownObject === null) {
console.error("We can't save a null or undefined variable.");
return;
}

const arrayBufferOrString =
typeof unknownObject === 'string' ||
String(unknownObject) === '[object ArrayBuffer]'
? unknownObject
: JSON.stringify(unknownObject);

const blob = new Blob([arrayBufferOrString], {
type: 'application/octet-stream',
});
const blobUrl = URL.createObjectURL(blob);

// Trigger the download programmatically
const downloadLink = document.createElement('a');
downloadLink.href = blobUrl;
downloadLink.download = filename ?? 'profile.data';
downloadLink.click();

// Clean up the URL object
URL.revokeObjectURL(blobUrl);
};

target.shortenUrl = shortenUrl;
target.getState = getState;
target.selectors = selectorsForConsole;
Expand Down Expand Up @@ -206,6 +261,8 @@ export function logFriendlyPreamble() {
%cwindow.experimental%c - The object that holds flags of all the experimental features.
%cwindow.togglePseudoLocalization%c - Enable pseudo localizations by passing "accented" or "bidi" to this function, or disable using no parameters.
%cwindow.toggleTimelineType%c - Toggle timeline graph type by passing "cpu-category", "category", or "stack".
%cwindow.retrieveRawProfileDataFromBrowser%c - Retrieve the profile attached to the current tab and returns it. Use "await" to call it.
%cwindow.saveToDisk%c - Saves to a file the parameter passed to it, with an optional filename parameter. You can use that to save the profile returned by "retrieveRawProfileDataFromBrowser".
The profile format is documented here:
%chttps://github.com/firefox-devtools/profiler/blob/main/docs-developer/processed-profile-format.md%c
Expand Down

0 comments on commit 6d19e07

Please sign in to comment.