summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/test/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/netmonitor/test/head.js')
-rw-r--r--devtools/client/netmonitor/test/head.js518
1 files changed, 518 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/test/head.js b/devtools/client/netmonitor/test/head.js
new file mode 100644
index 000000000..d733cc1d4
--- /dev/null
+++ b/devtools/client/netmonitor/test/head.js
@@ -0,0 +1,518 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/* import-globals-from ../../framework/test/shared-head.js */
+
+// shared-head.js handles imports, constants, and utility functions
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
+ this);
+
+var NetworkHelper = require("devtools/shared/webconsole/network-helper");
+var { Toolbox } = require("devtools/client/framework/toolbox");
+
+const EXAMPLE_URL = "http://example.com/browser/devtools/client/netmonitor/test/";
+const HTTPS_EXAMPLE_URL = "https://example.com/browser/devtools/client/netmonitor/test/";
+
+const API_CALLS_URL = EXAMPLE_URL + "html_api-calls-test-page.html";
+const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html";
+const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html";
+const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-test-page.html";
+const CONTENT_TYPE_WITHOUT_CACHE_URL = EXAMPLE_URL + "html_content-type-without-cache-test-page.html";
+const CONTENT_TYPE_WITHOUT_CACHE_REQUESTS = 8;
+const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html";
+const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html";
+const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html";
+const POST_JSON_URL = EXAMPLE_URL + "html_post-json-test-page.html";
+const POST_RAW_URL = EXAMPLE_URL + "html_post-raw-test-page.html";
+const POST_RAW_WITH_HEADERS_URL = EXAMPLE_URL + "html_post-raw-with-headers-test-page.html";
+const PARAMS_URL = EXAMPLE_URL + "html_params-test-page.html";
+const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
+const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html";
+const JSON_MALFORMED_URL = EXAMPLE_URL + "html_json-malformed-test-page.html";
+const JSON_CUSTOM_MIME_URL = EXAMPLE_URL + "html_json-custom-mime-test-page.html";
+const JSON_TEXT_MIME_URL = EXAMPLE_URL + "html_json-text-mime-test-page.html";
+const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html";
+const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html";
+const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html";
+const CUSTOM_GET_URL = EXAMPLE_URL + "html_custom-get-page.html";
+const SINGLE_GET_URL = EXAMPLE_URL + "html_single-get-page.html";
+const STATISTICS_URL = EXAMPLE_URL + "html_statistics-test-page.html";
+const CURL_URL = EXAMPLE_URL + "html_copy-as-curl.html";
+const CURL_UTILS_URL = EXAMPLE_URL + "html_curl-utils.html";
+const SEND_BEACON_URL = EXAMPLE_URL + "html_send-beacon.html";
+const CORS_URL = EXAMPLE_URL + "html_cors-test-page.html";
+
+const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
+const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
+const HTTPS_CONTENT_TYPE_SJS = HTTPS_EXAMPLE_URL + "sjs_content-type-test-server.sjs";
+const STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs";
+const SORTING_SJS = EXAMPLE_URL + "sjs_sorting-test-server.sjs";
+const HTTPS_REDIRECT_SJS = EXAMPLE_URL + "sjs_https-redirect-test-server.sjs";
+const CORS_SJS_PATH = "/browser/devtools/client/netmonitor/test/sjs_cors-test-server.sjs";
+const HSTS_SJS = EXAMPLE_URL + "sjs_hsts-test-server.sjs";
+
+const HSTS_BASE_URL = EXAMPLE_URL;
+const HSTS_PAGE_URL = CUSTOM_GET_URL;
+
+const TEST_IMAGE = EXAMPLE_URL + "test-image.png";
+const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
+
+const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
+
+// All tests are asynchronous.
+waitForExplicitFinish();
+
+const gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
+// To enable logging for try runs, just set the pref to true.
+Services.prefs.setBoolPref("devtools.debugger.log", false);
+
+// Uncomment this pref to dump all devtools emitted events to the console.
+// Services.prefs.setBoolPref("devtools.dump.emit", true);
+
+// Always reset some prefs to their original values after the test finishes.
+const gDefaultFilters = Services.prefs.getCharPref("devtools.netmonitor.filters");
+
+registerCleanupFunction(() => {
+ info("finish() was called, cleaning up...");
+
+ Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
+ Services.prefs.setCharPref("devtools.netmonitor.filters", gDefaultFilters);
+ Services.prefs.clearUserPref("devtools.cache.disabled");
+});
+
+function waitForNavigation(aTarget) {
+ let deferred = promise.defer();
+ aTarget.once("will-navigate", () => {
+ aTarget.once("navigate", () => {
+ deferred.resolve();
+ });
+ });
+ return deferred.promise;
+}
+
+function reconfigureTab(aTarget, aOptions) {
+ let deferred = promise.defer();
+ aTarget.activeTab.reconfigure(aOptions, deferred.resolve);
+ return deferred.promise;
+}
+
+function toggleCache(aTarget, aDisabled) {
+ let options = { cacheDisabled: aDisabled, performReload: true };
+ let navigationFinished = waitForNavigation(aTarget);
+
+ // Disable the cache for any toolbox that it is opened from this point on.
+ Services.prefs.setBoolPref("devtools.cache.disabled", aDisabled);
+
+ return reconfigureTab(aTarget, options).then(() => navigationFinished);
+}
+
+function initNetMonitor(aUrl, aWindow, aEnableCache) {
+ info("Initializing a network monitor pane.");
+
+ return Task.spawn(function* () {
+ let tab = yield addTab(aUrl);
+ info("Net tab added successfully: " + aUrl);
+
+ let target = TargetFactory.forTab(tab);
+
+ yield target.makeRemote();
+ info("Target remoted.");
+
+ if (!aEnableCache) {
+ info("Disabling cache and reloading page.");
+ yield toggleCache(target, true);
+ info("Cache disabled when the current and all future toolboxes are open.");
+ // Remove any requests generated by the reload while toggling the cache to
+ // avoid interfering with the test.
+ isnot([...target.activeConsole.getNetworkEvents()].length, 0,
+ "Request to reconfigure the tab was recorded.");
+ target.activeConsole.clearNetworkRequests();
+ }
+
+ let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
+ info("Network monitor pane shown successfully.");
+
+ let monitor = toolbox.getCurrentPanel();
+ return {tab, monitor};
+ });
+}
+
+function restartNetMonitor(monitor, newUrl) {
+ info("Restarting the specified network monitor.");
+
+ return Task.spawn(function* () {
+ let tab = monitor.target.tab;
+ let url = newUrl || tab.linkedBrowser.currentURI.spec;
+
+ let onDestroyed = monitor.once("destroyed");
+ yield removeTab(tab);
+ yield onDestroyed;
+
+ return initNetMonitor(url);
+ });
+}
+
+function teardown(monitor) {
+ info("Destroying the specified network monitor.");
+
+ return Task.spawn(function* () {
+ let tab = monitor.target.tab;
+
+ let onDestroyed = monitor.once("destroyed");
+ yield removeTab(tab);
+ yield onDestroyed;
+ });
+}
+
+function waitForNetworkEvents(aMonitor, aGetRequests, aPostRequests = 0) {
+ let deferred = promise.defer();
+
+ let panel = aMonitor.panelWin;
+ let events = panel.EVENTS;
+
+ let progress = {};
+ let genericEvents = 0;
+ let postEvents = 0;
+
+ let awaitedEventsToListeners = [
+ ["UPDATING_REQUEST_HEADERS", onGenericEvent],
+ ["RECEIVED_REQUEST_HEADERS", onGenericEvent],
+ ["UPDATING_REQUEST_COOKIES", onGenericEvent],
+ ["RECEIVED_REQUEST_COOKIES", onGenericEvent],
+ ["UPDATING_REQUEST_POST_DATA", onPostEvent],
+ ["RECEIVED_REQUEST_POST_DATA", onPostEvent],
+ ["UPDATING_RESPONSE_HEADERS", onGenericEvent],
+ ["RECEIVED_RESPONSE_HEADERS", onGenericEvent],
+ ["UPDATING_RESPONSE_COOKIES", onGenericEvent],
+ ["RECEIVED_RESPONSE_COOKIES", onGenericEvent],
+ ["STARTED_RECEIVING_RESPONSE", onGenericEvent],
+ ["UPDATING_RESPONSE_CONTENT", onGenericEvent],
+ ["RECEIVED_RESPONSE_CONTENT", onGenericEvent],
+ ["UPDATING_EVENT_TIMINGS", onGenericEvent],
+ ["RECEIVED_EVENT_TIMINGS", onGenericEvent]
+ ];
+
+ function initProgressForURL(url) {
+ if (progress[url]) return;
+ progress[url] = {};
+ awaitedEventsToListeners.forEach(([e]) => progress[url][e] = 0);
+ }
+
+ function updateProgressForURL(url, event) {
+ initProgressForURL(url);
+ progress[url][Object.keys(events).find(e => events[e] == event)] = 1;
+ }
+
+ function onGenericEvent(event, actor) {
+ genericEvents++;
+ maybeResolve(event, actor);
+ }
+
+ function onPostEvent(event, actor) {
+ postEvents++;
+ maybeResolve(event, actor);
+ }
+
+ function maybeResolve(event, actor) {
+ info("> Network events progress: " +
+ genericEvents + "/" + ((aGetRequests + aPostRequests) * 13) + ", " +
+ postEvents + "/" + (aPostRequests * 2) + ", " +
+ "got " + event + " for " + actor);
+
+ let networkInfo =
+ panel.NetMonitorController.webConsoleClient.getNetworkRequest(actor);
+ let url = networkInfo.request.url;
+ updateProgressForURL(url, event);
+
+ // Uncomment this to get a detailed progress logging (when debugging a test)
+ // info("> Current state: " + JSON.stringify(progress, null, 2));
+
+ // There are 15 updates which need to be fired for a request to be
+ // considered finished. The "requestPostData" packet isn't fired for
+ // non-POST requests.
+ if (genericEvents >= (aGetRequests + aPostRequests) * 13 &&
+ postEvents >= aPostRequests * 2) {
+
+ awaitedEventsToListeners.forEach(([e, l]) => panel.off(events[e], l));
+ executeSoon(deferred.resolve);
+ }
+ }
+
+ awaitedEventsToListeners.forEach(([e, l]) => panel.on(events[e], l));
+ return deferred.promise;
+}
+
+function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) {
+ info("> Verifying: " + aMethod + " " + aUrl + " " + aData.toSource());
+ // This bloats log sizes significantly in automation (bug 992485)
+ // info("> Request: " + aRequestItem.attachment.toSource());
+
+ let requestsMenu = aRequestItem.ownerView;
+ let widgetIndex = requestsMenu.indexOfItem(aRequestItem);
+ let visibleIndex = requestsMenu.visibleItems.indexOf(aRequestItem);
+
+ info("Widget index of item: " + widgetIndex);
+ info("Visible index of item: " + visibleIndex);
+
+ let { fuzzyUrl, status, statusText, cause, type, fullMimeType,
+ transferred, size, time, displayedStatus } = aData;
+ let { attachment, target } = aRequestItem;
+
+ let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
+ let unicodeUrl = NetworkHelper.convertToUnicode(unescape(aUrl));
+ let name = NetworkHelper.convertToUnicode(unescape(uri.fileName || uri.filePath || "/"));
+ let query = NetworkHelper.convertToUnicode(unescape(uri.query));
+ let hostPort = uri.hostPort;
+ let remoteAddress = attachment.remoteAddress;
+
+ if (fuzzyUrl) {
+ ok(attachment.method.startsWith(aMethod), "The attached method is correct.");
+ ok(attachment.url.startsWith(aUrl), "The attached url is correct.");
+ } else {
+ is(attachment.method, aMethod, "The attached method is correct.");
+ is(attachment.url, aUrl, "The attached url is correct.");
+ }
+
+ is(target.querySelector(".requests-menu-method").getAttribute("value"),
+ aMethod, "The displayed method is correct.");
+
+ if (fuzzyUrl) {
+ ok(target.querySelector(".requests-menu-file").getAttribute("value").startsWith(
+ name + (query ? "?" + query : "")), "The displayed file is correct.");
+ ok(target.querySelector(".requests-menu-file").getAttribute("tooltiptext").startsWith(unicodeUrl),
+ "The tooltip file is correct.");
+ } else {
+ is(target.querySelector(".requests-menu-file").getAttribute("value"),
+ name + (query ? "?" + query : ""), "The displayed file is correct.");
+ is(target.querySelector(".requests-menu-file").getAttribute("tooltiptext"),
+ unicodeUrl, "The tooltip file is correct.");
+ }
+
+ is(target.querySelector(".requests-menu-domain").getAttribute("value"),
+ hostPort, "The displayed domain is correct.");
+
+ let domainTooltip = hostPort + (remoteAddress ? " (" + remoteAddress + ")" : "");
+ is(target.querySelector(".requests-menu-domain").getAttribute("tooltiptext"),
+ domainTooltip, "The tooltip domain is correct.");
+
+ if (status !== undefined) {
+ let value = target.querySelector(".requests-menu-status-icon").getAttribute("code");
+ let codeValue = target.querySelector(".requests-menu-status-code").getAttribute("value");
+ let tooltip = target.querySelector(".requests-menu-status").getAttribute("tooltiptext");
+ info("Displayed status: " + value);
+ info("Displayed code: " + codeValue);
+ info("Tooltip status: " + tooltip);
+ is(value, displayedStatus ? displayedStatus : status, "The displayed status is correct.");
+ is(codeValue, status, "The displayed status code is correct.");
+ is(tooltip, status + " " + statusText, "The tooltip status is correct.");
+ }
+ if (cause !== undefined) {
+ let causeLabel = target.querySelector(".requests-menu-cause-label");
+ let value = causeLabel.getAttribute("value");
+ let tooltip = causeLabel.getAttribute("tooltiptext");
+ info("Displayed cause: " + value);
+ info("Tooltip cause: " + tooltip);
+ is(value, cause.type, "The displayed cause is correct.");
+ is(tooltip, cause.loadingDocumentUri, "The tooltip cause is correct.")
+ }
+ if (type !== undefined) {
+ let value = target.querySelector(".requests-menu-type").getAttribute("value");
+ let tooltip = target.querySelector(".requests-menu-type").getAttribute("tooltiptext");
+ info("Displayed type: " + value);
+ info("Tooltip type: " + tooltip);
+ is(value, type, "The displayed type is correct.");
+ is(tooltip, fullMimeType, "The tooltip type is correct.");
+ }
+ if (transferred !== undefined) {
+ let value = target.querySelector(".requests-menu-transferred").getAttribute("value");
+ let tooltip = target.querySelector(".requests-menu-transferred").getAttribute("tooltiptext");
+ info("Displayed transferred size: " + value);
+ info("Tooltip transferred size: " + tooltip);
+ is(value, transferred, "The displayed transferred size is correct.");
+ is(tooltip, transferred, "The tooltip transferred size is correct.");
+ }
+ if (size !== undefined) {
+ let value = target.querySelector(".requests-menu-size").getAttribute("value");
+ let tooltip = target.querySelector(".requests-menu-size").getAttribute("tooltiptext");
+ info("Displayed size: " + value);
+ info("Tooltip size: " + tooltip);
+ is(value, size, "The displayed size is correct.");
+ is(tooltip, size, "The tooltip size is correct.");
+ }
+ if (time !== undefined) {
+ let value = target.querySelector(".requests-menu-timings-total").getAttribute("value");
+ let tooltip = target.querySelector(".requests-menu-timings-total").getAttribute("tooltiptext");
+ info("Displayed time: " + value);
+ info("Tooltip time: " + tooltip);
+ ok(~~(value.match(/[0-9]+/)) >= 0, "The displayed time is correct.");
+ ok(~~(tooltip.match(/[0-9]+/)) >= 0, "The tooltip time is correct.");
+ }
+
+ if (visibleIndex != -1) {
+ if (visibleIndex % 2 == 0) {
+ ok(aRequestItem.target.hasAttribute("even"),
+ aRequestItem.value + " should have 'even' attribute.");
+ ok(!aRequestItem.target.hasAttribute("odd"),
+ aRequestItem.value + " shouldn't have 'odd' attribute.");
+ } else {
+ ok(!aRequestItem.target.hasAttribute("even"),
+ aRequestItem.value + " shouldn't have 'even' attribute.");
+ ok(aRequestItem.target.hasAttribute("odd"),
+ aRequestItem.value + " should have 'odd' attribute.");
+ }
+ }
+}
+
+/**
+ * Helper function for waiting for an event to fire before resolving a promise.
+ * Example: waitFor(aMonitor.panelWin, aMonitor.panelWin.EVENTS.TAB_UPDATED);
+ *
+ * @param object subject
+ * The event emitter object that is being listened to.
+ * @param string eventName
+ * The name of the event to listen to.
+ * @return object
+ * Returns a promise that resolves upon firing of the event.
+ */
+function waitFor(subject, eventName) {
+ let deferred = promise.defer();
+ subject.once(eventName, deferred.resolve);
+ return deferred.promise;
+}
+
+/**
+ * Tests if a button for a filter of given type is the only one checked.
+ *
+ * @param string filterType
+ * The type of the filter that should be the only one checked.
+ */
+function testFilterButtons(monitor, filterType) {
+ let doc = monitor.panelWin.document;
+ let target = doc.querySelector("#requests-menu-filter-" + filterType + "-button");
+ ok(target, `Filter button '${filterType}' was found`);
+ let buttons = [...doc.querySelectorAll(".menu-filter-button")];
+ ok(buttons.length > 0, "More than zero filter buttons were found");
+
+ // Only target should be checked.
+ let checkStatus = buttons.map(button => button == target ? 1 : 0);
+ testFilterButtonsCustom(monitor, checkStatus);
+}
+
+/**
+ * Tests if filter buttons have 'checked' attributes set correctly.
+ *
+ * @param array aIsChecked
+ * An array specifying if a button at given index should have a
+ * 'checked' attribute. For example, if the third item of the array
+ * evaluates to true, the third button should be checked.
+ */
+function testFilterButtonsCustom(aMonitor, aIsChecked) {
+ let doc = aMonitor.panelWin.document;
+ let buttons = doc.querySelectorAll(".menu-filter-button");
+ for (let i = 0; i < aIsChecked.length; i++) {
+ let button = buttons[i];
+ if (aIsChecked[i]) {
+ is(button.classList.contains("checked"), true,
+ "The " + button.id + " button should have a 'checked' class.");
+ } else {
+ is(button.classList.contains("checked"), false,
+ "The " + button.id + " button should not have a 'checked' class.");
+ }
+ }
+}
+
+/**
+ * Loads shared/frame-script-utils.js in the specified tab.
+ *
+ * @param tab
+ * Optional tab to load the frame script in. Defaults to the current tab.
+ */
+function loadCommonFrameScript(tab) {
+ let browser = tab ? tab.linkedBrowser : gBrowser.selectedBrowser;
+
+ browser.messageManager.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
+}
+
+/**
+ * Perform the specified requests in the context of the page content.
+ *
+ * @param Array requests
+ * An array of objects specifying the requests to perform. See
+ * shared/frame-script-utils.js for more information.
+ *
+ * @return A promise that resolves once the requests complete.
+ */
+function performRequestsInContent(requests) {
+ info("Performing requests in the context of the content.");
+ return executeInContent("devtools:test:xhr", requests);
+}
+
+/**
+ * Send an async message to the frame script (chrome -> content) and wait for a
+ * response message with the same name (content -> chrome).
+ *
+ * @param String name
+ * The message name. Should be one of the messages defined
+ * shared/frame-script-utils.js
+ * @param Object data
+ * Optional data to send along
+ * @param Object objects
+ * Optional CPOW objects to send along
+ * @param Boolean expectResponse
+ * If set to false, don't wait for a response with the same name from the
+ * content script. Defaults to true.
+ *
+ * @return Promise
+ * Resolves to the response data if a response is expected, immediately
+ * resolves otherwise
+ */
+function executeInContent(name, data = {}, objects = {}, expectResponse = true) {
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ mm.sendAsyncMessage(name, data, objects);
+ if (expectResponse) {
+ return waitForContentMessage(name);
+ } else {
+ return promise.resolve();
+ }
+}
+
+/**
+ * Wait for a content -> chrome message on the message manager (the window
+ * messagemanager is used).
+ * @param {String} name The message name
+ * @return {Promise} A promise that resolves to the response data when the
+ * message has been received
+ */
+function waitForContentMessage(name) {
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ let def = promise.defer();
+ mm.addMessageListener(name, function onMessage(msg) {
+ mm.removeMessageListener(name, onMessage);
+ def.resolve(msg);
+ });
+ return def.promise;
+}
+
+/**
+ * Open the requestMenu menu and return all of it's items in a flat array
+ * @param {netmonitorPanel} netmonitor
+ * @param {Event} event mouse event with screenX and screenX coordinates
+ * @return An array of MenuItems
+ */
+function openContextMenuAndGetAllItems(netmonitor, event) {
+ let menu = netmonitor.RequestsMenu.contextMenu.open(event);
+
+ // Flatten all menu items into a single array to make searching through it easier
+ let allItems = [].concat.apply([], menu.items.map(function addItem(item) {
+ if (item.submenu) {
+ return addItem(item.submenu.items);
+ }
+ return item;
+ }));
+
+ return allItems;
+}