Backed out 11 changesets (bug 1806598, bug 1812905, bug 1812540) for causing dt failures in devtools/client/webconsole/test/browser/browser_console_evaluation_context_selector.js CLOSED TREE

Backed out changeset 3b0e28b20def (bug 1812540)
Backed out changeset 810ed5832764 (bug 1812540)
Backed out changeset 9e042da8472f (bug 1806598)
Backed out changeset ab04e3122f20 (bug 1806598)
Backed out changeset f97c01add0a5 (bug 1806598)
Backed out changeset de9d04dc5014 (bug 1806598)
Backed out changeset 3808fc5bf528 (bug 1806598)
Backed out changeset 9be2509e8540 (bug 1806598)
Backed out changeset 3fc6c5d9d620 (bug 1806598)
Backed out changeset 297a91be123b (bug 1806598)
Backed out changeset 24cc5069faf3 (bug 1812905)
This commit is contained in:
Sandor Molnar 2023-02-06 07:19:51 +02:00
parent 3e6ba61292
commit 4fcb729dc9
18 changed files with 153 additions and 1013 deletions

View File

@ -16,10 +16,6 @@ function allProperties(obj) {
return matchingProperties(obj, /./);
}
function getter(obj, name) {
return Object.getOwnPropertyDescriptor(obj, name).get;
}
const TypedArray = Reflect.getPrototypeOf(Int8Array);
module.exports = [
@ -162,34 +158,4 @@ module.exports = [
isFinite,
isNaN,
unescape,
getter(ArrayBuffer.prototype, "byteLength"),
getter(ArrayBuffer, Symbol.species),
getter(Array, Symbol.species),
getter(DataView.prototype, "buffer"),
getter(DataView.prototype, "byteLength"),
getter(DataView.prototype, "byteOffset"),
getter(Map.prototype, "size"),
getter(Map, Symbol.species),
// NOTE: Object.prototype.__proto__ is not safe, because it can internally
// invoke Proxy getPrototypeOf handler.
getter(Promise, Symbol.species),
getter(RegExp.prototype, "dotAll"),
getter(RegExp.prototype, "flags"),
getter(RegExp.prototype, "global"),
getter(RegExp.prototype, "hasIndices"),
getter(RegExp.prototype, "ignoreCase"),
getter(RegExp.prototype, "multiline"),
getter(RegExp.prototype, "source"),
getter(RegExp.prototype, "sticky"),
getter(RegExp.prototype, "unicode"),
getter(RegExp, Symbol.species),
getter(Set.prototype, "size"),
getter(Set, Symbol.species),
getter(Symbol.prototype, "description"),
getter(TypedArray.prototype, "buffer"),
getter(TypedArray.prototype, "byteLength"),
getter(TypedArray.prototype, "byteOffset"),
getter(TypedArray.prototype, "length"),
getter(TypedArray.prototype, Symbol.toStringTag),
getter(TypedArray, Symbol.species),
];

View File

@ -6,64 +6,65 @@
const idlPureAllowlist = require("resource://devtools/server/actors/webconsole/webidl-pure-allowlist.js");
// Exclude interfaces only with "instance" property, such as Location,
// which is not available in sandbox.
const props = [];
for (const [iface, ifaceData] of Object.entries(idlPureAllowlist)) {
if ("static" in ifaceData || "prototype" in ifaceData) {
props.push(iface);
// TODO: Bug 1616013 - Move more of these to be part of the pure list.
const customEagerFunctions = {
Document: [
["prototype", "getSelection"],
["prototype", "hasStorageAccess"],
],
Range: [
["prototype", "isPointInRange"],
["prototype", "comparePoint"],
["prototype", "intersectsNode"],
// These two functions aren't pure because they do trigger layout when
// they are called, but in the context of eager evaluation, that should be
// a totally fine thing to do.
["prototype", "getClientRects"],
["prototype", "getBoundingClientRect"],
],
Selection: [
["prototype", "getRangeAt"],
["prototype", "containsNode"],
],
};
const mergedFunctions = {};
for (const [key, values] of Object.entries(idlPureAllowlist)) {
mergedFunctions[key] = [...values];
}
for (const [key, values] of Object.entries(customEagerFunctions)) {
if (!mergedFunctions[key]) {
mergedFunctions[key] = [];
}
mergedFunctions[key].push(...values);
}
const natives = [];
if (Components.Constructor && Cu) {
const sandbox = Cu.Sandbox(
Components.Constructor("@mozilla.org/systemprincipal;1", "nsIPrincipal")(),
{
invisibleToDebugger: true,
wantGlobalProperties: props,
wantGlobalProperties: Object.keys(mergedFunctions),
}
);
function maybePush(maybeFunc) {
if (maybeFunc) {
natives.push(maybeFunc);
}
}
function collectMethodsAndGetters(obj, methodsAndGetters) {
if ("methods" in methodsAndGetters) {
for (const name of methodsAndGetters.methods) {
maybePush(obj[name]);
}
}
if ("getters" in methodsAndGetters) {
for (const name of methodsAndGetters.getters) {
maybePush(Object.getOwnPropertyDescriptor(obj, name)?.get);
}
}
}
for (const [iface, ifaceData] of Object.entries(idlPureAllowlist)) {
const ctor = sandbox[iface];
if (!ctor) {
continue;
}
if ("static" in ifaceData) {
collectMethodsAndGetters(ctor, ifaceData.static);
}
if ("prototype" in ifaceData) {
const proto = ctor.prototype;
if (!proto) {
continue;
for (const iface of Object.keys(mergedFunctions)) {
for (const path of mergedFunctions[iface]) {
let value = sandbox;
for (const part of [iface, ...path]) {
value = value[part];
if (!value) {
break;
}
}
collectMethodsAndGetters(proto, ifaceData.prototype);
if (value) {
natives.push(value);
}
}
}
}
module.exports = { natives, idlPureAllowlist };
module.exports = natives;

View File

@ -107,8 +107,6 @@ function isObject(value) {
* - eager: Set to true if you want the evaluation to bail if it may have side effects.
* - url: the url to evaluate the script as. Defaults to "debugger eval code",
* or "debugger eager eval code" if eager is true.
* @param object webConsole
*
* @return object
* An object that holds the following properties:
* - dbg: the debugger where the string was evaluated.
@ -128,11 +126,7 @@ exports.evalWithDebugger = function(string, options = {}, webConsole) {
const evalString = getEvalInput(string);
const { frame, dbg } = getFrameDbg(options, webConsole);
const { dbgGlobal, bindSelf, evalGlobal } = getDbgGlobal(
options,
dbg,
webConsole
);
const { dbgGlobal, bindSelf } = getDbgGlobal(options, dbg, webConsole);
const helpers = getHelpers(dbgGlobal, options, webConsole);
let { bindings, helperCache } = bindCommands(
isCommand(string),
@ -164,7 +158,7 @@ exports.evalWithDebugger = function(string, options = {}, webConsole) {
let noSideEffectDebugger = null;
if (options.eager) {
noSideEffectDebugger = makeSideeffectFreeDebugger(false, evalGlobal);
noSideEffectDebugger = makeSideeffectFreeDebugger();
}
let result;
@ -363,15 +357,10 @@ function forceLexicalInitForVariableDeclarationsInThrowingExpression(
*
* @param boolean skipCheckingEffectfulOffsets
* If true, effectful offsets are excluded from the checks for side effects.
* @param object maybeEvalGlobal
* If provided, raw debuggee global to get `window` accessors.
* @return object
* Side-effect-free debugger.
*/
function makeSideeffectFreeDebugger(
skipCheckingEffectfulOffsets,
maybeEvalGlobal
) {
function makeSideeffectFreeDebugger(skipCheckingEffectfulOffsets) {
// We ensure that the metadata for native functions is loaded before we
// initialize sideeffect-prevention because the data is lazy-loaded, and this
// logic can run inside of debuggee compartments because the
@ -380,7 +369,7 @@ function makeSideeffectFreeDebugger(
// because building the list of valid native functions is itself a
// side-effectful operation because it needs to populate a
// module cache, among any number of other things.
ensureSideEffectFreeNatives(maybeEvalGlobal);
ensureSideEffectFreeNatives();
// Note: It is critical for debuggee performance that we implement all of
// this debuggee tracking logic with a separate Debugger instance.
@ -436,11 +425,11 @@ function makeSideeffectFreeDebugger(
// so we need to add this hook on "dbg" even though the rest of our hooks work via "newDbg".
dbg.onNativeCall = (callee, reason) => {
try {
// Setters are always effectful. Natives called normally or called via
// getters are handled with an allowlist.
// Getters are never considered effectful, and setters are always effectful.
// Natives called normally are handled with an allowlist.
if (
(reason == "get" || reason == "call") &&
nativeHasNoSideEffects(callee)
reason == "get" ||
(reason == "call" && nativeHasNoSideEffects(callee))
) {
// Returning undefined causes execution to continue normally.
return undefined;
@ -463,77 +452,17 @@ exports.makeSideeffectFreeDebugger = makeSideeffectFreeDebugger;
// Native functions which are considered to be side effect free.
let gSideEffectFreeNatives; // string => Array(Function)
/**
* Generate gSideEffectFreeNatives map.
*
* @param object maybeEvalGlobal
* If provided, raw debuggee global to get `window` accessors.
*/
function ensureSideEffectFreeNatives(maybeEvalGlobal) {
function ensureSideEffectFreeNatives() {
if (gSideEffectFreeNatives) {
return;
}
const { natives: domNatives, idlPureAllowlist } = eagerFunctionAllowlist;
const instanceFunctionAllowlist = [];
function collectMethodsAndGetters(obj, methodsAndGetters) {
// This can retrieve xray function if the obj comes from web content.
// Xray function has original native and JitInfo even if the property is
// modified by the web content.
if ("methods" in methodsAndGetters) {
for (const name of methodsAndGetters.methods) {
const func = obj[name];
if (func) {
instanceFunctionAllowlist.push(func);
}
}
}
if ("getters" in methodsAndGetters) {
for (const name of methodsAndGetters.getters) {
const func = Object.getOwnPropertyDescriptor(obj, name)?.get;
if (func) {
instanceFunctionAllowlist.push(func);
}
}
}
}
// `Window` can be undefined if this is inside sandbox.
if (
maybeEvalGlobal &&
typeof Window === "function" &&
Window.isInstance(maybeEvalGlobal) &&
"Window" in idlPureAllowlist &&
"instance" in idlPureAllowlist.Window
) {
collectMethodsAndGetters(maybeEvalGlobal, idlPureAllowlist.Window.instance);
const maybeLocation = maybeEvalGlobal.location;
if (maybeLocation) {
collectMethodsAndGetters(
maybeLocation,
idlPureAllowlist.Location.instance
);
}
const maybeDocument = maybeEvalGlobal.document;
if (maybeDocument) {
collectMethodsAndGetters(
maybeDocument,
idlPureAllowlist.Document.instance
);
}
}
const natives = [
...eagerEcmaAllowlist,
// Pull in all of the non-ECMAScript native functions that we want to
// allow as well.
...domNatives,
...instanceFunctionAllowlist,
...eagerFunctionAllowlist,
];
const map = new Map();
@ -560,14 +489,8 @@ function nativeHasNoSideEffects(fn) {
return true;
}
// This needs to use isSameNativeWithJitInfo instead of isSameNative, given
// DOM getters share single native function with different JSJitInto,
// and isSameNative cannot distinguish between side-effect-free getters
// and others.
//
// See bug 1806598 for more info.
const natives = gSideEffectFreeNatives.get(fn.name);
return natives && natives.some(n => fn.isSameNativeWithJitInfo(n));
return natives && natives.some(n => fn.isSameNative(n));
}
function updateConsoleInputEvaluation(dbg, webConsole) {
@ -629,22 +552,6 @@ function getFrameDbg(options, webConsole) {
);
}
/**
* Get debugger object for given debugger and Web Console.
*
* @param object options
* See the `options` parameter of evalWithDebugger
* @param {Debugger} dbg
* Debugger object
* @param {WebConsoleActor} webConsole
* A reference to a webconsole actor which is used to get the target
* eval global and optionally the target actor
* @return object
* An object that holds the following properties:
* - bindSelf: (optional) the self object for the evaluation
* - dbgGlobal: the global object reference in the debugger
* - evalGlobal: the raw global object
*/
function getDbgGlobal(options, dbg, webConsole) {
let evalGlobal = webConsole.evalGlobal;
@ -663,7 +570,7 @@ function getDbgGlobal(options, dbg, webConsole) {
// If we have an object to bind to |_self|, create a Debugger.Object
// referring to that object, belonging to dbg.
if (!options.selectedObjectActor) {
return { bindSelf: null, dbgGlobal, evalGlobal };
return { bindSelf: null, dbgGlobal };
}
// For objects related to console messages, they will be registered under the Target Actor
@ -674,12 +581,12 @@ function getDbgGlobal(options, dbg, webConsole) {
webConsole.parentActor.getActorByID(options.selectedObjectActor);
if (!actor) {
return { bindSelf: null, dbgGlobal, evalGlobal };
return { bindSelf: null, dbgGlobal };
}
const jsVal = actor instanceof LongStringActor ? actor.str : actor.rawValue();
if (!isObject(jsVal)) {
return { bindSelf: jsVal, dbgGlobal, evalGlobal };
return { bindSelf: jsVal, dbgGlobal };
}
// If we use the makeDebuggeeValue method of jsVal's own global, then
@ -687,7 +594,7 @@ function getDbgGlobal(options, dbg, webConsole) {
// that is, without wrappers. The evalWithBindings call will then wrap
// jsVal appropriately for the evaluation compartment.
const bindSelf = dbgGlobal.makeDebuggeeValue(jsVal);
return { bindSelf, dbgGlobal, evalGlobal };
return { bindSelf, dbgGlobal };
}
function getHelpers(dbgGlobal, options, webConsole) {

View File

@ -7,212 +7,66 @@
"use strict";
module.exports = {
DOMTokenList: {
prototype: {
getters: ["length", "value"],
methods: ["item", "contains"],
},
},
Document: {
instance: {
getters: ["location"],
},
prototype: {
getters: [
"URL",
"documentURI",
"compatMode",
"characterSet",
"charset",
"inputEncoding",
"contentType",
"doctype",
"documentElement",
"title",
"dir",
"body",
"head",
"images",
"embeds",
"plugins",
"links",
"forms",
"scripts",
"defaultView",
"currentScript",
"anchors",
"applets",
"all",
"styleSheetSets",
"featurePolicy",
"blockedNodeByClassifierCount",
"blockedNodesByClassifier",
"permDelegateHandler",
"children",
"firstElementChild",
"lastElementChild",
"childElementCount",
],
methods: [
"getSelection",
"hasStorageAccess",
"getElementsByTagName",
"getElementsByTagNameNS",
"getElementsByClassName",
"getElementById",
"getElementsByName",
"querySelector",
"querySelectorAll",
"createNSResolver",
],
},
},
Element: {
prototype: {
getters: [
"namespaceURI",
"prefix",
"localName",
"tagName",
"id",
"className",
"classList",
"part",
"attributes",
"innerHTML",
"outerHTML",
"previousElementSibling",
"nextElementSibling",
"children",
"firstElementChild",
"lastElementChild",
"childElementCount",
],
methods: [
"getAttributeNames",
"getAttribute",
"getAttributeNS",
"hasAttribute",
"hasAttributeNS",
"hasAttributes",
"closest",
"matches",
"webkitMatchesSelector",
"getElementsByTagName",
"getElementsByTagNameNS",
"getElementsByClassName",
"mozMatchesSelector",
"querySelector",
"querySelectorAll",
"getAsFlexContainer",
"getGridFragments",
"hasGridFragments",
"getElementsWithGrid",
],
},
},
FormData: {
prototype: {
methods: ["entries", "keys", "values"],
},
},
Headers: {
prototype: {
methods: ["entries", "keys", "values"],
},
},
Location: {
instance: {
getters: [
"href",
"origin",
"protocol",
"host",
"hostname",
"port",
"pathname",
"search",
"hash",
],
},
},
Node: {
prototype: {
getters: [
"nodeType",
"nodeName",
"baseURI",
"isConnected",
"ownerDocument",
"parentNode",
"parentElement",
"childNodes",
"firstChild",
"lastChild",
"previousSibling",
"nextSibling",
"nodeValue",
"textContent",
"flattenedTreeParentNode",
"isNativeAnonymous",
"containingShadowRoot",
"accessibleNode",
],
methods: [
"getRootNode",
"hasChildNodes",
"isSameNode",
"isEqualNode",
"compareDocumentPosition",
"contains",
"lookupPrefix",
"lookupNamespaceURI",
"isDefaultNamespace",
],
},
},
Performance: {
prototype: {
getters: ["timeOrigin", "timing", "navigation", "eventCounts"],
methods: ["now"],
},
},
Range: {
prototype: {
methods: [
"isPointInRange",
"comparePoint",
"intersectsNode",
"getClientRects",
"getBoundingClientRect",
],
},
},
Selection: {
prototype: {
methods: ["getRangeAt", "containsNode"],
},
},
URLSearchParams: {
prototype: {
methods: ["entries", "keys", "values"],
},
},
Window: {
instance: {
getters: [
"location",
"window",
"self",
"document",
"performance",
"browsingContext",
"windowUtils",
"windowGlobalChild",
"visualViewport",
"caches",
"scheduler",
],
},
},
DOMTokenList: [
["prototype", "item"],
["prototype", "contains"],
],
Document: [
["prototype", "getElementsByTagName"],
["prototype", "getElementsByTagNameNS"],
["prototype", "getElementsByClassName"],
["prototype", "getElementById"],
["prototype", "getElementsByName"],
["prototype", "querySelector"],
["prototype", "querySelectorAll"],
["prototype", "createNSResolver"],
],
Element: [
["prototype", "getAttributeNames"],
["prototype", "getAttribute"],
["prototype", "getAttributeNS"],
["prototype", "hasAttribute"],
["prototype", "hasAttributeNS"],
["prototype", "hasAttributes"],
["prototype", "closest"],
["prototype", "matches"],
["prototype", "webkitMatchesSelector"],
["prototype", "getElementsByTagName"],
["prototype", "getElementsByTagNameNS"],
["prototype", "getElementsByClassName"],
["prototype", "mozMatchesSelector"],
["prototype", "querySelector"],
["prototype", "querySelectorAll"],
["prototype", "getAsFlexContainer"],
["prototype", "getGridFragments"],
["prototype", "hasGridFragments"],
["prototype", "getElementsWithGrid"],
],
FormData: [
["prototype", "entries"],
["prototype", "keys"],
["prototype", "values"],
],
Headers: [
["prototype", "entries"],
["prototype", "keys"],
["prototype", "values"],
],
Node: [
["prototype", "getRootNode"],
["prototype", "hasChildNodes"],
["prototype", "isSameNode"],
["prototype", "isEqualNode"],
["prototype", "compareDocumentPosition"],
["prototype", "contains"],
["prototype", "lookupPrefix"],
["prototype", "lookupNamespaceURI"],
["prototype", "isDefaultNamespace"],
],
Performance: [["prototype", "now"]],
URLSearchParams: [
["prototype", "entries"],
["prototype", "keys"],
["prototype", "values"],
],
};

View File

@ -14,9 +14,6 @@ const {
add_task(async () => {
const tab = await addTab(`data:text/html;charset=utf-8,
<!DOCTYPE html>
<html dir="ltr" class="class1">
<head><title>Testcase</title></head>
<script>
window.foobarObject = Object.create(
null,
@ -49,18 +46,7 @@ add_task(async () => {
const aliased = "ALIASED";
return [0].map(() => aliased)[0];
}
var testMap = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]);
var testSet = new Set([1, 2, 3, 4, 5]);
var testProxy = new Proxy({}, { getPrototypeOf: prompt });
var testInt8Array = new Int8Array([1, 2, 3]);
var testArrayBuffer = testInt8Array.buffer;
var testDataView = new DataView(testArrayBuffer, 2);
var testCanvasContext = document.createElement("canvas").getContext("2d");
</script>
<body id="body1" class="class2"><h1>Body text</h1></body>
</html>`);
</script>`);
const commands = await CommandsFactory.forTab(tab);
await commands.targetCommand.startListening();
@ -77,8 +63,6 @@ add_task(async () => {
await doEagerEvalWithSideEffect(commands);
await doEagerEvalWithSideEffectIterator(commands);
await doEagerEvalWithSideEffectMonkeyPatched(commands);
await doEagerEvalESGetters(commands);
await doEagerEvalDOMGetters(commands);
await commands.destroy();
});
@ -427,258 +411,3 @@ async function doEagerEvalWithSideEffectMonkeyPatched(commands) {
result: "ab",
});
}
async function doEagerEvalESGetters(commands) {
// [code, expectedResult]
const testData = [
// ArrayBuffer
["testArrayBuffer.byteLength", 3],
// DataView
["testDataView.buffer === testArrayBuffer", true],
["testDataView.byteLength", 1],
["testDataView.byteOffset", 2],
// Map
["testMap.size", 4],
// RegExp
["/a/.dotAll", false],
["/a/giy.flags", "giy"],
["/a/g.global", true],
["/a/g.hasIndices", false],
["/a/g.ignoreCase", false],
["/a/g.multiline", false],
["/a/g.source", "a"],
["/a/g.sticky", false],
["/a/g.unicode", false],
// Set
["testSet.size", 5],
// Symbol
["Symbol.iterator.description", "Symbol.iterator"],
// TypedArray
["testInt8Array.buffer === testArrayBuffer", true],
["testInt8Array.byteLength", 3],
["testInt8Array.byteOffset", 0],
["testInt8Array.length", 3],
["testInt8Array[Symbol.toStringTag]", "Int8Array"],
];
for (const [code, expectedResult] of testData) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: expectedResult,
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
const testDataWithSideEffect = [
// get Object.prototype.__proto__
//
// This can invoke Proxy getPrototypeOf handler, which can be any native
// function, and debugger cannot hook the call.
`[].__proto__`,
`testProxy.__proto__`,
];
for (const code of testDataWithSideEffect) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: { type: "undefined" },
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
}
async function doEagerEvalDOMGetters(commands) {
// [code, expectedResult]
const testData = [
// DOMTokenList
["document.documentElement.classList.length", 1],
["document.documentElement.classList.value", "class1"],
// Document
["document.URL.startsWith('data:')", true],
["document.documentURI.startsWith('data:')", true],
["document.compatMode", "CSS1Compat"],
["document.characterSet", "UTF-8"],
["document.charset", "UTF-8"],
["document.inputEncoding", "UTF-8"],
["document.contentType", "text/html"],
["document.doctype.constructor.name", "DocumentType"],
["document.documentElement.constructor.name", "HTMLHtmlElement"],
["document.title", "Testcase"],
["document.dir", "ltr"],
["document.body.constructor.name", "HTMLBodyElement"],
["document.head.constructor.name", "HTMLHeadElement"],
["document.images.constructor.name", "HTMLCollection"],
["document.embeds.constructor.name", "HTMLCollection"],
["document.plugins.constructor.name", "HTMLCollection"],
["document.links.constructor.name", "HTMLCollection"],
["document.forms.constructor.name", "HTMLCollection"],
["document.scripts.constructor.name", "HTMLCollection"],
["document.defaultView === window", true],
["typeof document.currentScript", "object"],
["document.anchors.constructor.name", "HTMLCollection"],
["document.applets.constructor.name", "HTMLCollection"],
["document.all.constructor.name", "HTMLAllCollection"],
["document.styleSheetSets.constructor.name", "DOMStringList"],
["typeof document.featurePolicy", "undefined"],
["typeof document.blockedNodeByClassifierCount", "undefined"],
["typeof document.blockedNodesByClassifier", "undefined"],
["typeof document.permDelegateHandler", "undefined"],
["document.children.constructor.name", "HTMLCollection"],
["document.firstElementChild === document.documentElement", true],
["document.lastElementChild === document.documentElement", true],
["document.childElementCount", 1],
["document.location.href.startsWith('data:')", true],
// Element
["document.body.namespaceURI", "http://www.w3.org/1999/xhtml"],
["document.body.prefix === null", true],
["document.body.localName", "body"],
["document.body.tagName", "BODY"],
["document.body.id", "body1"],
["document.body.className", "class2"],
["document.body.classList.constructor.name", "DOMTokenList"],
["document.body.part.constructor.name", "DOMTokenList"],
["document.body.attributes.constructor.name", "NamedNodeMap"],
["document.body.innerHTML.includes('Body text')", true],
["document.body.outerHTML.includes('Body text')", true],
["document.body.previousElementSibling !== null", true],
["document.body.nextElementSibling === null", true],
["document.body.children.constructor.name", "HTMLCollection"],
["document.body.firstElementChild !== null", true],
["document.body.lastElementChild !== null", true],
["document.body.childElementCount", 1],
// Node
["document.body.nodeType === Node.ELEMENT_NODE", true],
["document.body.nodeName", "BODY"],
["document.body.baseURI.startsWith('data:')", true],
["document.body.isConnected", true],
["document.body.ownerDocument === document", true],
["document.body.parentNode === document.documentElement", true],
["document.body.parentElement === document.documentElement", true],
["document.body.childNodes.constructor.name", "NodeList"],
["document.body.firstChild !== null", true],
["document.body.lastChild !== null", true],
["document.body.previousSibling !== null", true],
["document.body.nextSibling === null", true],
["document.body.nodeValue === null", true],
["document.body.textContent.includes('Body text')", true],
["typeof document.body.flattenedTreeParentNode", "undefined"],
["typeof document.body.isNativeAnonymous", "undefined"],
["typeof document.body.containingShadowRoot", "undefined"],
["typeof document.body.accessibleNode", "undefined"],
// Performance
["performance.timeOrigin > 0", true],
["performance.timing.constructor.name", "PerformanceTiming"],
["performance.navigation.constructor.name", "PerformanceNavigation"],
["performance.eventCounts.constructor.name", "EventCounts"],
// window
["window.window === window", true],
["window.self === window", true],
["window.document.constructor.name", "HTMLDocument"],
["window.performance.constructor.name", "Performance"],
["typeof window.browsingContext", "undefined"],
["typeof window.windowUtils", "undefined"],
["typeof window.windowGlobalChild", "undefined"],
["window.visualViewport.constructor.name", "VisualViewport"],
["typeof window.caches", "undefined"],
["window.scheduler.constructor.name", "Scheduler"],
["window.location.href.startsWith('data:')", true],
];
for (const [code, expectedResult] of testData) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: expectedResult,
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
const testDataWithSideEffect = [
// NOTE: This is not an exhaustive list.
// Document
`document.implementation`,
`document.domain`,
`document.referrer`,
`document.cookie`,
`document.lastModified`,
`document.readyState`,
`document.designMode`,
`document.onbeforescriptexecute`,
`document.onafterscriptexecute`,
// Element
`document.documentElement.scrollTop`,
`document.documentElement.scrollLeft`,
`document.documentElement.scrollWidth`,
`document.documentElement.scrollHeight`,
// Performance
`performance.onresourcetimingbufferfull`,
// window
`window.name`,
`window.history`,
`window.customElements`,
`window.locationbar`,
`window.menubar`,
`window.status`,
`window.closed`,
// CanvasRenderingContext2D / CanvasCompositing
`testCanvasContext.globalAlpha`,
];
for (const code of testDataWithSideEffect) {
const response = await commands.scriptCommand.execute(code, {
eager: true,
});
checkObject(
response,
{
input: code,
result: { type: "undefined" },
},
code
);
ok(!response.exception, "no eval exception");
ok(!response.helperResult, "no helper result");
}
}

View File

@ -9,7 +9,7 @@ Services.scriptloader.loadSubScript(
this
);
function checkObject(object, expected, message) {
function checkObject(object, expected) {
if (object && object.getGrip) {
object = object.getGrip();
}
@ -17,35 +17,28 @@ function checkObject(object, expected, message) {
for (const name of Object.keys(expected)) {
const expectedValue = expected[name];
const value = object[name];
checkValue(name, value, expectedValue, message);
checkValue(name, value, expectedValue);
}
}
function checkValue(name, value, expected, message) {
if (message) {
message = ` for '${message}'`;
}
function checkValue(name, value, expected) {
if (expected === null) {
is(value, null, `'${name}' is null${message}`);
is(value, null, `'${name}' is null`);
} else if (expected === undefined) {
is(value, expected, `'${name}' is undefined${message}`);
is(value, expected, `'${name}' is undefined`);
} else if (
typeof expected == "string" ||
typeof expected == "number" ||
typeof expected == "boolean"
) {
is(value, expected, "property '" + name + "'" + message);
is(value, expected, "property '" + name + "'");
} else if (expected instanceof RegExp) {
ok(
expected.test(value),
name + ": " + expected + " matched " + value + message
);
ok(expected.test(value), name + ": " + expected + " matched " + value);
} else if (Array.isArray(expected)) {
info("checking array for property '" + name + "'" + message);
checkObject(value, expected, message);
info("checking array for property '" + name + "'");
checkObject(value, expected);
} else if (typeof expected == "object") {
info("checking object for property '" + name + "'" + message);
checkObject(value, expected, message);
info("checking object for property '" + name + "'");
checkObject(value, expected);
}
}

View File

@ -21,7 +21,6 @@ import buildconfig
# that we don't care about in the context of the devtools.
PURE_INTERFACE_ALLOWLIST = set(
[
"Window",
"Document",
"Node",
"DOMTokenList",
@ -78,101 +77,28 @@ for filepath in file_list["webidls"]:
parser.parse(f.read(), filepath)
results = parser.finish()
# TODO: Bug 1616013 - Move more of these to be part of the pure list.
pure_output = {
"Document": {
"instance": {
"getters": [
"location",
],
},
"prototype": {
"methods": [
"getSelection",
"hasStorageAccess",
],
},
},
"Range": {
"prototype": {
"methods": [
"isPointInRange",
"comparePoint",
"intersectsNode",
# These two functions aren't pure because they do trigger
# layout when they are called, but in the context of eager
# evaluation, that should be a totally fine thing to do.
"getClientRects",
"getBoundingClientRect",
],
}
},
"Selection": {
"prototype": {
"methods": ["getRangeAt", "containsNode"],
}
},
"Window": {
"instance": {
"getters": [
"location",
],
},
},
"Location": {
"instance": {
"getters": [
"href",
"origin",
"protocol",
"host",
"hostname",
"port",
"pathname",
"search",
"hash",
],
},
},
}
pure_output = {}
deprecated_output = {}
for result in results:
if isinstance(result, WebIDL.IDLInterface):
iface = result.identifier.name
is_global = result.getExtendedAttribute("Global")
for member in result.members:
name = member.identifier.name
if (member.isMethod() or member.isAttr()) and member.affects == "Nothing":
# We only care about methods because eager evaluation assumes that
# all getter functions are side-effect-free.
if member.isMethod() and member.affects == "Nothing":
if (
PURE_INTERFACE_ALLOWLIST and not iface in PURE_INTERFACE_ALLOWLIST
) or name.startswith("_"):
continue
if iface not in pure_output:
pure_output[iface] = {}
if is_global:
owner_type = "instance"
elif member.isStatic():
owner_type = "static"
if not iface in pure_output:
pure_output[iface] = []
if member.isStatic():
pure_output[iface].append([name])
else:
owner_type = "prototype"
if owner_type not in pure_output[iface]:
pure_output[iface][owner_type] = {}
if member.isMethod():
prop_type = "methods"
else:
prop_type = "getters"
if prop_type not in pure_output[iface][owner_type]:
pure_output[iface][owner_type][prop_type] = []
pure_output[iface][owner_type][prop_type].append(name)
pure_output[iface].append(["prototype", name])
if (
not iface in DEPRECATED_INTERFACE__EXCLUDE_LIST
and (member.isMethod() or member.isAttr())

View File

@ -1530,11 +1530,6 @@ static bool XrayResolveAttribute(
cacheOnHolder = true;
JS::Rooted<jsid> getterId(cx);
if (!JS::ToGetterId(cx, id, &getterId)) {
return false;
}
// Because of centralization, we need to make sure we fault in the JitInfos as
// well. At present, until the JSAPI changes, the easiest way to do this is
// wrap them up as functions ourselves.
@ -1542,21 +1537,16 @@ static bool XrayResolveAttribute(
// They all have getters, so we can just make it.
JS::Rooted<JSObject*> getter(
cx, XrayCreateFunction(cx, wrapper, attrSpec.u.accessors.getter.native, 0,
getterId));
id));
if (!getter) {
return false;
}
JS::Rooted<JSObject*> setter(cx);
if (attrSpec.u.accessors.setter.native.op) {
JS::Rooted<jsid> setterId(cx);
if (!JS::ToSetterId(cx, id, &setterId)) {
return false;
}
// We have a setter! Make it.
setter = XrayCreateFunction(cx, wrapper, attrSpec.u.accessors.setter.native,
1, setterId);
1, id);
if (!setter) {
return false;
}

View File

@ -255,16 +255,6 @@ MOZ_ALWAYS_INLINE void AssertIdIsNotGray(jsid id) {
extern JS_PUBLIC_API PropertyKey GetWellKnownSymbolKey(JSContext* cx,
SymbolCode which);
/**
* Generate getter/setter id for given id, by adding "get " or "set " prefix.
*/
extern JS_PUBLIC_API bool ToGetterId(
JSContext* cx, JS::Handle<JS::PropertyKey> id,
JS::MutableHandle<JS::PropertyKey> getterId);
extern JS_PUBLIC_API bool ToSetterId(
JSContext* cx, JS::Handle<JS::PropertyKey> id,
JS::MutableHandle<JS::PropertyKey> setterId);
} // namespace JS
namespace js {

View File

@ -970,22 +970,19 @@ NativeResumeMode DebugAPI::slowPathOnNativeCall(JSContext* cx,
// The onNativeCall hook is fired when self hosted functions are called,
// and any other self hosted function or C++ native that is directly called
// by the self hosted function is considered to be part of the same
// native call, except for the following 4 cases:
// native call, except for the following 2 cases:
//
// * callContentFunction and constructContentFunction,
// which uses CallReason::CallContent
// * Function.prototype.call and Function.prototype.apply,
// which uses CallReason::FunCall
// * Getter call which uses CallReason::Getter
// * Setter call which uses CallReason::Setter
//
// We check this only after checking that debuggerList has items in order
// to avoid unnecessary calls to cx->currentScript(), which can be expensive
// when the top frame is in jitcode.
JSScript* script = cx->currentScript();
if (script && script->selfHosted() && reason != CallReason::CallContent &&
reason != CallReason::FunCall && reason != CallReason::Getter &&
reason != CallReason::Setter) {
reason != CallReason::FunCall) {
return NativeResumeMode::Continue;
}

View File

@ -207,7 +207,6 @@ struct MOZ_STACK_CLASS DebuggerObject::CallData {
bool makeDebuggeeValueMethod();
bool makeDebuggeeNativeFunctionMethod();
bool isSameNativeMethod();
bool isSameNativeWithJitInfoMethod();
bool unsafeDereferenceMethod();
bool unwrapMethod();
bool getPromiseReactionsMethod();
@ -1331,18 +1330,7 @@ bool DebuggerObject::CallData::isSameNativeMethod() {
return false;
}
return DebuggerObject::isSameNative(cx, object, args[0], CheckJitInfo::No,
args.rval());
}
bool DebuggerObject::CallData::isSameNativeWithJitInfoMethod() {
if (!args.requireAtLeast(
cx, "Debugger.Object.prototype.isSameNativeWithJitInfo", 1)) {
return false;
}
return DebuggerObject::isSameNative(cx, object, args[0], CheckJitInfo::Yes,
args.rval());
return DebuggerObject::isSameNative(cx, object, args[0], args.rval());
}
bool DebuggerObject::CallData::unsafeDereferenceMethod() {
@ -1532,7 +1520,6 @@ const JSFunctionSpec DebuggerObject::methods_[] = {
JS_DEBUG_FN("makeDebuggeeNativeFunction", makeDebuggeeNativeFunctionMethod,
1),
JS_DEBUG_FN("isSameNative", isSameNativeMethod, 1),
JS_DEBUG_FN("isSameNativeWithJitInfo", isSameNativeWithJitInfoMethod, 1),
JS_DEBUG_FN("unsafeDereference", unsafeDereferenceMethod, 0),
JS_DEBUG_FN("unwrap", unwrapMethod, 0),
JS_DEBUG_FN("getPromiseReactions", getPromiseReactionsMethod, 0),
@ -2553,36 +2540,9 @@ static JSAtom* MaybeGetSelfHostedFunctionName(const Value& v) {
return GetClonedSelfHostedFunctionName(fun);
}
static bool IsSameNative(JSFunction* a, JSFunction* b,
DebuggerObject::CheckJitInfo checkJitInfo) {
if (a->native() != b->native()) {
return false;
}
if (checkJitInfo == DebuggerObject::CheckJitInfo::No) {
return true;
}
// Both function should agree with the existence of JitInfo.
if (a->hasJitInfo() != b->hasJitInfo()) {
return false;
}
if (!a->hasJitInfo()) {
return true;
}
if (a->jitInfo() == b->jitInfo()) {
return true;
}
return false;
}
/* static */
bool DebuggerObject::isSameNative(JSContext* cx, Handle<DebuggerObject*> object,
HandleValue value, CheckJitInfo checkJitInfo,
HandleValue value,
MutableHandleValue result) {
RootedValue referentValue(cx, ObjectValue(*object->referent()));
@ -2605,9 +2565,7 @@ bool DebuggerObject::isSameNative(JSContext* cx, Handle<DebuggerObject*> object,
}
RootedFunction referentFun(cx, EnsureNativeFunction(referentValue));
result.setBoolean(referentFun &&
IsSameNative(referentFun, fun, checkJitInfo));
result.setBoolean(referentFun && referentFun->native() == fun->native());
return true;
}

View File

@ -148,11 +148,9 @@ class DebuggerObject : public NativeObject {
[[nodiscard]] static bool makeDebuggeeNativeFunction(
JSContext* cx, Handle<DebuggerObject*> object, HandleValue value,
MutableHandleValue result);
enum class CheckJitInfo { No, Yes };
[[nodiscard]] static bool isSameNative(JSContext* cx,
Handle<DebuggerObject*> object,
HandleValue value,
CheckJitInfo checkJitInfo,
MutableHandleValue result);
[[nodiscard]] static bool unsafeDereference(JSContext* cx,
Handle<DebuggerObject*> object,

View File

@ -476,15 +476,6 @@ compartment wrapper.
If <i>value</i> is a native function in the debugger's compartment, return
whether the referent is a native function for the same C++ native.
### `isSameNativeWithJitInfo(value)`
If <i>value</i> is a native function in the debugger's compartment, return
whether the referent is a native function for the same C++ native with the
same JSJitInfo pointer value.
This can be used to distinguish functions with shared native function
implementation with different JSJitInfo pointer to define the underlying
functionality.
### `decompile([pretty])`
If the referent is a function that is debuggee code, return the
JavaScript source code for a function definition equivalent to the

View File

@ -7,7 +7,7 @@ var g = newGlobal({ newCompartment: true });
var dbg = new Debugger();
var gdbg = dbg.addDebuggee(g);
let rv = [];
const rv = [];
dbg.onNativeCall = (callee, reason) => {
rv.push(callee.name);
};
@ -26,43 +26,8 @@ gdbg.executeInGlobal(`
"abc".match(/a./);
`);
assertEqArray(rv, [
"map", "get [Symbol.species]", "push", "push", "push",
"map", "get [Symbol.species]", "padStart", "padStart", "padStart",
"map", "get [Symbol.species]", "dateNow", "dateNow", "dateNow",
"map", "push", "push", "push",
"map", "padStart", "padStart", "padStart",
"map", "dateNow", "dateNow", "dateNow",
"match", "[Symbol.match]",
]);
rv = [];
gdbg.executeInGlobal(`
// Nested getters called internally inside self-hosted.
const r = /a./;
r.foo = 10;
"abc".match(r);
// Setter inside self-hosted JS.
// Hook "A.length = n" at the end of Array.prototype.concat.
var a = [1, 2, 3];
Object.defineProperty(a, "constructor", {
get() {
return class {
constructor() {
let obj = {};
Object.defineProperty(obj, "length", { set: Array.prototype.join });
return obj;
}
static get [Symbol.species]() {
return this;
}
}
}
});
void Array.prototype.concat.call(a, [10, 20, 30]);
`);
assertEqArray(rv, [
"match", "[Symbol.match]",
"get flags",
"get hasIndices", "get global", "get ignoreCase", "get multiline",
"get dotAll", "get unicode", "get sticky",
"defineProperty", "call", "concat", "defineProperty", "join",
]);

View File

@ -1,32 +0,0 @@
var g = newGlobal({newCompartment: true});
var dbg = Debugger(g);
var gdbg = dbg.addDebuggee(g);
assertEq(gdbg.getProperty("print").return.isSameNativeWithJitInfo(print), true);
assertEq(gdbg.getProperty("print").return.isSameNativeWithJitInfo(newGlobal), false);
// FakeDOMObject's accessor shares the single native functions, with
// different JSJitInfo for each.
gdbg.executeInGlobal(`
var fun1 = Object.getOwnPropertyDescriptor(FakeDOMObject.prototype, "x").get;
var fun2 = Object.getOwnPropertyDescriptor(FakeDOMObject.prototype, "slot").get;
`);
var g_fun1 = gdbg.executeInGlobal("fun1").return;
var g_fun2 = gdbg.executeInGlobal("fun2").return;
var fun1 = Object.getOwnPropertyDescriptor(FakeDOMObject.prototype, "x").get;
var fun2 = Object.getOwnPropertyDescriptor(FakeDOMObject.prototype, "slot").get;
// isSameNative doesn't distinguish between fun1 and fun2.
assertEq(g_fun1.isSameNative(fun1), true);
assertEq(g_fun1.isSameNative(fun2), true);
assertEq(g_fun2.isSameNative(fun1), true);
assertEq(g_fun2.isSameNative(fun2), true);
// isSameNativeWithJitInfo can distinguish between fun1 and fun2.
assertEq(g_fun1.isSameNativeWithJitInfo(fun1), true);
assertEq(g_fun1.isSameNativeWithJitInfo(fun2), false);
assertEq(g_fun2.isSameNativeWithJitInfo(fun1), false);
assertEq(g_fun2.isSameNativeWithJitInfo(fun2), true);

View File

@ -96,7 +96,6 @@ UNIFIED_SOURCES += [
"testProfileStrings.cpp",
"testPromise.cpp",
"testPropCache.cpp",
"testPropertyKey.cpp",
"testRecordTupleToSource.cpp",
"testRegExp.cpp",
"testResolveRecursion.cpp",

View File

@ -1,69 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "js/Id.h" // JS::PropertyKey, JS::GetWellKnownSymbolKey, JS::ToGetterId, JS::ToSetterId
#include "js/String.h" // JSString, JS_AtomizeString, JS_StringEqualsAscii
#include "js/Symbol.h" // JS::Symbol, JS::SymbolCode
#include "jsapi-tests/tests.h"
BEGIN_TEST(testPropertyKeyGetterAndSetter) {
JS::Rooted<JSString*> str(cx, JS_AtomizeString(cx, "prop"));
CHECK(str);
JS::Rooted<JS::PropertyKey> strId(cx, JS::PropertyKey::NonIntAtom(str));
MOZ_ASSERT(strId.isString());
JS::Rooted<JS::PropertyKey> symId(
cx, JS::GetWellKnownSymbolKey(cx, JS::SymbolCode::iterator));
MOZ_ASSERT(symId.isSymbol());
JS::Rooted<JS::PropertyKey> numId(cx, JS::PropertyKey::Int(42));
MOZ_ASSERT(numId.isInt());
bool match;
JS::Rooted<JS::PropertyKey> strGetterId(cx);
CHECK(JS::ToGetterId(cx, strId, &strGetterId));
CHECK(strGetterId.isString());
CHECK(JS_StringEqualsAscii(cx, strGetterId.toString(), "get prop", &match));
CHECK(match);
JS::Rooted<JS::PropertyKey> strSetterId(cx);
CHECK(JS::ToSetterId(cx, strId, &strSetterId));
CHECK(strSetterId.isString());
CHECK(JS_StringEqualsAscii(cx, strSetterId.toString(), "set prop", &match));
CHECK(match);
JS::Rooted<JS::PropertyKey> symGetterId(cx);
CHECK(JS::ToGetterId(cx, symId, &symGetterId));
CHECK(symGetterId.isString());
CHECK(JS_StringEqualsAscii(cx, symGetterId.toString(),
"get [Symbol.iterator]", &match));
CHECK(match);
JS::Rooted<JS::PropertyKey> symSetterId(cx);
CHECK(JS::ToSetterId(cx, symId, &symSetterId));
CHECK(symSetterId.isString());
CHECK(JS_StringEqualsAscii(cx, symSetterId.toString(),
"set [Symbol.iterator]", &match));
CHECK(match);
JS::Rooted<JS::PropertyKey> numGetterId(cx);
CHECK(JS::ToGetterId(cx, numId, &numGetterId));
CHECK(numGetterId.isString());
CHECK(JS_StringEqualsAscii(cx, numGetterId.toString(), "get 42", &match));
CHECK(match);
JS::Rooted<JS::PropertyKey> numSetterId(cx);
CHECK(JS::ToSetterId(cx, numId, &numSetterId));
CHECK(numSetterId.isString());
CHECK(JS_StringEqualsAscii(cx, numSetterId.toString(), "set 42", &match));
CHECK(match);
return true;
}
END_TEST(testPropertyKeyGetterAndSetter)

View File

@ -76,7 +76,6 @@
#include "vm/EnvironmentObject.h"
#include "vm/ErrorObject.h"
#include "vm/ErrorReporting.h"
#include "vm/FunctionPrefixKind.h"
#include "vm/Interpreter.h"
#include "vm/JSAtom.h"
#include "vm/JSAtomState.h"
@ -3390,28 +3389,6 @@ JS_PUBLIC_API JS::PropertyKey JS::GetWellKnownSymbolKey(JSContext* cx,
return PropertyKey::Symbol(cx->wellKnownSymbols().get(which));
}
static bool AddPrefix(JSContext* cx, JS::Handle<JS::PropertyKey> id,
FunctionPrefixKind prefixKind,
JS::MutableHandle<JS::PropertyKey> out) {
JS::Rooted<JSAtom*> atom(cx, js::IdToFunctionName(cx, id, prefixKind));
if (!atom) {
return false;
}
out.set(JS::PropertyKey::NonIntAtom(atom));
return true;
}
JS_PUBLIC_API bool JS::ToGetterId(JSContext* cx, JS::Handle<JS::PropertyKey> id,
JS::MutableHandle<JS::PropertyKey> getterId) {
return AddPrefix(cx, id, FunctionPrefixKind::Get, getterId);
}
JS_PUBLIC_API bool JS::ToSetterId(JSContext* cx, JS::Handle<JS::PropertyKey> id,
JS::MutableHandle<JS::PropertyKey> setterId) {
return AddPrefix(cx, id, FunctionPrefixKind::Set, setterId);
}
#ifdef DEBUG
static bool PropertySpecNameIsDigits(JSPropertySpec::Name name) {
if (name.isSymbol()) {