summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/utils/pause/frames
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/client/debugger/src/utils/pause/frames
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
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.js68
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/collapseFrames.js58
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/displayName.js114
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/getLibraryFromUrl.js140
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/index.js8
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/moz.build14
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/tests/__snapshots__/collapseFrames.spec.js.snap87
-rw-r--r--devtools/client/debugger/src/utils/pause/frames/tests/collapseFrames.spec.js43
-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.js127
10 files changed, 788 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..3cdc4b1c1e
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/annotateFrames.js
@@ -0,0 +1,68 @@
+/* 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 { getLibraryFromUrl } from "./getLibraryFromUrl";
+
+/**
+ * Augment all frame objects with a 'library' attribute.
+ */
+export function annotateFramesWithLibrary(frames) {
+ for (const frame of frames) {
+ frame.library = getLibraryFromUrl(frame, frames);
+ }
+
+ // Babel need some special treatment to recognize some particular async stack pattern
+ for (const idx of getBabelFrameIndexes(frames)) {
+ const frame = frames[idx];
+ frame.library = "Babel";
+ }
+}
+
+/**
+ * Returns all the indexes that are part of a babel async call stack.
+ *
+ * @param {Array<Object>} frames
+ * @returns Array<Integer>
+ */
+function getBabelFrameIndexes(frames) {
+ const startIndexes = [];
+ const endIndexes = [];
+
+ for (let index = 0, length = frames.length; index < length; index++) {
+ const frame = frames[index];
+ const frameUrl = frame.location.source.url;
+
+ if (
+ frame.displayName === "tryCatch" &&
+ frameUrl.match(/regenerator-runtime/i)
+ ) {
+ startIndexes.push(index);
+ }
+
+ if (startIndexes.length > endIndexes.length) {
+ if (frame.displayName === "flush" && frameUrl.match(/_microtask/i)) {
+ endIndexes.push(index);
+ }
+ if (frame.displayName === "_asyncToGenerator/<") {
+ endIndexes.push(index + 1);
+ }
+ }
+ }
+
+ if (startIndexes.length != endIndexes.length || startIndexes.length === 0) {
+ return [];
+ }
+
+ const babelFrameIndexes = [];
+ // We have the same number of start and end indexes, we can loop through one of them to
+ // build our async call stack index ranges
+ // e.g. if we have startIndexes: [1,5] and endIndexes: [3,8], we want to return [1,2,3,5,6,7,8]
+ startIndexes.forEach((startIndex, index) => {
+ const matchingEndIndex = endIndexes[index];
+ for (let i = startIndex; i <= matchingEndIndex; i++) {
+ babelFrameIndexes.push(i);
+ }
+ });
+ return babelFrameIndexes;
+}
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..185b66e26c
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/collapseFrames.js
@@ -0,0 +1,58 @@
+/* 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/>. */
+
+function collapseLastFrames(frames) {
+ const index = frames.findIndex(frame =>
+ frame.location.source.url?.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 };
+}
+
+export function collapseFrames(frames) {
+ // 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..0a47e9ac04
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/displayName.js
@@ -0,0 +1,114 @@
+/* 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/>. */
+
+// 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]+)\(\^\)$/;
+const displayNameScenarios = [
+ objectProperty,
+ arrayProperty,
+ functionProperty,
+ annonymousProperty,
+];
+const includeSpace = /\s/;
+export function simplifyDisplayName(displayName) {
+ // if the display name has a space it has already been mapped
+ if (!displayName || includeSpace.exec(displayName)) {
+ return displayName;
+ }
+
+ for (const reg of displayNameScenarios) {
+ const match = reg.exec(displayName);
+ if (match) {
+ return match[1];
+ }
+ }
+
+ return displayName;
+}
+
+const displayNameLibraryMap = {
+ 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",
+ },
+};
+
+/**
+ * Compute the typical way to show a frame or function to the user.
+ *
+ * @param {Object} frameOrFunc
+ * Either a frame or a func object.
+ * Frame object is typically created via create.js::createFrame
+ * Func object comes from ast reducer and getSymbols selector.
+ * @param {Boolean} shouldMapDisplayName
+ * True by default, will try to translate internal framework function name
+ * into a most explicit and simplier name.
+ * @param {Object} l10n
+ * The localization object.
+ */
+export function formatDisplayName(
+ frameOrFunc,
+ { shouldMapDisplayName = true } = {},
+ l10n
+) {
+ // All the following attributes are only available on Frame objects
+ const { library, displayName, originalDisplayName } = frameOrFunc;
+ let displayedName;
+
+ // If the frame was identified to relate to a library,
+ // lookup for pretty name for the most important method of some frameworks
+ if (library && shouldMapDisplayName) {
+ displayedName = displayNameLibraryMap[library]?.[displayName];
+ }
+
+ // Frames for original sources may have both displayName for the generated source,
+ // or originalDisplayName for the original source.
+ // (in case original and generated have distinct function names in uglified sources)
+ //
+ // Also fallback to "name" attribute when the passed object is a Func object.
+ if (!displayedName) {
+ displayedName = originalDisplayName || displayName || frameOrFunc.name;
+ }
+
+ if (!displayedName) {
+ return l10n.getStr("anonymousFunction");
+ }
+
+ return simplifyDisplayName(displayedName);
+}
+
+export function formatCopyName(frame, l10n, shouldDisplayOriginalLocation) {
+ const displayName = formatDisplayName(frame, undefined, l10n);
+ const location = shouldDisplayOriginalLocation
+ ? frame.location
+ : frame.generatedLocation;
+ const fileName = location.source.url || location.source.id;
+ const frameLocation = frame.location.line;
+
+ return `${displayName} (${fileName}#${frameLocation})`;
+}
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..40a4d83841
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/getLibraryFromUrl.js
@@ -0,0 +1,140 @@
+/* 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/>. */
+
+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, callStack = []) {
+ const frameUrl = frame.location.source.url;
+
+ // 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 = f.location.source.url;
+ 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..8b232b3452
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/index.js
@@ -0,0 +1,8 @@
+/* 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/>. */
+
+export * from "./annotateFrames";
+export * from "./collapseFrames";
+export * from "./displayName";
+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..67462d40c3
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/moz.build
@@ -0,0 +1,14 @@
+# 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",
+ "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..a09cff5bee
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/tests/__snapshots__/collapseFrames.spec.js.snap
@@ -0,0 +1,87 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`collapseFrames default 1`] = `
+Array [
+ Object {
+ "displayName": "a",
+ "location": Object {
+ "source": Object {},
+ },
+ },
+ Array [
+ Object {
+ "displayName": "b",
+ "library": "React",
+ "location": Object {
+ "source": Object {},
+ },
+ },
+ Object {
+ "displayName": "c",
+ "library": "React",
+ "location": Object {
+ "source": Object {},
+ },
+ },
+ ],
+]
+`;
+
+exports[`collapseFrames promises 1`] = `
+Array [
+ Object {
+ "displayName": "a",
+ "location": Object {
+ "source": Object {},
+ },
+ },
+ Array [
+ Object {
+ "displayName": "b",
+ "library": "React",
+ "location": Object {
+ "source": Object {},
+ },
+ },
+ Object {
+ "displayName": "c",
+ "library": "React",
+ "location": Object {
+ "source": Object {},
+ },
+ },
+ ],
+ Object {
+ "asyncCause": "promise callback",
+ "displayName": "d",
+ "library": undefined,
+ "location": Object {
+ "source": Object {},
+ },
+ },
+ Array [
+ Object {
+ "displayName": "e",
+ "library": "React",
+ "location": Object {
+ "source": Object {},
+ },
+ },
+ Object {
+ "displayName": "f",
+ "library": "React",
+ "location": Object {
+ "source": Object {},
+ },
+ },
+ ],
+ Object {
+ "asyncCause": null,
+ "displayName": "g",
+ "library": undefined,
+ "location": Object {
+ "source": Object {},
+ },
+ },
+]
+`;
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..58cafc3211
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/tests/collapseFrames.spec.js
@@ -0,0 +1,43 @@
+/* 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", location: { source: {} } },
+
+ { displayName: "b", library: "React", location: { source: {} } },
+ { displayName: "c", library: "React", location: { source: {} } },
+ ]);
+
+ expect(groups).toMatchSnapshot();
+ });
+
+ it("promises", () => {
+ const groups = collapseFrames([
+ { displayName: "a", location: { source: {} } },
+
+ { displayName: "b", library: "React", location: { source: {} } },
+ { displayName: "c", library: "React", location: { source: {} } },
+ {
+ displayName: "d",
+ library: undefined,
+ asyncCause: "promise callback",
+ location: { source: {} },
+ },
+ { displayName: "e", library: "React", location: { source: {} } },
+ { displayName: "f", library: "React", location: { source: {} } },
+ {
+ displayName: "g",
+ library: undefined,
+ asyncCause: null,
+ location: { source: {} },
+ },
+ ]);
+
+ 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..d969c18753
--- /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/>. */
+
+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 };
+ 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"]],
+
+ privateMethod: [["#privateFunc", "#privateFunc"]],
+ };
+
+ 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..ff5be43285
--- /dev/null
+++ b/devtools/client/debugger/src/utils/pause/frames/tests/getLibraryFromUrl.spec.js
@@ -0,0 +1,127 @@
+/* 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 { 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");
+ });
+ });
+});