summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/utils/pause/frames
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /devtools/client/debugger/src/utils/pause/frames
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/debugger/src/utils/pause/frames')
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/annotateFrames.js80
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/collapseFrames.js69
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/displayName.js108
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/getFrameUrl.js11
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/getLibraryFromUrl.js149
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/index.js11
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/moz.build15
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/tests/__snapshots__/collapseFrames.spec.js.snap57
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/tests/annotateFrames.spec.js24
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/tests/collapseFrames.spec.js37
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/tests/displayName.spec.js129
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/tests/getLibraryFromUrl.spec.js129
12 files changed, 819 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/utils/pause/frames/annotateFrames.js b/devtools/client/debugger/src/utils/pause/frames/annotateFrames.js
new file mode 100644
index 0000000000..bd4114c5ab
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/annotateFrames.js
@@ -0,0 +1,80 @@
+/* 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/>. */
+
+// @flow
+
+import { flatMap, zip, range } from "lodash";
+
+import type { Frame } from "../../../types";
+import { getFrameUrl } from "./getFrameUrl";
+import { getLibraryFromUrl } from "./getLibraryFromUrl";
+
+type AnnotatedFrame =
+ | {|
+ ...Frame,
+ library: string,
+ |}
+ | Frame;
+
+export function annotateFrames(frames: Frame[]): AnnotatedFrame[] {
+ const annotatedFrames = frames.map(f => annotateFrame(f, frames));
+ return annotateBabelAsyncFrames(annotatedFrames);
+}
+
+function annotateFrame(frame: Frame, frames: Frame[]): AnnotatedFrame {
+ const library = getLibraryFromUrl(frame, frames);
+ if (library) {
+ return { ...frame, library };
+ }
+
+ return frame;
+}
+
+function annotateBabelAsyncFrames(frames: Frame[]): Frame[] {
+ const babelFrameIndexes = getBabelFrameIndexes(frames);
+ const isBabelFrame = frameIndex => babelFrameIndexes.includes(frameIndex);
+
+ return frames.map((frame, frameIndex) =>
+ isBabelFrame(frameIndex) ? { ...frame, library: "Babel" } : frame
+ );
+}
+
+// Receives an array of frames and looks for babel async
+// call stack groups.
+function getBabelFrameIndexes(frames) {
+ const startIndexes = frames.reduce((accumulator, frame, index) => {
+ if (
+ getFrameUrl(frame).match(/regenerator-runtime/i) &&
+ frame.displayName === "tryCatch"
+ ) {
+ return [...accumulator, index];
+ }
+ return accumulator;
+ }, []);
+
+ const endIndexes = frames.reduce((accumulator, frame, index) => {
+ if (
+ getFrameUrl(frame).match(/_microtask/i) &&
+ frame.displayName === "flush"
+ ) {
+ return [...accumulator, index];
+ }
+ if (frame.displayName === "_asyncToGenerator/<") {
+ return [...accumulator, index + 1];
+ }
+ return accumulator;
+ }, []);
+
+ if (startIndexes.length != endIndexes.length || startIndexes.length === 0) {
+ return frames;
+ }
+
+ // Receives an array of start and end index tuples and returns
+ // an array of async call stack index ranges.
+ // e.g. [[1,3], [5,7]] => [[1,2,3], [5,6,7]]
+ // $FlowIgnore
+ return flatMap(zip(startIndexes, endIndexes), ([startIndex, endIndex]) =>
+ range(startIndex, endIndex + 1)
+ );
+}
diff --git a/devtools/client/debugger/src/utils/pause/frames/collapseFrames.js b/devtools/client/debugger/src/utils/pause/frames/collapseFrames.js
new file mode 100644
index 0000000000..d39a26d574
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/collapseFrames.js
@@ -0,0 +1,69 @@
+/* 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/>. */
+
+// @flow
+
+import { findIndex } from "lodash";
+
+// eslint-disable-next-line max-len
+import type { Frame } from "../../../types";
+import { getFrameUrl } from "./getFrameUrl";
+
+function collapseLastFrames(frames) {
+ const index = findIndex(frames, frame =>
+ getFrameUrl(frame).match(/webpack\/bootstrap/i)
+ );
+
+ if (index == -1) {
+ return { newFrames: frames, lastGroup: [] };
+ }
+
+ const newFrames = frames.slice(0, index);
+ const lastGroup = frames.slice(index);
+ return { newFrames, lastGroup };
+}
+
+type FrameGroup = Frame[];
+type GroupedFrames = Array<FrameGroup | Frame>;
+
+export function collapseFrames(frames: Frame[]): GroupedFrames {
+ // We collapse groups of one so that user frames
+ // are not in a group of one
+ function addGroupToList(group, list) {
+ if (!group) {
+ return list;
+ }
+
+ if (group.length > 1) {
+ list.push(group);
+ } else {
+ list = list.concat(group);
+ }
+
+ return list;
+ }
+ const { newFrames, lastGroup } = collapseLastFrames(frames);
+ frames = newFrames;
+ let items = [];
+ let currentGroup = null;
+ let prevItem = null;
+ for (const frame of frames) {
+ const prevLibrary = prevItem?.library;
+
+ if (!currentGroup) {
+ currentGroup = [frame];
+ } else if (prevLibrary && prevLibrary == frame.library) {
+ currentGroup.push(frame);
+ } else {
+ items = addGroupToList(currentGroup, items);
+ currentGroup = [frame];
+ }
+
+ prevItem = frame;
+ }
+
+ items = addGroupToList(currentGroup, items);
+ items = addGroupToList(lastGroup, items);
+ return items;
+}
diff --git a/devtools/client/debugger/src/utils/pause/frames/displayName.js b/devtools/client/debugger/src/utils/pause/frames/displayName.js
new file mode 100644
index 0000000000..dbd53d53cd
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/displayName.js
@@ -0,0 +1,108 @@
+/* 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/>. */
+
+// @flow
+
+// eslint-disable-next-line max-len
+import type { Frame } from "../../../types";
+
+// Decodes an anonymous naming scheme that
+// spider monkey implements based on "Naming Anonymous JavaScript Functions"
+// http://johnjbarton.github.io/nonymous/index.html
+const objectProperty = /([\w\d\$]+)$/;
+const arrayProperty = /\[(.*?)\]$/;
+const functionProperty = /([\w\d]+)[\/\.<]*?$/;
+const annonymousProperty = /([\w\d]+)\(\^\)$/;
+
+export function simplifyDisplayName(displayName: string | void): string | void {
+ // if the display name has a space it has already been mapped
+ if (!displayName || /\s/.exec(displayName)) {
+ return displayName;
+ }
+
+ const scenarios = [
+ objectProperty,
+ arrayProperty,
+ functionProperty,
+ annonymousProperty,
+ ];
+
+ for (const reg of scenarios) {
+ const match = reg.exec(displayName);
+ if (match) {
+ return match[1];
+ }
+ }
+
+ return displayName;
+}
+
+const displayNameMap = {
+ Babel: {
+ tryCatch: "Async",
+ },
+ Backbone: {
+ "extend/child": "Create Class",
+ ".create": "Create Model",
+ },
+ jQuery: {
+ "jQuery.event.dispatch": "Dispatch Event",
+ },
+ React: {
+ // eslint-disable-next-line max-len
+ "ReactCompositeComponent._renderValidatedComponentWithoutOwnerOrContext/renderedElement<":
+ "Render",
+ _renderValidatedComponentWithoutOwnerOrContext: "Render",
+ },
+ VueJS: {
+ "renderMixin/Vue.prototype._render": "Render",
+ },
+ Webpack: {
+ // eslint-disable-next-line camelcase
+ __webpack_require__: "Bootstrap",
+ },
+};
+
+function mapDisplayNames(frame, library) {
+ const { displayName } = frame;
+ return displayNameMap[library]?.[displayName] || displayName;
+}
+
+function getFrameDisplayName(frame: Frame): string {
+ const {
+ displayName,
+ originalDisplayName,
+ userDisplayName,
+ name,
+ } = (frame: any);
+ return originalDisplayName || userDisplayName || displayName || name;
+}
+
+type formatDisplayNameParams = {
+ shouldMapDisplayName: boolean,
+};
+export function formatDisplayName(
+ frame: Frame,
+ { shouldMapDisplayName = true }: formatDisplayNameParams = {},
+ l10n: typeof L10N
+): string {
+ const { library } = frame;
+ let displayName = getFrameDisplayName(frame);
+ if (library && shouldMapDisplayName) {
+ displayName = mapDisplayNames(frame, library);
+ }
+
+ return simplifyDisplayName(displayName) || l10n.getStr("anonymousFunction");
+}
+
+export function formatCopyName(frame: Frame, l10n: typeof L10N): string {
+ const displayName = formatDisplayName(frame, undefined, l10n);
+ if (!frame.source) {
+ throw new Error("no frame source");
+ }
+ const fileName = frame.source.url || frame.source.id;
+ const frameLocation = frame.location.line;
+
+ return `${displayName} (${fileName}#${frameLocation})`;
+}
diff --git a/devtools/client/debugger/src/utils/pause/frames/getFrameUrl.js b/devtools/client/debugger/src/utils/pause/frames/getFrameUrl.js
new file mode 100644
index 0000000000..6a151e14f7
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/getFrameUrl.js
@@ -0,0 +1,11 @@
+/* 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/>. */
+
+// @flow
+
+import type { Frame } from "../../../types";
+
+export function getFrameUrl(frame: Frame) {
+ return frame?.source?.url ?? "";
+}
diff --git a/devtools/client/debugger/src/utils/pause/frames/getLibraryFromUrl.js b/devtools/client/debugger/src/utils/pause/frames/getLibraryFromUrl.js
new file mode 100644
index 0000000000..232079e3ec
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/getLibraryFromUrl.js
@@ -0,0 +1,149 @@
+/* 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/>. */
+
+// @flow
+
+import type { Frame } from "../../../types";
+import { getFrameUrl } from "./getFrameUrl";
+
+const libraryMap = [
+ {
+ label: "Backbone",
+ pattern: /backbone/i,
+ },
+ {
+ label: "Babel",
+ pattern: /node_modules\/@babel/i,
+ },
+ {
+ label: "jQuery",
+ pattern: /jquery/i,
+ },
+ {
+ label: "Preact",
+ pattern: /preact/i,
+ },
+ {
+ label: "React",
+ pattern: /(node_modules\/(?:react(-dom)?(-dev)?\/))|(react(-dom)?(-dev)?(\.[a-z]+)*\.js$)/,
+ },
+ {
+ label: "Immutable",
+ pattern: /immutable/i,
+ },
+ {
+ label: "Webpack",
+ pattern: /webpack\/bootstrap/i,
+ },
+ {
+ label: "Express",
+ pattern: /node_modules\/express/,
+ },
+ {
+ label: "Pug",
+ pattern: /node_modules\/pug/,
+ },
+ {
+ label: "ExtJS",
+ pattern: /\/ext-all[\.\-]/,
+ },
+ {
+ label: "MobX",
+ pattern: /mobx/i,
+ },
+ {
+ label: "Underscore",
+ pattern: /underscore/i,
+ },
+ {
+ label: "Lodash",
+ pattern: /lodash/i,
+ },
+ {
+ label: "Ember",
+ pattern: /ember/i,
+ },
+ {
+ label: "Choo",
+ pattern: /choo/i,
+ },
+ {
+ label: "VueJS",
+ pattern: /vue(?:\.[a-z]+)*\.js/i,
+ },
+ {
+ label: "RxJS",
+ pattern: /rxjs/i,
+ },
+ {
+ label: "Angular",
+ pattern: /angular(?!.*\/app\/)/i,
+ contextPattern: /zone\.js/,
+ },
+ {
+ label: "Redux",
+ pattern: /redux/i,
+ },
+ {
+ label: "Dojo",
+ pattern: /dojo/i,
+ },
+ {
+ label: "Marko",
+ pattern: /marko/i,
+ },
+ {
+ label: "NuxtJS",
+ pattern: /[\._]nuxt/i,
+ },
+ {
+ label: "Aframe",
+ pattern: /aframe/i,
+ },
+ {
+ label: "NextJS",
+ pattern: /[\._]next/i,
+ },
+];
+
+export function getLibraryFromUrl(
+ frame: Frame,
+ callStack: Array<Frame> = []
+): ?string | void {
+ // @TODO each of these fns calls getFrameUrl, just call it once
+ // (assuming there's not more complex logic to identify a lib)
+ const frameUrl = getFrameUrl(frame);
+
+ // Let's first check if the frame match a defined pattern.
+ let match = libraryMap.find(o => o.pattern.test(frameUrl));
+ if (match) {
+ return match.label;
+ }
+
+ // If it does not, it might still be one of the case where the file is used
+ // by a library but the name has not enough specificity. In such case, we want
+ // to only return the library name if there are frames matching the library
+ // pattern in the callStack (e.g. `zone.js` is used by Angular, but the name
+ // could be quite common and return false positive if evaluated alone. So we
+ // only return Angular if there are other frames matching Angular).
+ match = libraryMap.find(
+ o => o.contextPattern && o.contextPattern.test(frameUrl)
+ );
+ if (match) {
+ const contextMatch = callStack.some(f => {
+ const url = getFrameUrl(f);
+ if (!url) {
+ return false;
+ }
+
+ return libraryMap.some(o => o.pattern.test(url));
+ });
+
+ if (contextMatch) {
+ return match.label;
+ }
+ }
+
+ return null;
+}
diff --git a/devtools/client/debugger/src/utils/pause/frames/index.js b/devtools/client/debugger/src/utils/pause/frames/index.js
new file mode 100644
index 0000000000..68d214162c
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/index.js
@@ -0,0 +1,11 @@
+/* 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/>. */
+
+// @flow
+
+export * from "./annotateFrames";
+export * from "./collapseFrames";
+export * from "./displayName";
+export * from "./getFrameUrl";
+export * from "./getLibraryFromUrl";
diff --git a/devtools/client/debugger/src/utils/pause/frames/moz.build b/devtools/client/debugger/src/utils/pause/frames/moz.build
new file mode 100644
index 0000000000..5bb330a57f
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/moz.build
@@ -0,0 +1,15 @@
+# vim: set filetype=python:
+# 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/.
+
+DIRS += []
+
+CompiledModules(
+ "annotateFrames.js",
+ "collapseFrames.js",
+ "displayName.js",
+ "getFrameUrl.js",
+ "getLibraryFromUrl.js",
+ "index.js",
+)
diff --git a/devtools/client/debugger/src/utils/pause/frames/tests/__snapshots__/collapseFrames.spec.js.snap b/devtools/client/debugger/src/utils/pause/frames/tests/__snapshots__/collapseFrames.spec.js.snap
new file mode 100644
index 0000000000..89dbccd374
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/tests/__snapshots__/collapseFrames.spec.js.snap
@@ -0,0 +1,57 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`collapseFrames default 1`] = `
+Array [
+ Object {
+ "displayName": "a",
+ },
+ Array [
+ Object {
+ "displayName": "b",
+ "library": "React",
+ },
+ Object {
+ "displayName": "c",
+ "library": "React",
+ },
+ ],
+]
+`;
+
+exports[`collapseFrames promises 1`] = `
+Array [
+ Object {
+ "displayName": "a",
+ },
+ Array [
+ Object {
+ "displayName": "b",
+ "library": "React",
+ },
+ Object {
+ "displayName": "c",
+ "library": "React",
+ },
+ ],
+ Object {
+ "asyncCause": "promise callback",
+ "displayName": "d",
+ "library": undefined,
+ },
+ Array [
+ Object {
+ "displayName": "e",
+ "library": "React",
+ },
+ Object {
+ "displayName": "f",
+ "library": "React",
+ },
+ ],
+ Object {
+ "asyncCause": null,
+ "displayName": "g",
+ "library": undefined,
+ },
+]
+`;
diff --git a/devtools/client/debugger/src/utils/pause/frames/tests/annotateFrames.spec.js b/devtools/client/debugger/src/utils/pause/frames/tests/annotateFrames.spec.js
new file mode 100644
index 0000000000..752f470e2f
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/tests/annotateFrames.spec.js
@@ -0,0 +1,24 @@
+/* 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/>. */
+
+// @flow
+
+import { annotateFrames } from "../annotateFrames";
+import { makeMockFrameWithURL } from "../../../test-mockup";
+
+describe("annotateFrames", () => {
+ it("should return Angular", () => {
+ const callstack = [
+ makeMockFrameWithURL(
+ "https://stackblitz.io/turbo_modules/@angular/core@7.2.4/bundles/core.umd.js"
+ ),
+ makeMockFrameWithURL("/node_modules/zone/zone.js"),
+ makeMockFrameWithURL(
+ "https://cdnjs.cloudflare.com/ajax/libs/angular/angular.js"
+ ),
+ ];
+ const frames = annotateFrames(callstack);
+ expect(frames).toEqual(callstack.map(f => ({ ...f, library: "Angular" })));
+ });
+});
diff --git a/devtools/client/debugger/src/utils/pause/frames/tests/collapseFrames.spec.js b/devtools/client/debugger/src/utils/pause/frames/tests/collapseFrames.spec.js
new file mode 100644
index 0000000000..15210e0437
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/tests/collapseFrames.spec.js
@@ -0,0 +1,37 @@
+/* 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/>. */
+
+import { collapseFrames } from "../collapseFrames";
+
+describe("collapseFrames", () => {
+ it("default", () => {
+ const groups = collapseFrames([
+ { displayName: "a" },
+
+ { displayName: "b", library: "React" },
+ { displayName: "c", library: "React" },
+ ]);
+
+ expect(groups).toMatchSnapshot();
+ });
+
+ it("promises", () => {
+ const groups = collapseFrames([
+ { displayName: "a" },
+
+ { displayName: "b", library: "React" },
+ { displayName: "c", library: "React" },
+ {
+ displayName: "d",
+ library: undefined,
+ asyncCause: "promise callback",
+ },
+ { displayName: "e", library: "React" },
+ { displayName: "f", library: "React" },
+ { displayName: "g", library: undefined, asyncCause: null },
+ ]);
+
+ expect(groups).toMatchSnapshot();
+ });
+});
diff --git a/devtools/client/debugger/src/utils/pause/frames/tests/displayName.spec.js b/devtools/client/debugger/src/utils/pause/frames/tests/displayName.spec.js
new file mode 100644
index 0000000000..a362885e1d
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/tests/displayName.spec.js
@@ -0,0 +1,129 @@
+/* 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/>. */
+
+// @flow
+
+import {
+ formatCopyName,
+ formatDisplayName,
+ simplifyDisplayName,
+} from "../displayName";
+
+import { makeMockFrame, makeMockSource } from "../../../test-mockup";
+
+describe("formatCopyName", () => {
+ it("simple", () => {
+ const source = makeMockSource("todo-view.js");
+ const frame = makeMockFrame(undefined, source, undefined, 12, "child");
+
+ expect(formatCopyName(frame, L10N)).toEqual("child (todo-view.js#12)");
+ });
+});
+
+describe("formatting display names", () => {
+ it("uses a library description", () => {
+ const source = makeMockSource("assets/backbone.js");
+ const frame = {
+ ...makeMockFrame(undefined, source, undefined, undefined, "extend/child"),
+ library: "Backbone",
+ };
+
+ expect(formatDisplayName(frame, undefined, L10N)).toEqual("Create Class");
+ });
+
+ it("shortens an anonymous function", () => {
+ const source = makeMockSource("assets/bar.js");
+ const frame = makeMockFrame(
+ undefined,
+ source,
+ undefined,
+ undefined,
+ "extend/child/bar/baz"
+ );
+
+ expect(formatDisplayName(frame, undefined, L10N)).toEqual("baz");
+ });
+
+ it("does not truncates long function names", () => {
+ const source = makeMockSource("extend/child/bar/baz");
+ const frame = makeMockFrame(
+ undefined,
+ source,
+ undefined,
+ undefined,
+ "bazbazbazbazbazbazbazbazbazbazbazbazbaz"
+ );
+
+ expect(formatDisplayName(frame, undefined, L10N)).toEqual(
+ "bazbazbazbazbazbazbazbazbazbazbazbazbaz"
+ );
+ });
+
+ it("returns the original function name when present", () => {
+ const source = makeMockSource("entry.js");
+ const frame = {
+ ...makeMockFrame(undefined, source),
+ originalDisplayName: "originalFn",
+ displayName: "fn",
+ };
+
+ expect(formatDisplayName(frame, undefined, L10N)).toEqual("originalFn");
+ });
+
+ it("returns anonymous when displayName is undefined", () => {
+ const frame = { ...makeMockFrame(), displayName: undefined };
+ expect(formatDisplayName(frame, undefined, L10N)).toEqual("<anonymous>");
+ });
+
+ it("returns anonymous when displayName is null", () => {
+ const frame = { ...makeMockFrame(), displayName: (null: any) };
+ expect(formatDisplayName(frame, undefined, L10N)).toEqual("<anonymous>");
+ });
+
+ it("returns anonymous when displayName is an empty string", () => {
+ const frame = { ...makeMockFrame(), displayName: "" };
+ expect(formatDisplayName(frame, undefined, L10N)).toEqual("<anonymous>");
+ });
+});
+
+describe("simplifying display names", () => {
+ const cases = {
+ defaultCase: [["define", "define"]],
+
+ objectProperty: [
+ ["z.foz", "foz"],
+ ["z.foz/baz", "baz"],
+ ["z.foz/baz/y.bay", "bay"],
+ ["outer/x.fox.bax.nx", "nx"],
+ ["outer/fow.baw", "baw"],
+ ["fromYUI._attach", "_attach"],
+ ["Y.ClassNameManager</getClassName", "getClassName"],
+ ["orion.textview.TextView</addHandler", "addHandler"],
+ ["this.eventPool_.createObject", "createObject"],
+ ],
+
+ arrayProperty: [
+ ["this.eventPool_[createObject]", "createObject"],
+ ["jQuery.each(^)/jQuery.fn[o]", "o"],
+ ["viewport[get+D]", "get+D"],
+ ["arr[0]", "0"],
+ ],
+
+ functionProperty: [
+ ["fromYUI._attach/<.", "_attach"],
+ ["Y.ClassNameManager<", "ClassNameManager"],
+ ["fromExtJS.setVisible/cb<", "cb"],
+ ["fromDojo.registerWin/<", "registerWin"],
+ ],
+
+ annonymousProperty: [["jQuery.each(^)", "each"]],
+ };
+
+ Object.keys(cases).forEach(type => {
+ cases[type].forEach(([kase, expected]) => {
+ it(`${type} - ${kase}`, () =>
+ expect(simplifyDisplayName(kase)).toEqual(expected));
+ });
+ });
+});
diff --git a/devtools/client/debugger/src/utils/pause/frames/tests/getLibraryFromUrl.spec.js b/devtools/client/debugger/src/utils/pause/frames/tests/getLibraryFromUrl.spec.js
new file mode 100644
index 0000000000..afb8d7afc2
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/tests/getLibraryFromUrl.spec.js
@@ -0,0 +1,129 @@
+/* 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/>. */
+
+// @flow
+
+import { getLibraryFromUrl } from "../getLibraryFromUrl";
+import { makeMockFrameWithURL } from "../../../test-mockup";
+
+describe("getLibraryFromUrl", () => {
+ describe("When Preact is on the frame", () => {
+ it("should return Preact and not React", () => {
+ const frame = makeMockFrameWithURL(
+ "https://cdnjs.cloudflare.com/ajax/libs/preact/8.2.5/preact.js"
+ );
+ expect(getLibraryFromUrl(frame)).toEqual("Preact");
+ });
+ });
+
+ describe("When Vue is on the frame", () => {
+ it("should return VueJS for different builds", () => {
+ const buildTypeList = [
+ "vue.js",
+ "vue.common.js",
+ "vue.esm.js",
+ "vue.runtime.js",
+ "vue.runtime.common.js",
+ "vue.runtime.esm.js",
+ "vue.min.js",
+ "vue.runtime.min.js",
+ ];
+
+ buildTypeList.forEach(buildType => {
+ const frame = makeMockFrameWithURL(
+ `https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/${buildType}`
+ );
+ expect(getLibraryFromUrl(frame)).toEqual("VueJS");
+ });
+ });
+ });
+
+ describe("When React is in the URL", () => {
+ it("should not return React if it is not part of the filename", () => {
+ const notReactUrlList = [
+ "https://react.js.com/test.js",
+ "https://debugger-example.com/test.js",
+ "https://debugger-react-example.com/test.js",
+ "https://debugger-react-example.com/react/test.js",
+ "https://debugger-example.com/react-contextmenu.js",
+ ];
+ notReactUrlList.forEach(notReactUrl => {
+ const frame = makeMockFrameWithURL(notReactUrl);
+ expect(getLibraryFromUrl(frame)).toBeNull();
+ });
+ });
+ it("should return React if it is part of the filename", () => {
+ const reactUrlList = [
+ "https://debugger-example.com/react.js",
+ "https://debugger-example.com/react.development.js",
+ "https://debugger-example.com/react.production.min.js",
+ "https://debugger-react-example.com/react.js",
+ "https://debugger-react-example.com/react/react.js",
+ "https://debugger-example.com/react-dom.js",
+ "https://debugger-example.com/react-dom.development.js",
+ "https://debugger-example.com/react-dom.production.min.js",
+ "https://debugger-react-example.com/react-dom.js",
+ "https://debugger-react-example.com/react/react-dom.js",
+ "https://debugger-react-example.com/react-dom-dev.js",
+ "/node_modules/react/test.js",
+ "/node_modules/react-dev/test.js",
+ "/node_modules/react-dom/test.js",
+ "/node_modules/react-dom-dev/test.js",
+ ];
+ reactUrlList.forEach(reactUrl => {
+ const frame = makeMockFrameWithURL(reactUrl);
+ expect(getLibraryFromUrl(frame)).toEqual("React");
+ });
+ });
+ });
+
+ describe("When Angular is in the URL", () => {
+ it("should return Angular for AngularJS (1.x)", () => {
+ const frame = makeMockFrameWithURL(
+ "https://cdnjs.cloudflare.com/ajax/libs/angular/angular.js"
+ );
+ expect(getLibraryFromUrl(frame)).toEqual("Angular");
+ });
+
+ it("should return Angular for Angular (2.x)", () => {
+ const frame = makeMockFrameWithURL(
+ "https://stackblitz.io/turbo_modules/@angular/core@7.2.4/bundles/core.umd.js"
+ );
+ expect(getLibraryFromUrl(frame)).toEqual("Angular");
+ });
+
+ it("should not return Angular for Angular components", () => {
+ const frame = makeMockFrameWithURL(
+ "https://firefox-devtools-angular-log.stackblitz.io/~/src/app/hello.component.ts"
+ );
+ expect(getLibraryFromUrl(frame)).toBeNull();
+ });
+ });
+
+ describe("When zone.js is on the frame", () => {
+ it("should not return Angular when no callstack", () => {
+ const frame = makeMockFrameWithURL("/node_modules/zone/zone.js");
+ expect(getLibraryFromUrl(frame)).toBeNull();
+ });
+
+ it("should not return Angular when stack without Angular frames", () => {
+ const frame = makeMockFrameWithURL("/node_modules/zone/zone.js");
+ const callstack = [frame];
+
+ expect(getLibraryFromUrl(frame, callstack)).toBeNull();
+ });
+
+ it("should return Angular when stack with AngularJS (1.x) frames", () => {
+ const frame = makeMockFrameWithURL("/node_modules/zone/zone.js");
+ const callstack = [
+ frame,
+ makeMockFrameWithURL(
+ "https://cdnjs.cloudflare.com/ajax/libs/angular/angular.js"
+ ),
+ ];
+
+ expect(getLibraryFromUrl(frame, callstack)).toEqual("Angular");
+ });
+ });
+});