forked from mirrors/gecko-dev
Bug 1315251 - Create a DevTools Remote Debugger Actor as a backend for the WebExtension DevTools API. r=ochameau
MozReview-Commit-ID: E6eNG8BgBwF --HG-- extra : rebase_source : 84ca2206bfca41b0167f3de00a4874e811d41a47
This commit is contained in:
parent
31ce204c24
commit
34df30a2de
11 changed files with 1053 additions and 0 deletions
|
|
@ -118,8 +118,10 @@ devtools/server/actors/**
|
||||||
!devtools/server/actors/styles.js
|
!devtools/server/actors/styles.js
|
||||||
!devtools/server/actors/webbrowser.js
|
!devtools/server/actors/webbrowser.js
|
||||||
!devtools/server/actors/webextension.js
|
!devtools/server/actors/webextension.js
|
||||||
|
!devtools/server/actors/webextension-inspected-window.js
|
||||||
devtools/server/performance/**
|
devtools/server/performance/**
|
||||||
devtools/server/tests/browser/**
|
devtools/server/tests/browser/**
|
||||||
|
!devtools/server/tests/browser/browser_webextension_inspected_window.js
|
||||||
devtools/server/tests/mochitest/**
|
devtools/server/tests/mochitest/**
|
||||||
devtools/server/tests/unit/**
|
devtools/server/tests/unit/**
|
||||||
devtools/shared/*.js
|
devtools/shared/*.js
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ DevToolsModules(
|
||||||
'webaudio.js',
|
'webaudio.js',
|
||||||
'webbrowser.js',
|
'webbrowser.js',
|
||||||
'webconsole.js',
|
'webconsole.js',
|
||||||
|
'webextension-inspected-window.js',
|
||||||
'webextension.js',
|
'webextension.js',
|
||||||
'webgl.js',
|
'webgl.js',
|
||||||
'worker.js',
|
'worker.js',
|
||||||
|
|
|
||||||
469
devtools/server/actors/webextension-inspected-window.js
Normal file
469
devtools/server/actors/webextension-inspected-window.js
Normal file
|
|
@ -0,0 +1,469 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const protocol = require("devtools/shared/protocol");
|
||||||
|
|
||||||
|
const {Ci, Cu, Cr} = require("chrome");
|
||||||
|
|
||||||
|
const Services = require("Services");
|
||||||
|
|
||||||
|
const {
|
||||||
|
XPCOMUtils,
|
||||||
|
} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||||
|
|
||||||
|
const {
|
||||||
|
webExtensionInspectedWindowSpec,
|
||||||
|
} = require("devtools/shared/specs/webextension-inspected-window");
|
||||||
|
|
||||||
|
function CustomizedReload(params) {
|
||||||
|
this.docShell = params.tabActor.window
|
||||||
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDocShell);
|
||||||
|
this.docShell.QueryInterface(Ci.nsIWebProgress);
|
||||||
|
|
||||||
|
this.inspectedWindowEval = params.inspectedWindowEval;
|
||||||
|
this.callerInfo = params.callerInfo;
|
||||||
|
|
||||||
|
this.ignoreCache = params.ignoreCache;
|
||||||
|
this.injectedScript = params.injectedScript;
|
||||||
|
this.userAgent = params.userAgent;
|
||||||
|
|
||||||
|
this.customizedReloadWindows = new WeakSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomizedReload.prototype = {
|
||||||
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
||||||
|
Ci.nsISupportsWeakReference,
|
||||||
|
Ci.nsISupports]),
|
||||||
|
get window() {
|
||||||
|
return this.docShell.DOMWindow;
|
||||||
|
},
|
||||||
|
|
||||||
|
get webNavigation() {
|
||||||
|
return this.docShell
|
||||||
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIWebNavigation);
|
||||||
|
},
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (!this.waitForReloadCompleted) {
|
||||||
|
this.waitForReloadCompleted = new Promise((resolve, reject) => {
|
||||||
|
this.resolveReloadCompleted = resolve;
|
||||||
|
this.rejectReloadCompleted = reject;
|
||||||
|
|
||||||
|
if (this.userAgent) {
|
||||||
|
this.docShell.customUserAgent = this.userAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reloadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
|
||||||
|
|
||||||
|
if (this.ignoreCache) {
|
||||||
|
reloadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.injectedScript) {
|
||||||
|
// Listen to the newly created document elements only if there is an
|
||||||
|
// injectedScript to evaluate.
|
||||||
|
Services.obs.addObserver(this, "document-element-inserted", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch the loading progress and clear the current CustomizedReload once the
|
||||||
|
// page has been reloaded (or if its reloading has been interrupted).
|
||||||
|
this.docShell.addProgressListener(this,
|
||||||
|
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
|
||||||
|
|
||||||
|
this.webNavigation.reload(reloadFlags);
|
||||||
|
} catch (err) {
|
||||||
|
// Cancel the injected script listener if the reload fails
|
||||||
|
// (which will also report the error by rejecting the promise).
|
||||||
|
this.stop(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.waitForReloadCompleted;
|
||||||
|
},
|
||||||
|
|
||||||
|
observe(subject, topic, data) {
|
||||||
|
if (topic !== "document-element-inserted") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const document = subject;
|
||||||
|
const window = document && document.defaultView;
|
||||||
|
|
||||||
|
// Filter out non interesting documents.
|
||||||
|
if (!document || !document.location || !window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let subjectDocShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIWebNavigation)
|
||||||
|
.QueryInterface(Ci.nsIDocShell);
|
||||||
|
|
||||||
|
// Keep track of the set of window objects where we are going to inject
|
||||||
|
// the injectedScript: the top level window and all its descendant
|
||||||
|
// that are still of type content (filtering out loaded XUL pages, if any).
|
||||||
|
if (window == this.window) {
|
||||||
|
this.customizedReloadWindows.add(window);
|
||||||
|
} else if (subjectDocShell.sameTypeParent) {
|
||||||
|
let parentWindow = subjectDocShell.sameTypeParent
|
||||||
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDOMWindow);
|
||||||
|
if (parentWindow && this.customizedReloadWindows.has(parentWindow)) {
|
||||||
|
this.customizedReloadWindows.add(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.customizedReloadWindows.has(window)) {
|
||||||
|
const {
|
||||||
|
apiErrorResult
|
||||||
|
} = this.inspectedWindowEval(this.callerInfo, this.injectedScript, {}, window);
|
||||||
|
|
||||||
|
// Log only apiErrorResult, because no one is waiting for the
|
||||||
|
// injectedScript result, and any exception is going to be logged
|
||||||
|
// in the inspectedWindow webconsole.
|
||||||
|
if (apiErrorResult) {
|
||||||
|
console.error(
|
||||||
|
"Unexpected Error in injectedScript during inspectedWindow.reload for",
|
||||||
|
`${this.callerInfo.url}:${this.callerInfo.lineNumber}`,
|
||||||
|
apiErrorResult
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onStateChange(webProgress, request, state, status) {
|
||||||
|
if (webProgress.DOMWindow !== this.window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state & Ci.nsIWebProgressListener.STATE_STOP) {
|
||||||
|
if (status == Cr.NS_BINDING_ABORTED) {
|
||||||
|
// The customized reload has been interrupted and we can clear
|
||||||
|
// the CustomizedReload and reject the promise.
|
||||||
|
const url = this.window.location.href;
|
||||||
|
this.stop(new Error(
|
||||||
|
`devtools.inspectedWindow.reload on ${url} has been interrupted`
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
// Once the top level frame has been loaded, we can clear the customized reload
|
||||||
|
// and resolve the promise.
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stop(error) {
|
||||||
|
if (this.stopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.docShell.removeProgressListener(this);
|
||||||
|
|
||||||
|
if (this.injectedScript) {
|
||||||
|
Services.obs.removeObserver(this, "document-element-inserted", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the customized user agent.
|
||||||
|
if (this.userAgent && this.docShell.customUserAgent == this.userAgent) {
|
||||||
|
this.docShell.customUserAgent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.rejectReloadCompleted(error);
|
||||||
|
} else {
|
||||||
|
this.resolveReloadCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stopped = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var WebExtensionInspectedWindowActor = protocol.ActorClassWithSpec(
|
||||||
|
webExtensionInspectedWindowSpec,
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Created the WebExtension InspectedWindow actor
|
||||||
|
*/
|
||||||
|
initialize(conn, tabActor) {
|
||||||
|
protocol.Actor.prototype.initialize.call(this, conn);
|
||||||
|
this.tabActor = tabActor;
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy(conn) {
|
||||||
|
protocol.Actor.prototype.destroy.call(this, conn);
|
||||||
|
if (this.customizedReload) {
|
||||||
|
this.customizedReload.stop(
|
||||||
|
new Error("WebExtensionInspectedWindowActor destroyed")
|
||||||
|
);
|
||||||
|
delete this.customizedReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._dbg) {
|
||||||
|
this._dbg.enabled = false;
|
||||||
|
delete this._dbg;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isSystemPrincipal(window) {
|
||||||
|
const principal = window.document.nodePrincipal;
|
||||||
|
return Services.scriptSecurityManager.isSystemPrincipal(principal);
|
||||||
|
},
|
||||||
|
|
||||||
|
get dbg() {
|
||||||
|
if (this._dbg) {
|
||||||
|
return this._dbg;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dbg = this.tabActor.makeDebugger();
|
||||||
|
return this._dbg;
|
||||||
|
},
|
||||||
|
|
||||||
|
get window() {
|
||||||
|
return this.tabActor.window;
|
||||||
|
},
|
||||||
|
|
||||||
|
get webNavigation() {
|
||||||
|
return this.tabActor.webNavigation;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the target tab, optionally bypass cache, customize the userAgent and/or
|
||||||
|
* inject a script in targeted document or any of its sub-frame.
|
||||||
|
*
|
||||||
|
* @param {webExtensionCallerInfo} callerInfo
|
||||||
|
* the addonId and the url (the addon base url or the url of the actual caller
|
||||||
|
* filename and lineNumber) used to log useful debugging information in the
|
||||||
|
* produced error logs and eval stack trace.
|
||||||
|
*
|
||||||
|
* @param {webExtensionReloadOptions} options
|
||||||
|
* used to optionally enable the reload customizations.
|
||||||
|
* @param {boolean|undefined} options.ignoreCache
|
||||||
|
* enable/disable the cache bypass headers.
|
||||||
|
* @param {string|undefined} options.userAgent
|
||||||
|
* customize the userAgent during the page reload.
|
||||||
|
* @param {string|undefined} options.injectedScript
|
||||||
|
* evaluate the provided javascript code in the top level and every sub-frame
|
||||||
|
* created during the page reload, before any other script in the page has been
|
||||||
|
* executed.
|
||||||
|
*/
|
||||||
|
reload(callerInfo, {ignoreCache, userAgent, injectedScript}) {
|
||||||
|
if (this.isSystemPrincipal(this.window)) {
|
||||||
|
console.error("Ignored inspectedWindow.reload on system principal target for " +
|
||||||
|
`${callerInfo.url}:${callerInfo.lineNumber}`);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const delayedReload = () => {
|
||||||
|
// This won't work while the browser is shutting down and we don't really
|
||||||
|
// care.
|
||||||
|
if (Services.startup.shuttingDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (injectedScript || userAgent) {
|
||||||
|
if (this.customizedReload) {
|
||||||
|
// TODO(rpl): check what chrome does, and evaluate if queue the new reload
|
||||||
|
// after the current one has been completed.
|
||||||
|
console.error(
|
||||||
|
"Reload already in progress. Ignored inspectedWindow.reload for " +
|
||||||
|
`${callerInfo.url}:${callerInfo.lineNumber}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.customizedReload = new CustomizedReload({
|
||||||
|
tabActor: this.tabActor,
|
||||||
|
inspectedWindowEval: this.eval.bind(this),
|
||||||
|
callerInfo, injectedScript, userAgent, ignoreCache,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.customizedReload.start()
|
||||||
|
.then(() => {
|
||||||
|
delete this.customizedReload;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
delete this.customizedReload;
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
// Cancel the customized reload (if any) on exception during the
|
||||||
|
// reload setup.
|
||||||
|
if (this.customizedReload) {
|
||||||
|
this.customizedReload.stop(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If there is no custom user agent and/or injected script, then
|
||||||
|
// we can reload the target without subscribing any observer/listener.
|
||||||
|
let reloadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
|
||||||
|
if (ignoreCache) {
|
||||||
|
reloadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
|
||||||
|
}
|
||||||
|
this.webNavigation.reload(reloadFlags);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute the reload in a dispatched runnable, so that we can
|
||||||
|
// return the reply to the caller before the reload is actually
|
||||||
|
// started.
|
||||||
|
Services.tm.currentThread.dispatch(delayedReload, 0);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate the provided javascript code in a target window (that is always the
|
||||||
|
* tabActor window when called through RDP protocol, or the passed customTargetWindow
|
||||||
|
* when called directly from the CustomizedReload instances).
|
||||||
|
*
|
||||||
|
* @param {webExtensionCallerInfo} callerInfo
|
||||||
|
* the addonId and the url (the addon base url or the url of the actual caller
|
||||||
|
* filename and lineNumber) used to log useful debugging information in the
|
||||||
|
* produced error logs and eval stack trace.
|
||||||
|
*
|
||||||
|
* @param {string} expression
|
||||||
|
* the javascript code to be evaluated in the target window
|
||||||
|
*
|
||||||
|
* @param {webExtensionEvalOptions} evalOptions
|
||||||
|
* used to optionally enable the eval customizations.
|
||||||
|
* NOTE: none of the eval options is currently implemented, they will be already
|
||||||
|
* reported as unsupported by the WebExtensions schema validation wrappers, but
|
||||||
|
* an additional level of error reporting is going to be applied here, so that
|
||||||
|
* if the server and the client have different ideas of which option is supported
|
||||||
|
* the eval call result will contain detailed informations (in the format usually
|
||||||
|
* expected for errors not raised in the evaluated javascript code).
|
||||||
|
*
|
||||||
|
* @param {DOMWindow|undefined} customTargetWindow
|
||||||
|
* Used in the CustomizedReload instances to evaluate the `injectedScript`
|
||||||
|
* javascript code in every sub-frame of the target window during the tab reload.
|
||||||
|
* NOTE: this parameter is not part of the RDP protocol exposed by this actor, when
|
||||||
|
* it is called over the remote debugging protocol the target window is always
|
||||||
|
* `tabActor.window`.
|
||||||
|
*/
|
||||||
|
eval(callerInfo, expression, options, customTargetWindow) {
|
||||||
|
const window = customTargetWindow || this.window;
|
||||||
|
|
||||||
|
if (Object.keys(options).length > 0) {
|
||||||
|
return {
|
||||||
|
exceptionInfo: {
|
||||||
|
isError: true,
|
||||||
|
code: "E_PROTOCOLERROR",
|
||||||
|
description: "Inspector protocol error: %s",
|
||||||
|
details: [
|
||||||
|
"The inspectedWindow.eval options are currently not supported",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window) {
|
||||||
|
return {
|
||||||
|
exceptionInfo: {
|
||||||
|
isError: true,
|
||||||
|
code: "E_PROTOCOLERROR",
|
||||||
|
description: "Inspector protocol error: %s",
|
||||||
|
details: [
|
||||||
|
"The target window is not defined. inspectedWindow.eval not executed.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isSystemPrincipal(window)) {
|
||||||
|
// On denied JS evaluation, report it using the same data format
|
||||||
|
// used in the corresponding chrome API method to report issues that are
|
||||||
|
// not exceptions raised in the evaluated javascript code.
|
||||||
|
return {
|
||||||
|
exceptionInfo: {
|
||||||
|
isError: true,
|
||||||
|
code: "E_PROTOCOLERROR",
|
||||||
|
description: "Inspector protocol error: %s",
|
||||||
|
details: [
|
||||||
|
"This target has a system principal. inspectedWindow.eval denied.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbgWindow = this.dbg.makeGlobalObjectReference(window);
|
||||||
|
|
||||||
|
let evalCalledFrom = callerInfo.url;
|
||||||
|
if (callerInfo.lineNumber) {
|
||||||
|
evalCalledFrom += `:${callerInfo.lineNumber}`;
|
||||||
|
}
|
||||||
|
// TODO(rpl): add $0 and inspect(...) bindings (Bug 1300590)
|
||||||
|
const result = dbgWindow.executeInGlobalWithBindings(expression, {}, {
|
||||||
|
url: `debugger eval called from ${evalCalledFrom} - eval code`,
|
||||||
|
});
|
||||||
|
|
||||||
|
let evalResult;
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
if ("return" in result) {
|
||||||
|
evalResult = result.return;
|
||||||
|
} else if ("yield" in result) {
|
||||||
|
evalResult = result.yield;
|
||||||
|
} else if ("throw" in result) {
|
||||||
|
const throwErr = result.throw;
|
||||||
|
|
||||||
|
// XXXworkers: Calling unsafeDereference() returns an object with no
|
||||||
|
// toString method in workers. See Bug 1215120.
|
||||||
|
const unsafeDereference = throwErr && (typeof throwErr === "object") &&
|
||||||
|
throwErr.unsafeDereference();
|
||||||
|
const message = unsafeDereference && unsafeDereference.toString ?
|
||||||
|
unsafeDereference.toString() : String(throwErr);
|
||||||
|
const stack = unsafeDereference && unsafeDereference.stack ?
|
||||||
|
unsafeDereference.stack : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
exceptionInfo: {
|
||||||
|
isException: true,
|
||||||
|
value: `${message}\n\t${stack}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO(rpl): can the result of executeInGlobalWithBinding be null or
|
||||||
|
// undefined? (which means that it is not a return, a yield or a throw).
|
||||||
|
console.error("Unexpected empty inspectedWindow.eval result for",
|
||||||
|
`${callerInfo.url}:${callerInfo.lineNumber}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evalResult) {
|
||||||
|
try {
|
||||||
|
if (evalResult && typeof evalResult === "object") {
|
||||||
|
evalResult = evalResult.unsafeDereference();
|
||||||
|
}
|
||||||
|
evalResult = JSON.parse(JSON.stringify(evalResult));
|
||||||
|
} catch (err) {
|
||||||
|
// The evaluation result cannot be sent over the RDP Protocol,
|
||||||
|
// report it as with the same data format used in the corresponding
|
||||||
|
// chrome API method.
|
||||||
|
return {
|
||||||
|
exceptionInfo: {
|
||||||
|
isError: true,
|
||||||
|
code: "E_PROTOCOLERROR",
|
||||||
|
description: "Inspector protocol error: %s",
|
||||||
|
details: [
|
||||||
|
String(err),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {value: evalResult};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
exports.WebExtensionInspectedWindowActor = WebExtensionInspectedWindowActor;
|
||||||
|
|
@ -569,6 +569,11 @@ var DebuggerServer = {
|
||||||
constructor: "EmulationActor",
|
constructor: "EmulationActor",
|
||||||
type: { tab: true }
|
type: { tab: true }
|
||||||
});
|
});
|
||||||
|
this.registerModule("devtools/server/actors/webextension-inspected-window", {
|
||||||
|
prefix: "webExtensionInspectedWindow",
|
||||||
|
constructor: "WebExtensionInspectedWindowActor",
|
||||||
|
type: { tab: true }
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ support-files =
|
||||||
doc_force_gc.html
|
doc_force_gc.html
|
||||||
doc_innerHTML.html
|
doc_innerHTML.html
|
||||||
doc_perf.html
|
doc_perf.html
|
||||||
|
inspectedwindow-reload-target.sjs
|
||||||
navigate-first.html
|
navigate-first.html
|
||||||
navigate-second.html
|
navigate-second.html
|
||||||
storage-cookies-same-name.html
|
storage-cookies-same-name.html
|
||||||
|
|
@ -97,3 +98,4 @@ skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still di
|
||||||
[browser_directorscript_actors.js]
|
[browser_directorscript_actors.js]
|
||||||
skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
|
skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
|
||||||
[browser_register_actor.js]
|
[browser_register_actor.js]
|
||||||
|
[browser_webextension_inspected_window.js]
|
||||||
|
|
@ -0,0 +1,364 @@
|
||||||
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {
|
||||||
|
WebExtensionInspectedWindowFront
|
||||||
|
} = require("devtools/shared/fronts/webextension-inspected-window");
|
||||||
|
|
||||||
|
const TEST_RELOAD_URL = `${MAIN_DOMAIN}/inspectedwindow-reload-target.sjs`;
|
||||||
|
|
||||||
|
const FAKE_CALLER_INFO = {
|
||||||
|
url: "moz-extension://fake-webextension-uuid/fake-caller-script.js",
|
||||||
|
lineNumber: 1,
|
||||||
|
addonId: "fake-webextension-uuid",
|
||||||
|
};
|
||||||
|
|
||||||
|
function* setup(pageUrl) {
|
||||||
|
yield addTab(pageUrl);
|
||||||
|
initDebuggerServer();
|
||||||
|
|
||||||
|
const client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
|
const form = yield connectDebuggerClient(client);
|
||||||
|
|
||||||
|
const [, tabClient] = yield client.attachTab(form.actor);
|
||||||
|
|
||||||
|
const [, consoleClient] = yield client.attachConsole(form.consoleActor, []);
|
||||||
|
|
||||||
|
const inspectedWindowFront = new WebExtensionInspectedWindowFront(client, form);
|
||||||
|
|
||||||
|
return {
|
||||||
|
client, form,
|
||||||
|
tabClient, consoleClient,
|
||||||
|
inspectedWindowFront,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function* teardown({client}) {
|
||||||
|
yield client.close();
|
||||||
|
DebuggerServer.destroy();
|
||||||
|
gBrowser.removeCurrentTab();
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForNextTabNavigated(client) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
client.addListener("tabNavigated", function tabNavigatedListener(evt, pkt) {
|
||||||
|
if (pkt.state == "stop" && pkt.isFrameSwitching == false) {
|
||||||
|
client.removeListener("tabNavigated", tabNavigatedListener);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function consoleEvalJS(consoleClient, jsCode) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
consoleClient.evaluateJS(jsCode, resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Script used as the injectedScript option in the inspectedWindow.reload tests.
|
||||||
|
function injectedScript() {
|
||||||
|
if (!window.pageScriptExecutedFirst) {
|
||||||
|
window.addEventListener("DOMContentLoaded", function listener() {
|
||||||
|
window.removeEventListener("DOMContentLoaded", listener);
|
||||||
|
if (document.querySelector("pre")) {
|
||||||
|
document.querySelector("pre").textContent = "injected script executed first";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Script evaluated in the target tab, to collect the results of injectedScript
|
||||||
|
// evaluation in the inspectedWindow.reload tests.
|
||||||
|
function collectEvalResults() {
|
||||||
|
let results = [];
|
||||||
|
let iframeDoc = document;
|
||||||
|
|
||||||
|
while (iframeDoc) {
|
||||||
|
if (iframeDoc.querySelector("pre")) {
|
||||||
|
results.push(iframeDoc.querySelector("pre").textContent);
|
||||||
|
}
|
||||||
|
const iframe = iframeDoc.querySelector("iframe");
|
||||||
|
iframeDoc = iframe ? iframe.contentDocument : null;
|
||||||
|
}
|
||||||
|
return JSON.stringify(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(function* test_successfull_inspectedWindowEval_result() {
|
||||||
|
const {client, inspectedWindowFront} = yield setup(MAIN_DOMAIN);
|
||||||
|
const result = yield inspectedWindowFront.eval(FAKE_CALLER_INFO, "window.location", {});
|
||||||
|
|
||||||
|
ok(result.value, "Got a result from inspectedWindow eval");
|
||||||
|
is(result.value.href, MAIN_DOMAIN,
|
||||||
|
"Got the expected window.location.href property value");
|
||||||
|
is(result.value.protocol, "http:",
|
||||||
|
"Got the expected window.location.protocol property value");
|
||||||
|
|
||||||
|
yield teardown({client});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_error_inspectedWindowEval_result() {
|
||||||
|
const {client, inspectedWindowFront} = yield setup(MAIN_DOMAIN);
|
||||||
|
const result = yield inspectedWindowFront.eval(FAKE_CALLER_INFO, "window", {});
|
||||||
|
|
||||||
|
ok(!result.value, "Got a null result from inspectedWindow eval");
|
||||||
|
ok(result.exceptionInfo.isError, "Got an API Error result from inspectedWindow eval");
|
||||||
|
ok(!result.exceptionInfo.isException, "An error isException is false as expected");
|
||||||
|
is(result.exceptionInfo.code, "E_PROTOCOLERROR",
|
||||||
|
"Got the expected 'code' property in the error result");
|
||||||
|
is(result.exceptionInfo.description, "Inspector protocol error: %s",
|
||||||
|
"Got the expected 'description' property in the error result");
|
||||||
|
is(result.exceptionInfo.details.length, 1,
|
||||||
|
"The 'details' array property should contains 1 element");
|
||||||
|
ok(result.exceptionInfo.details[0].includes("cyclic object value"),
|
||||||
|
"Got the expected content in the error results's details");
|
||||||
|
|
||||||
|
yield teardown({client});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_system_principal_denied_error_inspectedWindowEval_result() {
|
||||||
|
const {client, inspectedWindowFront} = yield setup("about:addons");
|
||||||
|
const result = yield inspectedWindowFront.eval(FAKE_CALLER_INFO, "window", {});
|
||||||
|
|
||||||
|
ok(!result.value, "Got a null result from inspectedWindow eval");
|
||||||
|
ok(result.exceptionInfo.isError,
|
||||||
|
"Got an API Error result from inspectedWindow eval on a system principal page");
|
||||||
|
is(result.exceptionInfo.code, "E_PROTOCOLERROR",
|
||||||
|
"Got the expected 'code' property in the error result");
|
||||||
|
is(result.exceptionInfo.description, "Inspector protocol error: %s",
|
||||||
|
"Got the expected 'description' property in the error result");
|
||||||
|
is(result.exceptionInfo.details.length, 1,
|
||||||
|
"The 'details' array property should contains 1 element");
|
||||||
|
is(result.exceptionInfo.details[0],
|
||||||
|
"This target has a system principal. inspectedWindow.eval denied.",
|
||||||
|
"Got the expected content in the error results's details");
|
||||||
|
|
||||||
|
yield teardown({client});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_exception_inspectedWindowEval_result() {
|
||||||
|
const {client, inspectedWindowFront} = yield setup(MAIN_DOMAIN);
|
||||||
|
const result = yield inspectedWindowFront.eval(
|
||||||
|
FAKE_CALLER_INFO, "throw Error('fake eval error');", {});
|
||||||
|
|
||||||
|
ok(result.exceptionInfo.isException, "Got an exception as expected");
|
||||||
|
ok(!result.value, "Got an undefined eval value");
|
||||||
|
ok(!result.exceptionInfo.isError, "An exception should not be isError=true");
|
||||||
|
ok(result.exceptionInfo.value.includes("Error: fake eval error"),
|
||||||
|
"Got the expected exception message");
|
||||||
|
|
||||||
|
const expectedCallerInfo =
|
||||||
|
`called from ${FAKE_CALLER_INFO.url}:${FAKE_CALLER_INFO.lineNumber}`;
|
||||||
|
ok(result.exceptionInfo.value.includes(expectedCallerInfo),
|
||||||
|
"Got the expected caller info in the exception message");
|
||||||
|
|
||||||
|
const expectedStack = `eval code:1:7`;
|
||||||
|
ok(result.exceptionInfo.value.includes(expectedStack),
|
||||||
|
"Got the expected stack trace in the exception message");
|
||||||
|
|
||||||
|
yield teardown({client});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_exception_inspectedWindowReload() {
|
||||||
|
const {
|
||||||
|
client, consoleClient, inspectedWindowFront,
|
||||||
|
} = yield setup(`${TEST_RELOAD_URL}?test=cache`);
|
||||||
|
|
||||||
|
// Test reload with bypassCache=false.
|
||||||
|
|
||||||
|
const waitForNoBypassCacheReload = waitForNextTabNavigated(client);
|
||||||
|
const reloadResult = yield inspectedWindowFront.reload(FAKE_CALLER_INFO,
|
||||||
|
{ignoreCache: false});
|
||||||
|
|
||||||
|
ok(!reloadResult, "Got the expected undefined result from inspectedWindow reload");
|
||||||
|
|
||||||
|
yield waitForNoBypassCacheReload;
|
||||||
|
|
||||||
|
const noBypassCacheEval = yield consoleEvalJS(consoleClient,
|
||||||
|
"document.body.textContent");
|
||||||
|
|
||||||
|
is(noBypassCacheEval.result, "empty cache headers",
|
||||||
|
"Got the expected result with reload forceBypassCache=false");
|
||||||
|
|
||||||
|
// Test reload with bypassCache=true.
|
||||||
|
|
||||||
|
const waitForForceBypassCacheReload = waitForNextTabNavigated(client);
|
||||||
|
yield inspectedWindowFront.reload(FAKE_CALLER_INFO, {ignoreCache: true});
|
||||||
|
|
||||||
|
yield waitForForceBypassCacheReload;
|
||||||
|
|
||||||
|
const forceBypassCacheEval = yield consoleEvalJS(consoleClient,
|
||||||
|
"document.body.textContent");
|
||||||
|
|
||||||
|
is(forceBypassCacheEval.result, "no-cache:no-cache",
|
||||||
|
"Got the expected result with reload forceBypassCache=true");
|
||||||
|
|
||||||
|
yield teardown({client});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_exception_inspectedWindowReload_customUserAgent() {
|
||||||
|
const {
|
||||||
|
client, consoleClient, inspectedWindowFront,
|
||||||
|
} = yield setup(`${TEST_RELOAD_URL}?test=user-agent`);
|
||||||
|
|
||||||
|
// Test reload with custom userAgent.
|
||||||
|
|
||||||
|
const waitForCustomUserAgentReload = waitForNextTabNavigated(client);
|
||||||
|
yield inspectedWindowFront.reload(FAKE_CALLER_INFO,
|
||||||
|
{userAgent: "Customized User Agent"});
|
||||||
|
|
||||||
|
yield waitForCustomUserAgentReload;
|
||||||
|
|
||||||
|
const customUserAgentEval = yield consoleEvalJS(consoleClient,
|
||||||
|
"document.body.textContent");
|
||||||
|
|
||||||
|
is(customUserAgentEval.result, "Customized User Agent",
|
||||||
|
"Got the expected result on reload with a customized userAgent");
|
||||||
|
|
||||||
|
// Test reload with no custom userAgent.
|
||||||
|
|
||||||
|
const waitForNoCustomUserAgentReload = waitForNextTabNavigated(client);
|
||||||
|
yield inspectedWindowFront.reload(FAKE_CALLER_INFO, {});
|
||||||
|
|
||||||
|
yield waitForNoCustomUserAgentReload;
|
||||||
|
|
||||||
|
const noCustomUserAgentEval = yield consoleEvalJS(consoleClient,
|
||||||
|
"document.body.textContent");
|
||||||
|
|
||||||
|
is(noCustomUserAgentEval.result, window.navigator.userAgent,
|
||||||
|
"Got the expected result with reload without a customized userAgent");
|
||||||
|
|
||||||
|
yield teardown({client});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_exception_inspectedWindowReload_injectedScript() {
|
||||||
|
const {
|
||||||
|
client, consoleClient, inspectedWindowFront,
|
||||||
|
} = yield setup(`${TEST_RELOAD_URL}?test=injected-script&frames=3`);
|
||||||
|
|
||||||
|
// Test reload with an injectedScript.
|
||||||
|
|
||||||
|
const waitForInjectedScriptReload = waitForNextTabNavigated(client);
|
||||||
|
yield inspectedWindowFront.reload(FAKE_CALLER_INFO,
|
||||||
|
{injectedScript: `new ${injectedScript}`});
|
||||||
|
yield waitForInjectedScriptReload;
|
||||||
|
|
||||||
|
const injectedScriptEval = yield consoleEvalJS(consoleClient,
|
||||||
|
`(${collectEvalResults})()`);
|
||||||
|
|
||||||
|
const expectedResult = (new Array(4)).fill("injected script executed first");
|
||||||
|
|
||||||
|
SimpleTest.isDeeply(JSON.parse(injectedScriptEval.result), expectedResult,
|
||||||
|
"Got the expected result on reload with an injected script");
|
||||||
|
|
||||||
|
// Test reload without an injectedScript.
|
||||||
|
|
||||||
|
const waitForNoInjectedScriptReload = waitForNextTabNavigated(client);
|
||||||
|
yield inspectedWindowFront.reload(FAKE_CALLER_INFO, {});
|
||||||
|
yield waitForNoInjectedScriptReload;
|
||||||
|
|
||||||
|
const noInjectedScriptEval = yield consoleEvalJS(consoleClient,
|
||||||
|
`(${collectEvalResults})()`);
|
||||||
|
|
||||||
|
const newExpectedResult = (new Array(4)).fill("injected script NOT executed");
|
||||||
|
|
||||||
|
SimpleTest.isDeeply(JSON.parse(noInjectedScriptEval.result), newExpectedResult,
|
||||||
|
"Got the expected result on reload with no injected script");
|
||||||
|
|
||||||
|
yield teardown({client});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_exception_inspectedWindowReload_multiple_calls() {
|
||||||
|
const {
|
||||||
|
client, consoleClient, inspectedWindowFront,
|
||||||
|
} = yield setup(`${TEST_RELOAD_URL}?test=user-agent`);
|
||||||
|
|
||||||
|
// Test reload with custom userAgent three times (and then
|
||||||
|
// check that only the first one has affected the page reload.
|
||||||
|
|
||||||
|
const waitForCustomUserAgentReload = waitForNextTabNavigated(client);
|
||||||
|
|
||||||
|
inspectedWindowFront.reload(FAKE_CALLER_INFO, {userAgent: "Customized User Agent 1"});
|
||||||
|
inspectedWindowFront.reload(FAKE_CALLER_INFO, {userAgent: "Customized User Agent 2"});
|
||||||
|
|
||||||
|
yield waitForCustomUserAgentReload;
|
||||||
|
|
||||||
|
const customUserAgentEval = yield consoleEvalJS(consoleClient,
|
||||||
|
"document.body.textContent");
|
||||||
|
|
||||||
|
is(customUserAgentEval.result, "Customized User Agent 1",
|
||||||
|
"Got the expected result on reload with a customized userAgent");
|
||||||
|
|
||||||
|
// Test reload with no custom userAgent.
|
||||||
|
|
||||||
|
const waitForNoCustomUserAgentReload = waitForNextTabNavigated(client);
|
||||||
|
yield inspectedWindowFront.reload(FAKE_CALLER_INFO, {});
|
||||||
|
|
||||||
|
yield waitForNoCustomUserAgentReload;
|
||||||
|
|
||||||
|
const noCustomUserAgentEval = yield consoleEvalJS(consoleClient,
|
||||||
|
"document.body.textContent");
|
||||||
|
|
||||||
|
is(noCustomUserAgentEval.result, window.navigator.userAgent,
|
||||||
|
"Got the expected result with reload without a customized userAgent");
|
||||||
|
|
||||||
|
yield teardown({client});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_exception_inspectedWindowReload_stopped() {
|
||||||
|
const {
|
||||||
|
client, consoleClient, inspectedWindowFront,
|
||||||
|
} = yield setup(`${TEST_RELOAD_URL}?test=injected-script&frames=3`);
|
||||||
|
|
||||||
|
// Test reload on a page that calls window.stop() immediately during the page loading
|
||||||
|
|
||||||
|
const waitForPageLoad = waitForNextTabNavigated(client);
|
||||||
|
yield inspectedWindowFront.eval(FAKE_CALLER_INFO,
|
||||||
|
"window.location += '&stop=windowStop'");
|
||||||
|
|
||||||
|
info("Load a webpage that calls 'window.stop()' while is still loading");
|
||||||
|
yield waitForPageLoad;
|
||||||
|
|
||||||
|
info("Starting a reload with an injectedScript");
|
||||||
|
const waitForInjectedScriptReload = waitForNextTabNavigated(client);
|
||||||
|
yield inspectedWindowFront.reload(FAKE_CALLER_INFO,
|
||||||
|
{injectedScript: `new ${injectedScript}`});
|
||||||
|
yield waitForInjectedScriptReload;
|
||||||
|
|
||||||
|
const injectedScriptEval = yield consoleEvalJS(consoleClient,
|
||||||
|
`(${collectEvalResults})()`);
|
||||||
|
|
||||||
|
// The page should have stopped during the reload and only one injected script
|
||||||
|
// is expected.
|
||||||
|
const expectedResult = (new Array(1)).fill("injected script executed first");
|
||||||
|
|
||||||
|
SimpleTest.isDeeply(JSON.parse(injectedScriptEval.result), expectedResult,
|
||||||
|
"The injected script has been executed on the 'stopped' page reload");
|
||||||
|
|
||||||
|
// Reload again with no options.
|
||||||
|
|
||||||
|
info("Reload the tab again without any reload options");
|
||||||
|
const waitForNoInjectedScriptReload = waitForNextTabNavigated(client);
|
||||||
|
yield inspectedWindowFront.reload(FAKE_CALLER_INFO, {});
|
||||||
|
yield waitForNoInjectedScriptReload;
|
||||||
|
|
||||||
|
const noInjectedScriptEval = yield consoleEvalJS(consoleClient,
|
||||||
|
`(${collectEvalResults})()`);
|
||||||
|
|
||||||
|
// The page should have stopped during the reload and no injected script should
|
||||||
|
// have been executed during this second reload (or it would mean that the previous
|
||||||
|
// customized reload was still pending and has wrongly affected the second reload)
|
||||||
|
const newExpectedResult = (new Array(1)).fill("injected script NOT executed");
|
||||||
|
|
||||||
|
SimpleTest.isDeeply(
|
||||||
|
JSON.parse(noInjectedScriptEval.result), newExpectedResult,
|
||||||
|
"No injectedScript should have been evaluated during the second reload"
|
||||||
|
);
|
||||||
|
|
||||||
|
yield teardown({client});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: check eval with $0 binding once implemented (Bug 1300590)
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
Components.utils.importGlobalProperties(["URLSearchParams"]);
|
||||||
|
|
||||||
|
function handleRequest(request, response) {
|
||||||
|
let params = new URLSearchParams(request.queryString);
|
||||||
|
|
||||||
|
switch(params.get("test")) {
|
||||||
|
case "cache":
|
||||||
|
handleCacheTestRequest(request, response);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "user-agent":
|
||||||
|
handleUserAgentTestRequest(request, response);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "injected-script":
|
||||||
|
handleInjectedScriptTestRequest(request, response, params);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCacheTestRequest(request, response) {
|
||||||
|
response.setHeader("Content-Type", "text/plain; charset=UTF-8", false);
|
||||||
|
|
||||||
|
if (request.hasHeader("pragma") && request.hasHeader("cache-control")) {
|
||||||
|
response.write(`${request.getHeader("pragma")}:${request.getHeader("cache-control")}`);
|
||||||
|
} else {
|
||||||
|
response.write("empty cache headers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUserAgentTestRequest(request, response) {
|
||||||
|
response.setHeader("Content-Type", "text/plain; charset=UTF-8", false);
|
||||||
|
|
||||||
|
if (request.hasHeader("user-agent")) {
|
||||||
|
response.write(request.getHeader("user-agent"));
|
||||||
|
} else {
|
||||||
|
response.write("no user agent header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInjectedScriptTestRequest(request, response, params) {
|
||||||
|
response.setHeader("Content-Type", "text/html; charset=UTF-8", false);
|
||||||
|
|
||||||
|
const frames = parseInt(params.get("frames"));
|
||||||
|
let content = "";
|
||||||
|
|
||||||
|
if (frames > 0) {
|
||||||
|
// Output an iframe in seamless mode, so that there is an higher chance that in case
|
||||||
|
// of test failures we get a screenshot where the nested iframes are all visible.
|
||||||
|
content = `<iframe seamless src="?test=injected-script&frames=${frames - 1}"></iframe>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.get("stop") == "windowStop") {
|
||||||
|
content = "<script>window.stop();</script>" + content;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.write(`<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
iframe { width: 100%; height: ${frames * 150}px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>IFRAME ${frames}</h1>
|
||||||
|
<pre>injected script NOT executed</pre>
|
||||||
|
<script>
|
||||||
|
window.pageScriptExecutedFirst = true;
|
||||||
|
</script>
|
||||||
|
${content}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
@ -37,5 +37,6 @@ DevToolsModules(
|
||||||
'stylesheets.js',
|
'stylesheets.js',
|
||||||
'timeline.js',
|
'timeline.js',
|
||||||
'webaudio.js',
|
'webaudio.js',
|
||||||
|
'webextension-inspected-window.js',
|
||||||
'webgl.js'
|
'webgl.js'
|
||||||
)
|
)
|
||||||
|
|
|
||||||
27
devtools/shared/fronts/webextension-inspected-window.js
Normal file
27
devtools/shared/fronts/webextension-inspected-window.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* 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/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {
|
||||||
|
webExtensionInspectedWindowSpec,
|
||||||
|
} = require("devtools/shared/specs/webextension-inspected-window");
|
||||||
|
|
||||||
|
const protocol = require("devtools/shared/protocol");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The corresponding Front object for the WebExtensionInspectedWindowActor.
|
||||||
|
*/
|
||||||
|
const WebExtensionInspectedWindowFront = protocol.FrontClassWithSpec(
|
||||||
|
webExtensionInspectedWindowSpec,
|
||||||
|
{
|
||||||
|
initialize: function (client, { webExtensionInspectedWindowActor }) {
|
||||||
|
protocol.Front.prototype.initialize.call(this, client, {
|
||||||
|
actor: webExtensionInspectedWindowActor
|
||||||
|
});
|
||||||
|
this.manage(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
exports.WebExtensionInspectedWindowFront = WebExtensionInspectedWindowFront;
|
||||||
|
|
@ -45,6 +45,7 @@ DevToolsModules(
|
||||||
'stylesheets.js',
|
'stylesheets.js',
|
||||||
'timeline.js',
|
'timeline.js',
|
||||||
'webaudio.js',
|
'webaudio.js',
|
||||||
|
'webextension-inspected-window.js',
|
||||||
'webgl.js',
|
'webgl.js',
|
||||||
'worker.js'
|
'worker.js'
|
||||||
)
|
)
|
||||||
|
|
|
||||||
106
devtools/shared/specs/webextension-inspected-window.js
Normal file
106
devtools/shared/specs/webextension-inspected-window.js
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {
|
||||||
|
Arg,
|
||||||
|
RetVal,
|
||||||
|
generateActorSpec,
|
||||||
|
types,
|
||||||
|
} = require("devtools/shared/protocol");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sent with the eval and reload requests, used to inform the
|
||||||
|
* webExtensionInspectedWindowActor about the caller information
|
||||||
|
* to be able to evaluate code as being executed from the caller
|
||||||
|
* WebExtension sources, or log errors with information that can
|
||||||
|
* help the addon developer to more easily identify the affected
|
||||||
|
* lines in his own addon code.
|
||||||
|
*/
|
||||||
|
types.addDictType("webExtensionCallerInfo", {
|
||||||
|
// Information related to the line of code that has originated
|
||||||
|
// the request.
|
||||||
|
url: "string",
|
||||||
|
lineNumber: "nullable:number",
|
||||||
|
|
||||||
|
// The called addonId.
|
||||||
|
addonId: "string",
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RDP type related to the inspectedWindow.eval method request.
|
||||||
|
*/
|
||||||
|
types.addDictType("webExtensionEvalOptions", {
|
||||||
|
frameURL: "nullable:string",
|
||||||
|
contextSecurityOrigin: "nullable:string",
|
||||||
|
useContentScriptContext: "nullable:boolean",
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RDP type related to the inspectedWindow.eval method result errors.
|
||||||
|
*
|
||||||
|
* This type has been modelled on the same data format
|
||||||
|
* used in the corresponding chrome API method.
|
||||||
|
*/
|
||||||
|
types.addDictType("webExtensionEvalExceptionInfo", {
|
||||||
|
// The following properties are set if the error has not occurred
|
||||||
|
// in the evaluated JS code.
|
||||||
|
isError: "nullable:boolean",
|
||||||
|
code: "nullable:string",
|
||||||
|
description: "nullable:string",
|
||||||
|
details: "nullable:array:json",
|
||||||
|
|
||||||
|
// The following properties are set if the error has occurred
|
||||||
|
// in the evaluated JS code.
|
||||||
|
isException: "nullable:string",
|
||||||
|
value: "nullable:string",
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RDP type related to the inspectedWindow.eval method result.
|
||||||
|
*/
|
||||||
|
types.addDictType("webExtensionEvalResult", {
|
||||||
|
// The following properties are set if the evaluation has been
|
||||||
|
// completed successfully.
|
||||||
|
value: "nullable:json",
|
||||||
|
// The following properties are set if the evalutation has been
|
||||||
|
// completed with errors.
|
||||||
|
exceptionInfo: "nullable:webExtensionEvalExceptionInfo",
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RDP type related to the inspectedWindow.reload method request.
|
||||||
|
*/
|
||||||
|
types.addDictType("webExtensionReloadOptions", {
|
||||||
|
ignoreCache: "nullable:boolean",
|
||||||
|
userAgent: "nullable:string",
|
||||||
|
injectedScript: "nullable:string",
|
||||||
|
});
|
||||||
|
|
||||||
|
const webExtensionInspectedWindowSpec = generateActorSpec({
|
||||||
|
typeName: "webExtensionInspectedWindow",
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
reload: {
|
||||||
|
request: {
|
||||||
|
webExtensionCallerInfo: Arg(0, "webExtensionCallerInfo"),
|
||||||
|
options: Arg(1, "webExtensionReloadOptions"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eval: {
|
||||||
|
request: {
|
||||||
|
webExtensionCallerInfo: Arg(0, "webExtensionCallerInfo"),
|
||||||
|
expression: Arg(1, "string"),
|
||||||
|
options: Arg(2, "webExtensionEvalOptions"),
|
||||||
|
},
|
||||||
|
|
||||||
|
response: {
|
||||||
|
evalResult: RetVal("webExtensionEvalResult"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.webExtensionInspectedWindowSpec = webExtensionInspectedWindowSpec;
|
||||||
Loading…
Reference in a new issue