summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/frame-script-utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/frame-script-utils.js')
-rw-r--r--devtools/client/shared/frame-script-utils.js206
1 files changed, 206 insertions, 0 deletions
diff --git a/devtools/client/shared/frame-script-utils.js b/devtools/client/shared/frame-script-utils.js
new file mode 100644
index 000000000..3db7ed9ab
--- /dev/null
+++ b/devtools/client/shared/frame-script-utils.js
@@ -0,0 +1,206 @@
+/* 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/. */
+
+/* eslint-env browser */
+/* global addMessageListener, sendAsyncMessage, content */
+"use strict";
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+const {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const defer = require("devtools/shared/defer");
+const { Task } = require("devtools/shared/task");
+
+loader.lazyGetter(this, "nsIProfilerModule", () => {
+ return Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+});
+
+addMessageListener("devtools:test:history", function ({ data }) {
+ content.history[data.direction]();
+});
+
+addMessageListener("devtools:test:navigate", function ({ data }) {
+ content.location = data.location;
+});
+
+addMessageListener("devtools:test:reload", function ({ data }) {
+ data = data || {};
+ content.location.reload(data.forceget);
+});
+
+addMessageListener("devtools:test:console", function ({ data }) {
+ let { method, args, id } = data;
+ content.console[method].apply(content.console, args);
+ sendAsyncMessage("devtools:test:console:response", { id });
+});
+
+/**
+ * Performs a single XMLHttpRequest and returns a promise that resolves once
+ * the request has loaded.
+ *
+ * @param Object data
+ * { method: the request method (default: "GET"),
+ * url: the url to request (default: content.location.href),
+ * body: the request body to send (default: ""),
+ * nocache: append an unique token to the query string (default: true),
+ * requestHeaders: set request headers (default: none)
+ * }
+ *
+ * @return Promise A promise that's resolved with object
+ * { status: XMLHttpRequest.status,
+ * response: XMLHttpRequest.response }
+ *
+ */
+function promiseXHR(data) {
+ let xhr = new content.XMLHttpRequest();
+
+ let method = data.method || "GET";
+ let url = data.url || content.location.href;
+ let body = data.body || "";
+
+ if (data.nocache) {
+ url += "?devtools-cachebust=" + Math.random();
+ }
+
+ let deferred = defer();
+ xhr.addEventListener("loadend", function loadend(event) {
+ xhr.removeEventListener("loadend", loadend);
+ deferred.resolve({ status: xhr.status, response: xhr.response });
+ });
+
+ xhr.open(method, url);
+
+ // Set request headers
+ if (data.requestHeaders) {
+ data.requestHeaders.forEach(header => {
+ xhr.setRequestHeader(header.name, header.value);
+ });
+ }
+
+ xhr.send(body);
+ return deferred.promise;
+}
+
+/**
+ * Performs XMLHttpRequest request(s) in the context of the page. The data
+ * parameter can be either a single object or an array of objects described
+ * below. The requests will be performed one at a time in the order they appear
+ * in the data.
+ *
+ * The objects should have following form (any of them can be omitted; defaults
+ * shown below):
+ * {
+ * method: "GET",
+ * url: content.location.href,
+ * body: "",
+ * nocache: true, // Adds a cache busting random token to the URL,
+ * requestHeaders: [{
+ * name: "Content-Type",
+ * value: "application/json"
+ * }]
+ * }
+ *
+ * The handler will respond with devtools:test:xhr message after all requests
+ * have finished. Following data will be available for each requests
+ * (in the same order as requests):
+ * {
+ * status: XMLHttpRequest.status
+ * response: XMLHttpRequest.response
+ * }
+ */
+addMessageListener("devtools:test:xhr", Task.async(function* ({ data }) {
+ let requests = Array.isArray(data) ? data : [data];
+ let responses = [];
+
+ for (let request of requests) {
+ let response = yield promiseXHR(request);
+ responses.push(response);
+ }
+
+ sendAsyncMessage("devtools:test:xhr", responses);
+}));
+
+addMessageListener("devtools:test:profiler", function ({ data }) {
+ let { method, args, id } = data;
+ let result = nsIProfilerModule[method](...args);
+ sendAsyncMessage("devtools:test:profiler:response", {
+ data: result,
+ id: id
+ });
+});
+
+// To eval in content, look at `evalInDebuggee` in the shared-head.js.
+addMessageListener("devtools:test:eval", function ({ data }) {
+ sendAsyncMessage("devtools:test:eval:response", {
+ value: content.eval(data.script),
+ id: data.id
+ });
+});
+
+addEventListener("load", function () {
+ sendAsyncMessage("devtools:test:load");
+}, true);
+
+/**
+ * Set a given style property value on a node.
+ * @param {Object} data
+ * - {String} selector The CSS selector to get the node (can be a "super"
+ * selector).
+ * - {String} propertyName The name of the property to set.
+ * - {String} propertyValue The value for the property.
+ */
+addMessageListener("devtools:test:setStyle", function (msg) {
+ let {selector, propertyName, propertyValue} = msg.data;
+ let node = superQuerySelector(selector);
+ if (!node) {
+ return;
+ }
+
+ node.style[propertyName] = propertyValue;
+
+ sendAsyncMessage("devtools:test:setStyle");
+});
+
+/**
+ * Set a given attribute value on a node.
+ * @param {Object} data
+ * - {String} selector The CSS selector to get the node (can be a "super"
+ * selector).
+ * - {String} attributeName The name of the attribute to set.
+ * - {String} attributeValue The value for the attribute.
+ */
+addMessageListener("devtools:test:setAttribute", function (msg) {
+ let {selector, attributeName, attributeValue} = msg.data;
+ let node = superQuerySelector(selector);
+ if (!node) {
+ return;
+ }
+
+ node.setAttribute(attributeName, attributeValue);
+
+ sendAsyncMessage("devtools:test:setAttribute");
+});
+
+/**
+ * Like document.querySelector but can go into iframes too.
+ * ".container iframe || .sub-container div" will first try to find the node
+ * matched by ".container iframe" in the root document, then try to get the
+ * content document inside it, and then try to match ".sub-container div" inside
+ * this document.
+ * Any selector coming before the || separator *MUST* match a frame node.
+ * @param {String} superSelector.
+ * @return {DOMNode} The node, or null if not found.
+ */
+function superQuerySelector(superSelector, root = content.document) {
+ let frameIndex = superSelector.indexOf("||");
+ if (frameIndex === -1) {
+ return root.querySelector(superSelector);
+ }
+ let rootSelector = superSelector.substring(0, frameIndex).trim();
+ let childSelector = superSelector.substring(frameIndex + 2).trim();
+ root = root.querySelector(rootSelector);
+ if (!root || !root.contentWindow) {
+ return null;
+ }
+
+ return superQuerySelector(childSelector, root.contentWindow.document);
+}