diff options
Diffstat (limited to 'devtools/server/tests/chrome')
113 files changed, 9130 insertions, 0 deletions
diff --git a/devtools/server/tests/chrome/.eslintrc.js b/devtools/server/tests/chrome/.eslintrc.js new file mode 100644 index 0000000000..e563d6bea6 --- /dev/null +++ b/devtools/server/tests/chrome/.eslintrc.js @@ -0,0 +1,6 @@ +"use strict"; + +module.exports = { + // Extend from the common devtools mochitest eslintrc config. + extends: "../../../.eslintrc.mochitests.js", +}; diff --git a/devtools/server/tests/chrome/Debugger.Source.prototype.element-2.js b/devtools/server/tests/chrome/Debugger.Source.prototype.element-2.js new file mode 100644 index 0000000000..7260431428 --- /dev/null +++ b/devtools/server/tests/chrome/Debugger.Source.prototype.element-2.js @@ -0,0 +1,4 @@ +"use strict"; + +// eslint-disable-next-line no-debugger +debugger; diff --git a/devtools/server/tests/chrome/Debugger.Source.prototype.element.html b/devtools/server/tests/chrome/Debugger.Source.prototype.element.html new file mode 100644 index 0000000000..6959ad970d --- /dev/null +++ b/devtools/server/tests/chrome/Debugger.Source.prototype.element.html @@ -0,0 +1,25 @@ +<head> + <!-- Static (not dynamically inserted) inline script. --> + <script id='franz'> + /* exported franz */ + "use strict"; + + function franz() { + // eslint-disable-next-line no-debugger + debugger; + } + </script> + + <!-- Static out-of-line script element. --> + <script id='heinrich' src='Debugger.Source.prototype.element.js'></script> +</head> + +<!-- HTML requires some body element onfoo attributes to add handlers to the + *window*, not the element --- but Debugger.Source.prototype.element should + return the element. Here, that rule should apply to the body's 'onresize' + handler. (For the reason for the 'cancelable' check, see the code that + sends the event.) --> +<body onresize='if (event.cancelable) debugger;'> + <!-- Ordinary content element with event handler. --> + <div id='heidi' onclick='heinrichFun();'>Heidi</div> +</body> diff --git a/devtools/server/tests/chrome/Debugger.Source.prototype.element.js b/devtools/server/tests/chrome/Debugger.Source.prototype.element.js new file mode 100644 index 0000000000..095398ddad --- /dev/null +++ b/devtools/server/tests/chrome/Debugger.Source.prototype.element.js @@ -0,0 +1,7 @@ +/* exported heinrichFun */ +/* global franz */ +"use strict"; + +function heinrichFun() { + franz(); +} diff --git a/devtools/server/tests/chrome/chrome.ini b/devtools/server/tests/chrome/chrome.ini new file mode 100644 index 0000000000..2ef1d8385b --- /dev/null +++ b/devtools/server/tests/chrome/chrome.ini @@ -0,0 +1,105 @@ +[DEFAULT] +tags = devtools +skip-if = os == 'android' +support-files = + doc_Debugger.Source.prototype.introductionType.xhtml + Debugger.Source.prototype.element.js + Debugger.Source.prototype.element-2.js + Debugger.Source.prototype.element.html + framerate-helpers.js + hello-actor.js + iframe1_makeGlobalObjectReference.html + iframe2_makeGlobalObjectReference.html + inspector_css-properties.html + inspector_display-type.html + inspector_getImageData.html + inspector_getOffsetParent.html + inspector-delay-image-response.sjs + inspector-eyedropper.html + inspector-helpers.js + inspector-search-data.html + inspector-styles-data.css + inspector-styles-data.html + inspector-template.html + inspector-traversal-data.html + large-image.jpg + memory-helpers.js + nonchrome_unsafeDereference.html + suspendTimeouts_content.html + suspendTimeouts_content.js + suspendTimeouts_worker.js + small-image.gif + test_suspendTimeouts.js + webconsole-helpers.js + webextension-helpers.js + inactive-property-helper/*.js +[test_animation-type-longhand.html] +[test_css-logic.html] +[test_css-logic-media-queries.html] +[test_css-logic-specificity.html] +[test_css-properties.html] +[test_Debugger.Source.prototype.introductionScript.html] +[test_Debugger.Source.prototype.introductionType.html] +[test_Debugger.Source.prototype.element.html] +[test_Debugger.Script.prototype.global.html] +[test_device.html] +[test_executeInGlobal-outerized_this.html] +[test_framerate_01.html] +[test_framerate_02.html] +[test_framerate_03.html] +[test_framerate_04.html] +[test_framerate_05.html] +[test_framerate_06.html] +[test_highlighter_paused_debugger.html] +[test_inspector-changeattrs.html] +[test_inspector-changevalue.html] +[test_inspector-dead-nodes.html] +[test_inspector-display-type.html] +[test_inspector-duplicate-node.html] +[test_inspector_getImageData.html] +[test_inspector_getImageDataFromURL.html] +[test_inspector_getImageData-wait-for-load.html] +[test_inspector_getNodeFromActor.html] +[test_inspector_getOffsetParent.html] +[test_inspector-hide.html] +[test_inspector-inactive-property-helper.html] +[test_inspector-mutations-attr.html] +[test_inspector-mutations-events.html] +[test_inspector-mutations-value.html] +[test_inspector-pick-color.html] +[test_inspector-pseudoclass-lock.html] +[test_inspector-reload.html] +[test_inspector-resize.html] +[test_inspector-resolve-url.html] +[test_inspector-search-front.html] +[test_inspector-scroll-into-view.html] +[test_inspector-template.html] +[test_makeGlobalObjectReference.html] +[test_memory.html] +[test_memory_allocations_02.html] +[test_memory_allocations_03.html] +[test_memory_allocations_04.html] +[test_memory_allocations_05.html] +[test_memory_allocations_06.html] +[test_memory_allocations_07.html] +[test_memory_attach_01.html] +[test_memory_attach_02.html] +[test_memory_census.html] +[test_memory_gc_01.html] +[test_memory_gc_events.html] +[test_overflowing-children.html] +[test_overflowing-body.html] +[test_preference.html] +[test_styles-applied.html] +[test_styles-computed.html] +[test_styles-layout.html] +[test_styles-matched.html] +[test_styles-modify.html] +[test_styles-svg.html] +[test_unsafeDereference.html] +[test_webconsole-node-grip.html] +[test_webextension-addon-debugging-connect.html] +disabled = true # Bug 1467313: broken, needs repair or removal +[test_webextension-addon-debugging-reload.html] +disabled = true # Bug 1467313: broken, needs repair or removal +[test_suspendTimeouts.html] diff --git a/devtools/server/tests/chrome/doc_Debugger.Source.prototype.introductionType.xhtml b/devtools/server/tests/chrome/doc_Debugger.Source.prototype.introductionType.xhtml new file mode 100644 index 0000000000..b037190c9a --- /dev/null +++ b/devtools/server/tests/chrome/doc_Debugger.Source.prototype.introductionType.xhtml @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'> +<script id='xulie'> +/* eslint-disable strict, no-unused-vars, no-debugger */ +function xulScriptFunc() { debugger; } +</script> +</window> diff --git a/devtools/server/tests/chrome/framerate-helpers.js b/devtools/server/tests/chrome/framerate-helpers.js new file mode 100644 index 0000000000..1539e8b517 --- /dev/null +++ b/devtools/server/tests/chrome/framerate-helpers.js @@ -0,0 +1,63 @@ +/* exported getTargetForSelectedTab, waitFor, plotFPS */ +"use strict"; +const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); +const { TargetFactory } = require("devtools/client/framework/target"); +const Services = require("Services"); + +// Always log packets when running tests. +Services.prefs.setBoolPref("devtools.debugger.log", true); +SimpleTest.registerCleanupFunction(function() { + Services.prefs.clearUserPref("devtools.debugger.log"); +}); + +SimpleTest.waitForExplicitFinish(); + +/** + * Add a new test tab in the browser and load the given url. + * @return Promise a promise that resolves to the new target representing + * the page currently opened. + */ +function getTargetForSelectedTab() { + // Get the target and get the necessary front + const { gBrowser } = Services.wm.getMostRecentWindow("navigator:browser"); + return TargetFactory.forTab(gBrowser.selectedTab); +} + +function waitFor(time) { + return new Promise(resolve => setTimeout(resolve, time)); +} + +function plotFPS(ticks, interval = 100, clamp = 60) { + const timeline = []; + const totalTicks = ticks.length; + + // If the refresh driver didn't get a chance to tick before the + // recording was stopped, assume framerate was 0. + if (totalTicks == 0) { + timeline.push({ delta: 0, value: 0 }); + timeline.push({ delta: interval, value: 0 }); + return timeline; + } + + let frameCount = 0; + let prevTime = ticks[0]; + + for (let i = 1; i < totalTicks; i++) { + const currTime = ticks[i]; + frameCount++; + + const elapsedTime = currTime - prevTime; + if (elapsedTime < interval) { + continue; + } + + const framerate = Math.min(1000 / (elapsedTime / frameCount), clamp); + timeline.push({ delta: prevTime, value: framerate }); + timeline.push({ delta: currTime, value: framerate }); + + frameCount = 0; + prevTime = currTime; + } + + return timeline; +} diff --git a/devtools/server/tests/chrome/hello-actor.js b/devtools/server/tests/chrome/hello-actor.js new file mode 100644 index 0000000000..a0ccbf2727 --- /dev/null +++ b/devtools/server/tests/chrome/hello-actor.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* exported HelloActor */ +"use strict"; + +const protocol = require("devtools/shared/protocol"); + +const helloSpec = protocol.generateActorSpec({ + typeName: "helloActor", + + methods: { + count: { + request: {}, + response: { count: protocol.RetVal("number") }, + }, + }, +}); + +var HelloActor = protocol.ActorClassWithSpec(helloSpec, { + initialize: function() { + protocol.Actor.prototype.initialize.apply(this, arguments); + this.counter = 0; + }, + + count: function() { + return ++this.counter; + }, +}); diff --git a/devtools/server/tests/chrome/iframe1_makeGlobalObjectReference.html b/devtools/server/tests/chrome/iframe1_makeGlobalObjectReference.html new file mode 100644 index 0000000000..bab5a70765 --- /dev/null +++ b/devtools/server/tests/chrome/iframe1_makeGlobalObjectReference.html @@ -0,0 +1 @@ +<html>The word 'smorgasbord' spoken by an adorably plump child, symbolizing prosperity</html> diff --git a/devtools/server/tests/chrome/iframe2_makeGlobalObjectReference.html b/devtools/server/tests/chrome/iframe2_makeGlobalObjectReference.html new file mode 100644 index 0000000000..b297ca8a2b --- /dev/null +++ b/devtools/server/tests/chrome/iframe2_makeGlobalObjectReference.html @@ -0,0 +1 @@ +<html>Her retrospection, in hindsight, was prescient.</html> diff --git a/devtools/server/tests/chrome/inactive-property-helper/.eslintrc.js b/devtools/server/tests/chrome/inactive-property-helper/.eslintrc.js new file mode 100644 index 0000000000..1514b14fde --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + parserOptions: { + sourceType: "module", + }, +}; diff --git a/devtools/server/tests/chrome/inactive-property-helper/align-content.js b/devtools/server/tests/chrome/inactive-property-helper/align-content.js new file mode 100644 index 0000000000..4a5f4837ff --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/align-content.js @@ -0,0 +1,93 @@ +/* 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/. */ + +// InactivePropertyHelper `align-content` test cases. + +export default [ + { + info: + "align-content is inactive on block elements (until bug 1105571 is fixed)", + property: "align-content", + tagName: "div", + rules: ["div { align-content: center; }"], + isActive: false, + }, + { + info: "align-content is active on flex containers", + property: "align-content", + tagName: "div", + rules: ["div { align-content: center; display: flex; }"], + isActive: true, + }, + { + info: "align-content is active on grid containers", + property: "align-content", + tagName: "div", + rules: ["div { align-content: center; display: grid; }"], + isActive: true, + }, + { + info: "align-content is inactive on flex items", + property: "align-content", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: ["div { display: flex; }", "span { align-content: center; }"], + ruleIndex: 1, + isActive: false, + }, + { + info: "align-content is inactive on grid items", + property: "align-content", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: ["div { display: grid; }", "span { align-content: center; }"], + ruleIndex: 1, + isActive: false, + }, + { + info: "align-content:baseline is active on flex items", + property: "align-content", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: ["div { display: flex; }", "span { align-content: baseline; }"], + ruleIndex: 1, + isActive: true, + }, + { + info: "align-content:baseline is active on grid items", + property: "align-content", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: ["div { display: grid; }", "span { align-content: baseline; }"], + ruleIndex: 1, + isActive: true, + }, + { + info: "align-content:baseline is active on table cells", + property: "align-content", + tagName: "div", + rules: ["div { display: table-cell; align-content: baseline; }"], + isActive: true, + }, +]; diff --git a/devtools/server/tests/chrome/inactive-property-helper/flex-grid-item-properties.js b/devtools/server/tests/chrome/inactive-property-helper/flex-grid-item-properties.js new file mode 100644 index 0000000000..79c587798a --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/flex-grid-item-properties.js @@ -0,0 +1,229 @@ +/* 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/. */ + +// InactivePropertyHelper `align-self`, `place-self`, and `order` test cases. +export default [ + { + info: "align-self is inactive on block element", + property: "align-self", + tagName: "div", + rules: ["div { align-self: center; }"], + isActive: false, + }, + { + info: "align-self is inactive on flex container", + property: "align-self", + tagName: "div", + rules: ["div { align-self: center; display: flex;}"], + isActive: false, + }, + { + info: "align-self is inactive on inline-flex container", + property: "align-self", + tagName: "div", + rules: ["div { align-self: center; display: inline-flex;}"], + isActive: false, + }, + { + info: "align-self is inactive on grid container", + property: "align-self", + tagName: "div", + rules: ["div { align-self: center; display: grid;}"], + isActive: false, + }, + { + info: "align-self is inactive on inline grid container", + property: "align-self", + tagName: "div", + rules: ["div { align-self: center; display: inline-grid;}"], + isActive: false, + }, + { + info: "align-self is inactive on inline element", + property: "align-self", + tagName: "span", + rules: ["span { align-self: center; }"], + isActive: false, + }, + { + info: "align-self is active on flex item", + property: "align-self", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: [ + "div { display: flex; align-items: start; }", + "span { align-self: center; }", + ], + ruleIndex: 1, + isActive: true, + }, + { + info: "align-self is active on grid item", + property: "align-self", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: [ + "div { display: grid; align-items: start; }", + "span { align-self: center; }", + ], + ruleIndex: 1, + isActive: true, + }, + { + info: "place-self is inactive on block element", + property: "place-self", + tagName: "div", + rules: ["div { place-self: center; }"], + isActive: false, + }, + { + info: "place-self is inactive on flex container", + property: "place-self", + tagName: "div", + rules: ["div { place-self: center; display: flex;}"], + isActive: false, + }, + { + info: "place-self is inactive on inline-flex container", + property: "place-self", + tagName: "div", + rules: ["div { place-self: center; display: inline-flex;}"], + isActive: false, + }, + { + info: "place-self is inactive on grid container", + property: "place-self", + tagName: "div", + rules: ["div { place-self: center; display: grid;}"], + isActive: false, + }, + { + info: "place-self is inactive on inline grid container", + property: "place-self", + tagName: "div", + rules: ["div { place-self: center; display: inline-grid;}"], + isActive: false, + }, + { + info: "place-self is inactive on inline element", + property: "place-self", + tagName: "span", + rules: ["span { place-self: center; }"], + isActive: false, + }, + { + info: "place-self is active on flex item", + property: "place-self", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: [ + "div { display: flex; align-items: start; }", + "span { place-self: center; }", + ], + ruleIndex: 1, + isActive: true, + }, + { + info: "place-self is active on grid item", + property: "place-self", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: [ + "div { display: grid; align-items: start; }", + "span { place-self: center; }", + ], + ruleIndex: 1, + isActive: true, + }, + { + info: "order is inactive on block element", + property: "order", + tagName: "div", + rules: ["div { order: 1; }"], + isActive: false, + }, + { + info: "order is inactive on flex container", + property: "order", + tagName: "div", + rules: ["div { order: 1; display: flex;}"], + isActive: false, + }, + { + info: "order is inactive on inline-flex container", + property: "order", + tagName: "div", + rules: ["div { order: 1; display: inline-flex;}"], + isActive: false, + }, + { + info: "order is inactive on grid container", + property: "order", + tagName: "div", + rules: ["div { order: 1; display: grid;}"], + isActive: false, + }, + { + info: "order is inactive on inline grid container", + property: "order", + tagName: "div", + rules: ["div { order: 1; display: inline-grid;}"], + isActive: false, + }, + { + info: "order is inactive on inline element", + property: "order", + tagName: "span", + rules: ["span { order: 1; }"], + isActive: false, + }, + { + info: "order is active on flex item", + property: "order", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: ["div { display: flex; }", "span { order: 1; }"], + ruleIndex: 1, + isActive: true, + }, + { + info: "order is active on grid item", + property: "order", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: ["div { display: grid; }", "span { order: 1; }"], + ruleIndex: 1, + isActive: true, + }, +]; diff --git a/devtools/server/tests/chrome/inactive-property-helper/float.js b/devtools/server/tests/chrome/inactive-property-helper/float.js new file mode 100644 index 0000000000..4c502e3cca --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/float.js @@ -0,0 +1,76 @@ +/* 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/. */ + +// InactivePropertyHelper `float` test cases. +export default [ + { + info: "display: inline is inactive on a floated element", + property: "display", + tagName: "div", + rules: ["div { display: inline; float: right; }"], + isActive: false, + }, + { + info: "display: block is active on a floated element", + property: "display", + tagName: "div", + rules: ["div { display: block; float: right;}"], + isActive: true, + }, + { + info: "display: inline-grid is inactive on a floated element", + property: "display", + createTestElement: rootNode => { + const container = document.createElement("div"); + container.classList.add("test"); + rootNode.append(container); + return container; + }, + rules: [ + "div { float: left; display:block; }", + ".test { display: inline-grid ;}", + ], + isActive: false, + }, + { + info: "display: table-footer-group is inactive on a floated element", + property: "display", + createTestElement: rootNode => { + const container = document.createElement("div"); + container.style.display = "table"; + const footer = document.createElement("div"); + footer.classList.add("table-footer"); + container.append(footer); + rootNode.append(container); + return footer; + }, + rules: [".table-footer { display: table-footer-group; float: left;}"], + isActive: false, + }, + createGridPlacementOnFloatedItemTest("grid-row"), + createGridPlacementOnFloatedItemTest("grid-column"), + createGridPlacementOnFloatedItemTest("grid-area", "foo"), +]; + +function createGridPlacementOnFloatedItemTest(property, value = "2") { + return { + info: `grid placement property ${property} is active on a floated grid item`, + property, + createTestElement: rootNode => { + const grid = document.createElement("div"); + grid.style.display = "grid"; + grid.style.gridTemplateRows = "repeat(5, 1fr)"; + grid.style.gridTemplateColumns = "repeat(5, 1fr)"; + grid.style.gridTemplateAreas = "'foo foo foo'"; + rootNode.appendChild(grid); + + const item = document.createElement("span"); + grid.appendChild(item); + + return item; + }, + rules: [`span { ${property}: ${value}; float: left; }`], + isActive: true, + }; +} diff --git a/devtools/server/tests/chrome/inactive-property-helper/gap.js b/devtools/server/tests/chrome/inactive-property-helper/gap.js new file mode 100644 index 0000000000..e2dd0b5bbb --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/gap.js @@ -0,0 +1,98 @@ +/* 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/. */ + +// InactivePropertyHelper `gap` test cases. +export default [ + { + info: "column-gap is inactive on non-grid and non-flex container", + property: "column-gap", + tagName: "div", + rules: ["div { column-gap: 10px; display: block; }"], + isActive: false, + }, + { + info: "column-gap is active on grid container", + property: "column-gap", + tagName: "div", + rules: ["div { column-gap: 10px; display: grid; }"], + isActive: true, + }, + { + info: "column-gap is active on flex container", + property: "column-gap", + tagName: "div", + rules: ["div { column-gap: 10px; display: flex; }"], + isActive: true, + }, + { + info: "column-gap is inactive on non-multi-col container", + property: "column-gap", + tagName: "div", + rules: ["div { column-gap: 10px; column-count: auto; }"], + isActive: false, + }, + { + info: "column-gap is active on multi-column container", + property: "column-gap", + tagName: "div", + rules: ["div { column-gap: 10px; column-count: 2; }"], + isActive: true, + }, + { + info: "row-gap is inactive on non-grid and non-flex container", + property: "row-gap", + tagName: "div", + rules: ["div { row-gap: 10px; display: block; }"], + isActive: false, + }, + { + info: "row-gap is active on grid container", + property: "row-gap", + tagName: "div", + rules: ["div { row-gap: 10px; display: grid; }"], + isActive: true, + }, + { + info: "row-gap is active on flex container", + property: "row-gap", + tagName: "div", + rules: ["div { row-gap: 10px; display: flex; }"], + isActive: true, + }, + { + info: "gap is inactive on non-grid and non-flex container", + property: "gap", + tagName: "div", + rules: ["div { gap: 10px; display: block; }"], + isActive: false, + }, + { + info: "gap is active on flex container", + property: "gap", + tagName: "div", + rules: ["div { gap: 10px; display: flex; }"], + isActive: true, + }, + { + info: "gap is active on grid container", + property: "gap", + tagName: "div", + rules: ["div { gap: 10px; display: grid; }"], + isActive: true, + }, + { + info: "gap is inactive on non-multi-col container", + property: "gap", + tagName: "div", + rules: ["div { gap: 10px; column-count: auto; }"], + isActive: false, + }, + { + info: "gap is active on multi-col container", + property: "gap", + tagName: "div", + rules: ["div { gap: 10px; column-count: 2; }"], + isActive: true, + }, +]; diff --git a/devtools/server/tests/chrome/inactive-property-helper/grid-with-absolute-properties.js b/devtools/server/tests/chrome/inactive-property-helper/grid-with-absolute-properties.js new file mode 100644 index 0000000000..fd963e0d3b --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/grid-with-absolute-properties.js @@ -0,0 +1,71 @@ +/* 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/. */ + +// InactivePropertyHelper test cases: +// `grid-area`, `grid-column`, `grid-column-end`, `grid-column-start`, +// `grid-row`, `grid-row-end`, `grid-row-start`, `justify-self`, `align-self` +// and `place-self`. +let tests = []; + +for (const { propertyName, propertyValue } of [ + { propertyName: "grid-area", propertyValue: "2 / 1 / span 2 / span 3" }, + { propertyName: "grid-column", propertyValue: 2 }, + { propertyName: "grid-column-end", propertyValue: "span 3" }, + { propertyName: "grid-column-start", propertyValue: 2 }, + { propertyName: "grid-row", propertyValue: "1 / span 2" }, + { propertyName: "grid-row-end", propertyValue: "span 3" }, + { propertyName: "grid-row-start", propertyValue: 2 }, + { propertyName: "justify-self", propertyValue: "start" }, + { propertyName: "align-self", propertyValue: "auto" }, + { propertyName: "place-self", propertyValue: "auto center" }, +]) { + tests = tests.concat(createTestsForProp(propertyName, propertyValue)); +} + +function createTestsForProp(propertyName, propertyValue) { + return [ + { + info: `${propertyName} is active on a grid item`, + property: `${propertyName}`, + createTestElement, + rules: [ + `#grid-container { display:grid; grid:auto/100px 100px; }`, + `#grid-item { ${propertyName}: ${propertyValue}; }`, + ], + ruleIndex: 1, + isActive: true, + }, + { + info: `${propertyName} is active on an absolutely positioned grid item`, + property: `${propertyName}`, + createTestElement, + rules: [ + `#grid-container { display:grid; grid:auto/100px 100px; position: relative }`, + `#grid-item { ${propertyName}: ${propertyValue}; position: absolute; }`, + ], + ruleIndex: 1, + isActive: true, + }, + { + info: `${propertyName} is inactive on a non-grid item`, + property: `${propertyName}`, + tagName: `div`, + rules: [`div { ${propertyName}: ${propertyValue}; }`], + isActive: false, + }, + ]; +} + +function createTestElement(rootNode) { + const container = document.createElement("div"); + container.id = "grid-container"; + const element = document.createElement("div"); + element.id = "grid-item"; + container.append(element); + rootNode.append(container); + + return element; +} + +export default tests; diff --git a/devtools/server/tests/chrome/inactive-property-helper/margin-padding.js b/devtools/server/tests/chrome/inactive-property-helper/margin-padding.js new file mode 100644 index 0000000000..7c1d348512 --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/margin-padding.js @@ -0,0 +1,260 @@ +/* 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/. */ + +// InactivePropertyHelper `align-content` test cases. + +export default [ + { + info: "margin is active on block containers", + property: "margin", + tagName: "div", + rules: ["div { margin: 10px; }"], + isActive: true, + }, + { + info: "margin is active on flex containers", + property: "margin", + tagName: "div", + rules: ["div { display: flex; margin: 10px; }"], + isActive: true, + }, + { + info: "margin is active on grid containers", + property: "margin", + tagName: "div", + rules: ["div { display: grid; margin: 10px; }"], + isActive: true, + }, + { + info: "margin is active on tables", + property: "margin", + tagName: "div", + rules: ["div { display: table; margin: 10px; }"], + isActive: true, + }, + { + info: "margin is active on inline tables", + property: "margin", + tagName: "div", + rules: ["div { display: inline-table; margin: 10px; }"], + isActive: true, + }, + { + info: "margin is active on table captions", + property: "margin", + tagName: "div", + rules: ["div { display: table-caption; margin: 10px; }"], + isActive: true, + }, + { + info: "margin is inactive on table cells", + property: "margin", + tagName: "div", + rules: ["div { display: table-cell; margin: 10px; }"], + isActive: false, + }, + { + info: "margin-block is inactive on table cells", + property: "margin-block", + tagName: "div", + rules: ["div { display: table-cell; margin-block: 10px; }"], + isActive: false, + }, + { + info: "margin-block-start is inactive on table cells", + property: "margin-block-start", + tagName: "div", + rules: ["div { display: table-cell; margin-block-start: 10px; }"], + isActive: false, + }, + { + info: "margin-block-end is inactive on table cells", + property: "margin-block-end", + tagName: "div", + rules: ["div { display: table-cell; margin-block-end: 10px; }"], + isActive: false, + }, + { + info: "margin-block is inactive on table cells", + property: "margin-block", + tagName: "div", + rules: ["div { display: table-cell; margin-block: 10px; }"], + isActive: false, + }, + { + info: "margin-bottom is inactive on table rows", + property: "margin-bottom", + tagName: "div", + rules: ["div { display: table-row; margin-bottom: 10px; }"], + isActive: false, + }, + { + info: "margin-inline-start is inactive on table rows", + property: "margin-inline-start", + tagName: "div", + rules: ["div { display: table-row; margin-inline-start: 10px; }"], + isActive: false, + }, + { + info: "margin-inline-end is inactive on table rows", + property: "margin-inline-end", + tagName: "div", + rules: ["div { display: table-row; margin-inline-end: 10px; }"], + isActive: false, + }, + { + info: "margin-inline is inactive on table rows", + property: "margin-inline", + tagName: "div", + rules: ["div { display: table-row; margin-inline: 10px; }"], + isActive: false, + }, + { + info: "margin-left is inactive on table columns", + property: "margin-left", + tagName: "div", + rules: ["div { display: table-column; margin-left: 10px; }"], + isActive: false, + }, + { + info: "margin-right is inactive on table row groups", + property: "margin-right", + tagName: "div", + rules: ["div { display: table-row-group; margin-right: 10px; }"], + isActive: false, + }, + { + info: "margin-top is inactive on table column groups", + property: "margin-top", + tagName: "div", + rules: ["div { display: table-column-group; margin-top: 10px; }"], + isActive: false, + }, + { + info: "padding is active on block containers", + property: "padding", + tagName: "div", + rules: ["div { padding: 10px; }"], + isActive: true, + }, + { + info: "padding is active on flex containers", + property: "padding", + tagName: "div", + rules: ["div { display: flex; padding: 10px; }"], + isActive: true, + }, + { + info: "padding is active on grid containers", + property: "padding", + tagName: "div", + rules: ["div { display: grid; padding: 10px; }"], + isActive: true, + }, + { + info: "padding is active on tables", + property: "padding", + tagName: "div", + rules: ["div { display: table; padding: 10px; }"], + isActive: true, + }, + { + info: "padding is active on inline tables", + property: "padding", + tagName: "div", + rules: ["div { display: inline-table; padding: 10px; }"], + isActive: true, + }, + { + info: "padding is active on table captions", + property: "padding", + tagName: "div", + rules: ["div { display: table-caption; padding: 10px; }"], + isActive: true, + }, + { + info: "padding is active on table cells", + property: "padding", + tagName: "div", + rules: ["div { display: table-cell; padding: 10px; }"], + isActive: true, + }, + { + info: "padding-block is active on table cells", + property: "padding-block", + tagName: "div", + rules: ["div { display: table-cell; padding-block: 10px; }"], + isActive: true, + }, + { + info: "padding-block-start is active on table cells", + property: "padding-block-start", + tagName: "div", + rules: ["div { display: table-cell; padding-block-start: 10px; }"], + isActive: true, + }, + { + info: "padding-block-end is active on table cells", + property: "padding-block-end", + tagName: "div", + rules: ["div { display: table-cell; padding-block-end: 10px; }"], + isActive: true, + }, + { + info: "padding-block is active on table cells", + property: "padding-block", + tagName: "div", + rules: ["div { display: table-cell; padding-block: 10px; }"], + isActive: true, + }, + { + info: "padding-bottom is inactive on table rows", + property: "padding-bottom", + tagName: "div", + rules: ["div { display: table-row; padding-bottom: 10px; }"], + isActive: false, + }, + { + info: "padding-inline-start is inactive on table rows", + property: "padding-inline-start", + tagName: "div", + rules: ["div { display: table-row; padding-inline-start: 10px; }"], + isActive: false, + }, + { + info: "padding-inline-end is inactive on table rows", + property: "padding-inline-end", + tagName: "div", + rules: ["div { display: table-row; padding-inline-end: 10px; }"], + isActive: false, + }, + { + info: "padding-inline is inactive on table rows", + property: "padding-inline", + tagName: "div", + rules: ["div { display: table-row; padding-inline: 10px; }"], + isActive: false, + }, + { + info: "padding-left is inactive on table columns", + property: "padding-left", + tagName: "div", + rules: ["div { display: table-column; padding-left: 10px; }"], + isActive: false, + }, + { + info: "padding-right is inactive on table row groups", + property: "padding-right", + tagName: "div", + rules: ["div { display: table-row-group; padding-right: 10px; }"], + isActive: false, + }, + { + info: "padding-top is inactive on table column groups", + property: "padding-top", + tagName: "div", + rules: ["div { display: table-column-group; padding-top: 10px; }"], + isActive: false, + }, +]; diff --git a/devtools/server/tests/chrome/inactive-property-helper/max-min-width-height.js b/devtools/server/tests/chrome/inactive-property-helper/max-min-width-height.js new file mode 100644 index 0000000000..e0ce4d24a5 --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/max-min-width-height.js @@ -0,0 +1,367 @@ +/* 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/. */ + +// InactivePropertyHelper `width`, `min-width`, `max-width`, `height`, `min-height`, +// `max-height` test cases. +export default [ + { + info: "width is inactive on a non-replaced inline element", + property: "width", + tagName: "span", + rules: ["span { width: 500px; }"], + isActive: false, + }, + { + info: "min-width is inactive on a non-replaced inline element", + property: "min-width", + tagName: "span", + rules: ["span { min-width: 500px; }"], + isActive: false, + }, + { + info: "max-width is inactive on a non-replaced inline element", + property: "max-width", + tagName: "span", + rules: ["span { max-width: 500px; }"], + isActive: false, + }, + { + info: "width is inactive on an tr element", + property: "width", + tagName: "tr", + rules: ["tr { width: 500px; }"], + isActive: false, + }, + { + info: "min-width is inactive on an tr element", + property: "min-width", + tagName: "tr", + rules: ["tr { min-width: 500px; }"], + isActive: false, + }, + { + info: "max-width is inactive on an tr element", + property: "max-width", + tagName: "tr", + rules: ["tr { max-width: 500px; }"], + isActive: false, + }, + { + info: "width is inactive on an thead element", + property: "width", + tagName: "thead", + rules: ["thead { width: 500px; }"], + isActive: false, + }, + { + info: "min-width is inactive on an thead element", + property: "min-width", + tagName: "thead", + rules: ["thead { min-width: 500px; }"], + isActive: false, + }, + { + info: "max-width is inactive on an thead element", + property: "max-width", + tagName: "thead", + rules: ["thead { max-width: 500px; }"], + isActive: false, + }, + { + info: "width is inactive on an tfoot element", + property: "width", + tagName: "tfoot", + rules: ["tfoot { width: 500px; }"], + isActive: false, + }, + { + info: "min-width is inactive on an tfoot element", + property: "min-width", + tagName: "tfoot", + rules: ["tfoot { min-width: 500px; }"], + isActive: false, + }, + { + info: "max-width is inactive on an tfoot element", + property: "max-width", + tagName: "tfoot", + rules: ["tfoot { max-width: 500px; }"], + isActive: false, + }, + { + info: "width is active on a replaced inline element", + property: "width", + tagName: "img", + rules: ["img { width: 500px; }"], + isActive: true, + }, + { + info: "width is active on an inline input element", + property: "width", + tagName: "input", + rules: ["input { display: inline; width: 500px; }"], + isActive: true, + }, + { + info: "width is active on an inline select element", + property: "width", + tagName: "select", + rules: ["select { display: inline; width: 500px; }"], + isActive: true, + }, + { + info: "width is active on a textarea element", + property: "width", + tagName: "textarea", + rules: ["textarea { width: 500px; }"], + isActive: true, + }, + { + info: "min-width is active on a replaced inline element", + property: "min-width", + tagName: "img", + rules: ["img { min-width: 500px; }"], + isActive: true, + }, + { + info: "max-width is active on a replaced inline element", + property: "max-width", + tagName: "img", + rules: ["img { max-width: 500px; }"], + isActive: true, + }, + { + info: "width is active on a block element", + property: "width", + tagName: "div", + rules: ["div { width: 500px; }"], + isActive: true, + }, + { + info: "min-width is active on a block element", + property: "min-width", + tagName: "div", + rules: ["div { min-width: 500px; }"], + isActive: true, + }, + { + info: "max-width is active on a block element", + property: "max-width", + tagName: "div", + rules: ["div { max-width: 500px; }"], + isActive: true, + }, + { + info: "height is inactive on a non-replaced inline element", + property: "height", + tagName: "span", + rules: ["span { height: 500px; }"], + isActive: false, + }, + { + info: "min-height is inactive on a non-replaced inline element", + property: "min-height", + tagName: "span", + rules: ["span { min-height: 500px; }"], + isActive: false, + }, + { + info: "max-height is inactive on a non-replaced inline element", + property: "max-height", + tagName: "span", + rules: ["span { max-height: 500px; }"], + isActive: false, + }, + { + info: "height is inactive on colgroup element", + property: "height", + tagName: "colgroup", + rules: ["colgroup { height: 500px; }"], + isActive: false, + }, + { + info: "min-height is inactive on colgroup element", + property: "min-height", + tagName: "colgroup", + rules: ["colgroup { min-height: 500px; }"], + isActive: false, + }, + { + info: "max-height is inactive on colgroup element", + property: "max-height", + tagName: "colgroup", + rules: ["colgroup { max-height: 500px; }"], + isActive: false, + }, + { + info: "height is inactive on col element", + property: "height", + tagName: "col", + rules: ["col { height: 500px; }"], + isActive: false, + }, + { + info: "min-height is inactive on col element", + property: "min-height", + tagName: "col", + rules: ["col { min-height: 500px; }"], + isActive: false, + }, + { + info: "max-height is inactive on col element", + property: "max-height", + tagName: "col", + rules: ["col { max-height: 500px; }"], + isActive: false, + }, + { + info: "height is active on a replaced inline element", + property: "height", + tagName: "img", + rules: ["img { height: 500px; }"], + isActive: true, + }, + { + info: "height is active on an inline input element", + property: "height", + tagName: "input", + rules: ["input { display: inline; height: 500px; }"], + isActive: true, + }, + { + info: "height is active on an inline select element", + property: "height", + tagName: "select", + rules: ["select { display: inline; height: 500px; }"], + isActive: true, + }, + { + info: "height is active on a textarea element", + property: "height", + tagName: "textarea", + rules: ["textarea { height: 500px; }"], + isActive: true, + }, + { + info: "min-height is active on a replaced inline element", + property: "min-height", + tagName: "img", + rules: ["img { min-height: 500px; }"], + isActive: true, + }, + { + info: "max-height is active on a replaced inline element", + property: "max-height", + tagName: "img", + rules: ["img { max-height: 500px; }"], + isActive: true, + }, + { + info: "height is active on a block element", + property: "height", + tagName: "div", + rules: ["div { height: 500px; }"], + isActive: true, + }, + { + info: "min-height is active on a block element", + property: "min-height", + tagName: "div", + rules: ["div { min-height: 500px; }"], + isActive: true, + }, + { + info: "max-height is active on a block element", + property: "max-height", + tagName: "div", + rules: ["div { max-height: 500px; }"], + isActive: true, + }, + { + info: "height is active on an svg <rect> element.", + property: "height", + createTestElement: main => { + main.innerHTML = ` + <svg width=100 height=100> + <rect width=100 fill=green></rect> + </svg> + `; + return main.querySelector("rect"); + }, + rules: ["rect { height: 100px; }"], + isActive: true, + }, + createTableElementTestCase("width", false, "table-row"), + createTableElementTestCase("width", false, "table-row-group"), + createTableElementTestCase("width", true, "table-column"), + createTableElementTestCase("width", true, "table-column-group"), + createTableElementTestCase("height", false, "table-column"), + createTableElementTestCase("height", false, "table-column-group"), + createTableElementTestCase("height", true, "table-row"), + createTableElementTestCase("height", true, "table-row-group"), + createVerticalTableElementTestCase("width", true, "table-row"), + createVerticalTableElementTestCase("width", true, "table-row-group"), + createVerticalTableElementTestCase("width", false, "table-column"), + createVerticalTableElementTestCase("width", false, "table-column-group"), + createVerticalTableElementTestCase("height", true, "table-column"), + createVerticalTableElementTestCase("height", true, "table-column-group"), + createVerticalTableElementTestCase("height", false, "table-row"), + createVerticalTableElementTestCase("height", false, "table-row-group"), + { + info: + "width's inactivity status for a row takes the table's writing mode into account", + property: "width", + createTestElement: rootNode => { + const table = document.createElement("table"); + table.style.writingMode = "vertical-lr"; + rootNode.appendChild(table); + + const tbody = document.createElement("tbody"); + table.appendChild(tbody); + + const tr = document.createElement("tr"); + tbody.appendChild(tr); + + const td = document.createElement("td"); + tr.appendChild(td); + + return tr; + }, + rules: ["tr { writing-mode: horizontal-tb; width: 360px; }"], + isActive: true, + }, +]; + +function createTableElementTestCase(property, isActive, displayType) { + return { + info: `${property} is ${ + isActive ? "active" : "inactive" + } on a ${displayType}`, + property, + tagName: "div", + rules: [`div { display: ${displayType}; ${property}: 100px; }`], + isActive, + }; +} + +function createVerticalTableElementTestCase(property, isActive, displayType) { + return { + info: `${property} is ${ + isActive ? "active" : "inactive" + } on a vertical ${displayType}`, + property, + createTestElement: rootNode => { + const container = document.createElement("div"); + container.style.writingMode = "vertical-lr"; + rootNode.append(container); + + const element = document.createElement("span"); + container.append(element); + + return element; + }, + rules: [`span { display: ${displayType}; ${property}: 100px; }`], + isActive, + }; +} diff --git a/devtools/server/tests/chrome/inactive-property-helper/outline-radius.js b/devtools/server/tests/chrome/inactive-property-helper/outline-radius.js new file mode 100644 index 0000000000..8c4985437f --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/outline-radius.js @@ -0,0 +1,28 @@ +/* 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/. */ + +// InactivePropertyHelper `outline-radius` test cases. +export default [ + { + info: "-moz-outline-radius is inactive when outline-style is auto", + property: "-moz-outline-radius", + tagName: "div", + rules: ["div { outline: 10px auto; -moz-outline-radius: 10px; }"], + isActive: false, + }, + { + info: "-moz-outline-radius is inactive when outline-style is none", + property: "-moz-outline-radius", + tagName: "div", + rules: ["div { -moz-outline-radius: 10px; }"], + isActive: false, + }, + { + info: "-moz-outline-radius is active when outline-style is not auto", + property: "-moz-outline-radius", + tagName: "div", + rules: ["div { outline: 10px solid; -moz-outline-radius: 10px; }"], + isActive: true, + }, +]; diff --git a/devtools/server/tests/chrome/inactive-property-helper/place-items-content.js b/devtools/server/tests/chrome/inactive-property-helper/place-items-content.js new file mode 100644 index 0000000000..f554a785a7 --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/place-items-content.js @@ -0,0 +1,159 @@ +/* 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/. */ + +// InactivePropertyHelper `place-items` and `place-content` test cases. +export default [ + { + info: "place-items is inactive on block element", + property: "place-items", + tagName: "div", + rules: ["div { place-items: center; }"], + isActive: false, + }, + { + info: "place-items is inactive on inline element", + property: "place-items", + tagName: "span", + rules: ["span { place-items: center; }"], + isActive: false, + }, + { + info: "place-items is inactive on flex item", + property: "place-items", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: [ + "div { display: flex; align-items: start; }", + "span { place-items: center; }", + ], + ruleIndex: 1, + isActive: false, + }, + { + info: "place-items is inactive on grid item", + property: "place-items", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: [ + "div { display: grid; align-items: start; }", + "span { place-items: center; }", + ], + ruleIndex: 1, + isActive: false, + }, + { + info: "place-items is active on flex container", + property: "place-items", + tagName: "div", + rules: ["div { place-items: center; display: flex;}"], + isActive: true, + }, + { + info: "place-items is active on inline-flex container", + property: "place-items", + tagName: "div", + rules: ["div { place-items: center; display: inline-flex;}"], + isActive: true, + }, + { + info: "place-items is active on grid container", + property: "place-items", + tagName: "div", + rules: ["div { place-items: center; display: grid;}"], + isActive: true, + }, + { + info: "place-items is active on inline grid container", + property: "place-items", + tagName: "div", + rules: ["div { place-items: center; display: inline-grid;}"], + isActive: true, + }, + { + info: "place-content is inactive on block element", + property: "place-content", + tagName: "div", + rules: ["div { place-content: center; }"], + isActive: false, + }, + { + info: "place-content is inactive on inline element", + property: "place-content", + tagName: "span", + rules: ["span { place-content: center; }"], + isActive: false, + }, + { + info: "place-content is inactive on flex item", + property: "place-content", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: [ + "div { display: flex; align-items: start; }", + "span { place-content: center; }", + ], + ruleIndex: 1, + isActive: false, + }, + { + info: "place-content is inactive on grid item", + property: "place-content", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: [ + "div { display: grid; align-items: start; }", + "span { place-content: center; }", + ], + ruleIndex: 1, + isActive: false, + }, + { + info: "place-content is active on flex container", + property: "place-content", + tagName: "div", + rules: ["div { place-content: center; display: flex;}"], + isActive: true, + }, + { + info: "place-content is active on inline-flex container", + property: "place-content", + tagName: "div", + rules: ["div { place-content: center; display: inline-flex;}"], + isActive: true, + }, + { + info: "place-content is active on grid container", + property: "place-content", + tagName: "div", + rules: ["div { place-content: center; display: grid;}"], + isActive: true, + }, + { + info: "place-content is active on inline grid container", + property: "place-content", + tagName: "div", + rules: ["div { place-content: center; display: inline-grid;}"], + isActive: true, + }, +]; diff --git a/devtools/server/tests/chrome/inactive-property-helper/positioned.js b/devtools/server/tests/chrome/inactive-property-helper/positioned.js new file mode 100644 index 0000000000..b9ebe5aaee --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/positioned.js @@ -0,0 +1,84 @@ +/* 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/. */ + +// InactivePropertyHelper positioned elements test cases. + +// These are the properties we care about, those that are inactive when the element isn't +// positioned. +const PROPERTIES = [ + { property: "z-index", value: "2" }, + { property: "top", value: "20px" }, + { property: "right", value: "20px" }, + { property: "bottom", value: "20px" }, + { property: "left", value: "20px" }, +]; + +// These are all of the different position values and whether the above properties are +// active or not for each. +const POSITIONS = [ + { position: "unset", isActive: false }, + { position: "initial", isActive: false }, + { position: "inherit", isActive: false }, + { position: "static", isActive: false }, + { position: "absolute", isActive: true }, + { position: "relative", isActive: true }, + { position: "fixed", isActive: true }, + { position: "sticky", isActive: true }, +]; + +function makeTestCase(property, value, position, isActive) { + return { + info: `${property} is ${ + isActive ? "" : "in" + }active when position is ${position}`, + property, + tagName: "div", + rules: [`div { ${property}: ${value}; position: ${position}; }`], + isActive, + }; +} + +// Make the test cases for all the combinations of PROPERTIES and POSITIONS +const mainTests = []; + +for (const { property, value } of PROPERTIES) { + for (const { position, isActive } of POSITIONS) { + mainTests.push(makeTestCase(property, value, position, isActive)); + } +} + +// Add a few test cases to check that z-index actually works inside grids and flexboxes. +mainTests.push({ + info: + "z-index is active even on unpositioned elements if they are grid items", + property: "z-index", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: ["div { display: grid; }", "span { z-index: 3; }"], + ruleIndex: 1, + isActive: true, +}); + +mainTests.push({ + info: + "z-index is active even on unpositioned elements if they are flex items", + property: "z-index", + createTestElement: rootNode => { + const container = document.createElement("div"); + const element = document.createElement("span"); + container.append(element); + rootNode.append(container); + return element; + }, + rules: ["div { display: flex; }", "span { z-index: 3; }"], + ruleIndex: 1, + isActive: true, +}); + +export default mainTests; diff --git a/devtools/server/tests/chrome/inactive-property-helper/text-overflow.js b/devtools/server/tests/chrome/inactive-property-helper/text-overflow.js new file mode 100644 index 0000000000..21bc180458 --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/text-overflow.js @@ -0,0 +1,44 @@ +/* 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/. */ + +// InactivePropertyHelper `text-overflow` test cases. +export default [ + { + info: "text-overflow is inactive when overflow is not set", + property: "text-overflow", + tagName: "div", + rules: ["div { text-overflow: ellipsis; }"], + isActive: false, + }, + { + info: "text-overflow is active when overflow is set to hidden", + property: "text-overflow", + tagName: "div", + rules: ["div { text-overflow: ellipsis; overflow: hidden; }"], + isActive: true, + }, + { + info: "text-overflow is inactive when overflow is set to visible", + property: "text-overflow", + tagName: "div", + rules: ["div { text-overflow: ellipsis; overflow: visible; }"], + isActive: false, + }, + { + info: + "as soon as overflow:hidden is set, text-overflow is active whatever the box type", + property: "text-overflow", + tagName: "span", + rules: ["span { text-overflow: ellipsis; overflow: hidden; }"], + isActive: true, + }, + { + info: + "as soon as overflow:hidden is set, text-overflow is active whatever the box type", + property: "text-overflow", + tagName: "legend", + rules: ["legend { text-overflow: ellipsis; overflow: hidden; }"], + isActive: true, + }, +]; diff --git a/devtools/server/tests/chrome/inactive-property-helper/vertical-align.js b/devtools/server/tests/chrome/inactive-property-helper/vertical-align.js new file mode 100644 index 0000000000..e9873d4865 --- /dev/null +++ b/devtools/server/tests/chrome/inactive-property-helper/vertical-align.js @@ -0,0 +1,56 @@ +/* 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/. */ + +// InactivePropertyHelper `vertical-align` test cases. +export default [ + { + info: "vertical-align is inactive on a block element", + property: "vertical-align", + tagName: "div", + rules: ["div { vertical-align: top; }"], + isActive: false, + }, + { + info: "vertical-align is inactive on a span with display block", + property: "vertical-align", + tagName: "span", + rules: ["span { vertical-align: top; display: block;}"], + isActive: false, + }, + { + info: "vertical-align is active on a div with display inline-block", + property: "vertical-align", + tagName: "div", + rules: ["div { vertical-align: top; display: inline-block;}"], + isActive: true, + }, + { + info: "vertical-align is active on a table-cell", + property: "vertical-align", + tagName: "div", + rules: ["div { vertical-align: top; display: table-cell;}"], + isActive: true, + }, + { + info: "vertical-align is active on a block element ::first-letter", + property: "vertical-align", + tagName: "div", + rules: ["div::first-letter { vertical-align: top; }"], + isActive: true, + }, + { + info: "vertical-align is active on a block element ::first-line", + property: "vertical-align", + tagName: "div", + rules: ["div::first-line { vertical-align: top; }"], + isActive: true, + }, + { + info: "vertical-align is active on an inline element", + property: "vertical-align", + tagName: "span", + rules: ["span { vertical-align: top; }"], + isActive: true, + }, +]; diff --git a/devtools/server/tests/chrome/inspector-delay-image-response.sjs b/devtools/server/tests/chrome/inspector-delay-image-response.sjs new file mode 100644 index 0000000000..d8de857751 --- /dev/null +++ b/devtools/server/tests/chrome/inspector-delay-image-response.sjs @@ -0,0 +1,40 @@ +/** + * Adapted from https://searchfox.org/mozilla-central/source/layout/reftests/backgrounds/delay-image-response.sjs + */ +"use strict"; + +// A 1x1 PNG image. +// Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain) +const IMAGE = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" + + "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="); + +// To avoid GC. +let timer = null; + +function handleRequest(request, response) { + let query = {}; + request.queryString.split("&").forEach(function(val) { + let [name, value] = val.split("="); + query[name] = unescape(value); + }); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "image/png", false); + + // If there is no delay, we write the image and leave. + if (!("delay" in query)) { + response.write(IMAGE); + return; + } + + // If there is a delay, we create a timer which, when it fires, will write + // image and leave. + response.processAsync(); + const nsITimer = Components.interfaces.nsITimer; + + timer = Components.classes["@mozilla.org/timer;1"].createInstance(nsITimer); + timer.initWithCallback(function() { + response.write(IMAGE); + response.finish(); + }, query.delay, nsITimer.TYPE_ONE_SHOT); +} diff --git a/devtools/server/tests/chrome/inspector-eyedropper.html b/devtools/server/tests/chrome/inspector-eyedropper.html new file mode 100644 index 0000000000..f5bd3a1cb8 --- /dev/null +++ b/devtools/server/tests/chrome/inspector-eyedropper.html @@ -0,0 +1,20 @@ +<html> +<head> + <meta charset="UTF-8"> + <title>Inspector Eyedropper tests</title> + <style> + html { + background: black; + } + </style> + <script type="text/javascript"> + "use strict"; + + window.onload = function() { + window.opener.postMessage("ready", "*"); + }; + </script> +</head> +</body> +</body> +</html> diff --git a/devtools/server/tests/chrome/inspector-helpers.js b/devtools/server/tests/chrome/inspector-helpers.js new file mode 100644 index 0000000000..bd436c313e --- /dev/null +++ b/devtools/server/tests/chrome/inspector-helpers.js @@ -0,0 +1,146 @@ +/* exported attachURL, promiseDone, + promiseOnce, + addTest, addAsyncTest, + runNextTest, _documentWalker, + createResourceWatcher */ +"use strict"; + +const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); +const { TargetFactory } = require("devtools/client/framework/target"); +const { DevToolsServer } = require("devtools/server/devtools-server"); +const { + BrowserTestUtils, +} = require("resource://testing-common/BrowserTestUtils.jsm"); +const { + DocumentWalker: _documentWalker, +} = require("devtools/server/actors/inspector/document-walker"); + +const { TargetList } = require("devtools/shared/resources/target-list"); +const { + ResourceWatcher, +} = require("devtools/shared/resources/resource-watcher"); + +const Services = require("Services"); + +// Always log packets when running tests. +Services.prefs.setBoolPref("devtools.debugger.log", true); +SimpleTest.registerCleanupFunction(function() { + Services.prefs.clearUserPref("devtools.debugger.log"); +}); + +if (!DevToolsServer.initialized) { + DevToolsServer.init(); + DevToolsServer.registerAllActors(); + SimpleTest.registerCleanupFunction(function() { + DevToolsServer.destroy(); + }); +} + +var gAttachCleanups = []; + +SimpleTest.registerCleanupFunction(function() { + for (const cleanup of gAttachCleanups) { + cleanup(); + } +}); + +/** + * Add a new test tab in the browser and load the given url. + * @return Promise a promise that resolves to the new target representing + * the page currently opened. + */ + +async function getTargetForSelectedTab(gBrowser) { + const selectedTab = gBrowser.selectedTab; + await BrowserTestUtils.browserLoaded(selectedTab.linkedBrowser); + return TargetFactory.forTab(selectedTab); +} + +/** + * Open a tab, load the url, wait for it to signal its readiness, + * find the tab with the devtools server, and call the callback. + * + * Returns a function which can be called to close the opened ta + * and disconnect its devtools client. + */ +async function attachURL(url) { + // Get the current browser window + const gBrowser = Services.wm.getMostRecentWindow("navigator:browser") + .gBrowser; + + // open the url in a new tab, save a reference to the new inner window global object + // and wait for it to load. The tests rely on this window object to send a "ready" + // event to its opener (the test page). This window reference is used within + // the test tab, to reference the webpage being tested against, which is in another + // tab. + const windowOpened = BrowserTestUtils.waitForNewTab(gBrowser, url); + const win = window.open(url, "_blank"); + await windowOpened; + + const target = await getTargetForSelectedTab(gBrowser); + await target.attach(); + + const cleanup = async function() { + await target.destroy(); + if (win) { + win.close(); + } + }; + + gAttachCleanups.push(cleanup); + return { target, doc: win.document }; +} + +function promiseOnce(target, event) { + return new Promise(resolve => { + target.on(event, (...args) => { + if (args.length === 1) { + resolve(args[0]); + } else { + resolve(args); + } + }); + }); +} + +function promiseDone(currentPromise) { + currentPromise.catch(err => { + ok(false, "Promise failed: " + err); + if (err.stack) { + dump(err.stack); + } + SimpleTest.finish(); + }); +} + +var _tests = []; +function addTest(test) { + _tests.push(test); +} + +function addAsyncTest(generator) { + _tests.push(() => generator().catch(ok.bind(null, false))); +} + +function runNextTest() { + if (_tests.length == 0) { + SimpleTest.finish(); + return; + } + const fn = _tests.shift(); + try { + fn(); + } catch (ex) { + info( + "Test function " + + (fn.name ? "'" + fn.name + "' " : "") + + "threw an exception: " + + ex + ); + } +} + +function createResourceWatcher(target) { + const targetList = new TargetList(target.client.mainRoot, target); + return new ResourceWatcher(targetList); +} diff --git a/devtools/server/tests/chrome/inspector-search-data.html b/devtools/server/tests/chrome/inspector-search-data.html new file mode 100644 index 0000000000..784dcb7c9b --- /dev/null +++ b/devtools/server/tests/chrome/inspector-search-data.html @@ -0,0 +1,54 @@ +<html> +<head> + <meta charset="UTF-8"> + <title>Inspector Search Test Data</title> + <style> + #pseudo { + display: block; + margin: 0; + } + #pseudo:before { + content: "before element"; + } + #pseudo:after { + content: "after element"; + } + </style> + <script type="text/javascript"> + "use strict"; + + window.onload = function() { + window.opener.postMessage("ready", "*"); + }; + </script> +</head> +</body> + <!-- A comment + spread across multiple lines --> + + <img width="100" height="100" src="large-image.jpg" /> + + <h1 id="pseudo">Heading 1</h1> + <p>A p tag with the text 'h1' inside of it. + <strong>A strong h1 result</strong> + </p> + + <div id="arrows" northwest="↖" northeast="↗" southeast="↘" southwest="↙"> + Unicode arrows + </div> + + <h2>Heading 2</h2> + <h2>Heading 2</h2> + <h2>Heading 2</h2> + + <h3>Heading 3</h3> + <h3>Heading 3</h3> + <h3>Heading 3</h3> + + <h4>Heading 4</h4> + <h4>Heading 4</h4> + <h4>Heading 4</h4> + + <div class="💩" id="💩" 💩="💩"></div> +</body> +</html> diff --git a/devtools/server/tests/chrome/inspector-styles-data.css b/devtools/server/tests/chrome/inspector-styles-data.css new file mode 100644 index 0000000000..5c3652f522 --- /dev/null +++ b/devtools/server/tests/chrome/inspector-styles-data.css @@ -0,0 +1,3 @@ +.external-rule { + cursor: crosshair; +} diff --git a/devtools/server/tests/chrome/inspector-styles-data.html b/devtools/server/tests/chrome/inspector-styles-data.html new file mode 100644 index 0000000000..334b268bfd --- /dev/null +++ b/devtools/server/tests/chrome/inspector-styles-data.html @@ -0,0 +1,85 @@ +<html> +<script> + "use strict"; + + window.onload = () => { + window.opener.postMessage("ready", "*"); + }; +</script> +<style> + .inheritable-rule { + font-size: 15px; + } + /* Has to be on one line, is such for test */ + .column-rule { font-size: 20px; } .column-rule { font-size: 25px; } + .uninheritable-rule { + background-color: #f06; + } + @media screen { + #mediaqueried { + background-color: #f06; + } + } + #svgcontent rect { + fill: rgb(1,2,3); + } + + #layout-element, + #layout-auto-margin-element { + width: 50px; + height: 50px; + padding: 3px 5px 7px 5px; + border: 5px solid red; + margin: 10px 20px 30px 0; + box-sizing: border-box; + position: absolute; + z-index: 2; + } + + #layout-auto-margin-element { + margin: 10px auto; + } +</style> +<link type="text/css" rel="stylesheet" href="inspector-styles-data.css"></link> +<body> + <h1>Style Actor Tests</h1> + <!-- Inheritance checks --> + <div id="inheritable-rule-uninheritable-style" class="inheritable-rule" style="background-color: purple"> + <div id="inheritable-rule-inheritable-style" class="inheritable-rule" style="color: blue"> + <div id="uninheritable-rule-uninheritable-style" class="uninheritable-rule" style="background-color: green"> + <div id="uninheritable-rule-inheritable-style" class="uninheritable-rule" style="color: red"> + <div id="test-node"> + Here is the test node. + </div> + </div> + </div> + </div> + </div> + + <!-- Computed checks --> + <div id="computed-parent" class="external-rule inheritable-rule uninheritable-rule" style="color: red;"> + <div id="computed-test-node" class="external-rule"> + Here is the test node. + </div> + </div> + + <!-- Matched checks --> + <div id="matched-parent" class="external-rule inheritable-rule column-rule uninheritable-rule" style="color: red;"> + <div id="matched-test-node" style="font-size: 10px" class="external-rule"> + Here is the test node. + </div> + </div> + + <div id="mediaqueried"> + Screen mediaqueried. + </div> + + <div id="svgcontent"> + <svg><rect></rect></svg> + </div> + + <div id="layout-element">I can has layout</div> + <div id="layout-auto-margin-element">I can has layout too</div> + +</body> +</html> diff --git a/devtools/server/tests/chrome/inspector-template.html b/devtools/server/tests/chrome/inspector-template.html new file mode 100644 index 0000000000..13c9d5c7d3 --- /dev/null +++ b/devtools/server/tests/chrome/inspector-template.html @@ -0,0 +1,17 @@ +<html> +<body> + <template> + <p>template content</p> + </template> + <div></div> + <script> + "use strict"; + + const template = document.querySelector("template"); + const clone = document.importNode(template.content, true); + document.querySelector("div").appendChild(clone); + + window.opener.postMessage("ready", "*"); + </script> +</body> +</html> diff --git a/devtools/server/tests/chrome/inspector-traversal-data.html b/devtools/server/tests/chrome/inspector-traversal-data.html new file mode 100644 index 0000000000..e294796467 --- /dev/null +++ b/devtools/server/tests/chrome/inspector-traversal-data.html @@ -0,0 +1,103 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Inspector Traversal Test Data</title> + <style type="text/css"> + #pseudo::before { + content: "before"; + } + #pseudo::after { + content: "after"; + } + #pseudo-empty::before { + content: "before an empty element"; + } + #shadow::before { + content: "Testing ::before on a shadow host"; + } + </style> + <script type="text/javascript"> + "use strict"; + + window.onload = function() { + // Set up a basic shadow DOM + const host = document.querySelector("#shadow"); + if (host.attachShadow) { + const root = host.attachShadow({ mode: "open" }); + + const h3 = document.createElement("h3"); + h3.append("Shadow "); + + const em = document.createElement("em"); + em.append("DOM"); + + const select = document.createElement("select"); + select.setAttribute("multiple", ""); + h3.appendChild(em); + root.appendChild(h3); + root.appendChild(select); + } + + // Put a copy of the body in an iframe to test frame traversal. + const body = document.querySelector("body"); + const data = "data:text/html,<html>" + body.outerHTML + "<html>"; + const iframe = document.createElement("iframe"); + iframe.setAttribute("id", "childFrame"); + iframe.onload = function() { + window.opener.postMessage("ready", "*"); + }; + iframe.src = data; + body.appendChild(iframe); + }; + </script> +</head> +<body style="background-color:white"> + <h1>Inspector Actor Tests</h1> + <span id="longstring">longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong</span> + <span id="shortstring">short</span> + <span id="empty"></span> + <div id="longlist" data-test="exists"> + <div id="a">a</div> + <div id="b">b</div> + <div id="c">c</div> + <div id="d">d</div> + <div id="e">e</div> + <div id="f">f</div> + <div id="g">g</div> + <div id="h">h</div> + <div id="i">i</div> + <div id="j">j</div> + <div id="k">k</div> + <div id="l">l</div> + <div id="m">m</div> + <div id="n">n</div> + <div id="o">o</div> + <div id="p">p</div> + <div id="q">q</div> + <div id="r">r</div> + <div id="s">s</div> + <div id="t">t</div> + <div id="u">u</div> + <div id="v">v</div> + <div id="w">w</div> + <div id="x">x</div> + <div id="y">y</div> + <div id="z">z</div> + </div> + <div id="longlist-sibling"> + <div id="longlist-sibling-firstchild"></div> + </div> + <p id="edit-html"></p> + + <select multiple><option>one</option><option>two</option></select> + <div id="pseudo"><span>middle</span></div> + <div id="pseudo-empty"></div> + <div id="shadow">light dom</div> + <object> + <div id="1"></div> + </object> + <div class="node-to-duplicate"></div> + <div id="scroll-into-view" style="margin-top: 1000px;">scroll</div> +</body> +</html> diff --git a/devtools/server/tests/chrome/inspector_css-properties.html b/devtools/server/tests/chrome/inspector_css-properties.html new file mode 100644 index 0000000000..8cc6368cd1 --- /dev/null +++ b/devtools/server/tests/chrome/inspector_css-properties.html @@ -0,0 +1,12 @@ +<html> +<head> +<body> + <script type="text/javascript"> + "use strict"; + + window.onload = function() { + window.opener.postMessage("ready", "*"); + }; + </script> +</body> +</html> diff --git a/devtools/server/tests/chrome/inspector_display-type.html b/devtools/server/tests/chrome/inspector_display-type.html new file mode 100644 index 0000000000..7bd0da6709 --- /dev/null +++ b/devtools/server/tests/chrome/inspector_display-type.html @@ -0,0 +1,17 @@ +<html> +<head> +<body> + <div id="inline-block" style="display: inline-block"> + HELLO WORLD + </div> + <div id="grid" style="display: grid"></div> + <div id="block" style="position: fixed"></div> + <script> + "use strict"; + + window.onload = () => { + window.opener.postMessage("ready", "*"); + }; + </script> +</body> +</html> diff --git a/devtools/server/tests/chrome/inspector_getImageData.html b/devtools/server/tests/chrome/inspector_getImageData.html new file mode 100644 index 0000000000..754798df44 --- /dev/null +++ b/devtools/server/tests/chrome/inspector_getImageData.html @@ -0,0 +1,23 @@ +<html> +<head> +<body> + <img class="custom"> + <img class="big-horizontal" src="large-image.jpg" style="width:500px;"> + <canvas class="big-vertical" style="width:500px;"></canvas> + <img class="small" src="small-image.gif"> + <img class="data" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAAJklEQVRIie3NMREAAAgAoe9fWls4eAzMVM0xoVAoFAqFQqFQ+C9chp4NHvu+4Q4AAAAASUVORK5CYII="> + <script> + "use strict"; + + window.onload = () => { + const canvas = document.querySelector("canvas"), ctx = canvas.getContext("2d"); + canvas.width = 1000; + canvas.height = 2000; + ctx.fillStyle = "red"; + ctx.fillRect(0, 0, 1000, 2000); + + window.opener.postMessage("ready", "*"); + }; + </script> +</body> +</html> diff --git a/devtools/server/tests/chrome/inspector_getOffsetParent.html b/devtools/server/tests/chrome/inspector_getOffsetParent.html new file mode 100644 index 0000000000..72aac5f70b --- /dev/null +++ b/devtools/server/tests/chrome/inspector_getOffsetParent.html @@ -0,0 +1,18 @@ +<html> +<head> +<body> + <div id="relative_parent" style="position: relative"> + <div id="absolute_child" style="position: absolute"></div> + </div> + <div id="static"></div> + <div id="no_parent" style="position: absolute"></div> + <div id="fixed" style="position: fixed"></div> + <script> + "use strict"; + + window.onload = () => { + window.opener.postMessage("ready", "*"); + }; + </script> +</body> +</html> diff --git a/devtools/server/tests/chrome/large-image.jpg b/devtools/server/tests/chrome/large-image.jpg Binary files differnew file mode 100644 index 0000000000..bda383e594 --- /dev/null +++ b/devtools/server/tests/chrome/large-image.jpg diff --git a/devtools/server/tests/chrome/memory-helpers.js b/devtools/server/tests/chrome/memory-helpers.js new file mode 100644 index 0000000000..f94e1f1a9e --- /dev/null +++ b/devtools/server/tests/chrome/memory-helpers.js @@ -0,0 +1,53 @@ +/* exported Task, startServerAndGetSelectedTabMemory, destroyServerAndFinish, + waitForTime, waitUntil */ +"use strict"; + +const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); +const Services = require("Services"); +const { TargetFactory } = require("devtools/client/framework/target"); + +// Always log packets when running tests. +Services.prefs.setBoolPref("devtools.debugger.log", true); +var gReduceTimePrecision = Services.prefs.getBoolPref( + "privacy.reduceTimerPrecision" +); +Services.prefs.setBoolPref("privacy.reduceTimerPrecision", false); +SimpleTest.registerCleanupFunction(function() { + Services.prefs.clearUserPref("devtools.debugger.log"); + Services.prefs.setBoolPref( + "privacy.reduceTimerPrecision", + gReduceTimePrecision + ); +}); + +async function getTargetForSelectedTab() { + const browserWindow = Services.wm.getMostRecentWindow("navigator:browser"); + const target = await TargetFactory.forTab(browserWindow.gBrowser.selectedTab); + return target; +} + +async function startServerAndGetSelectedTabMemory() { + const target = await getTargetForSelectedTab(); + const memory = await target.getFront("memory"); + return { memory, target }; +} + +async function destroyServerAndFinish(target) { + await target.destroy(); + SimpleTest.finish(); +} + +function waitForTime(ms) { + return new Promise((resolve, reject) => { + setTimeout(resolve, ms); + }); +} + +function waitUntil(predicate) { + if (predicate()) { + return Promise.resolve(true); + } + return new Promise(resolve => + setTimeout(() => waitUntil(predicate).then(() => resolve(true)), 10) + ); +} diff --git a/devtools/server/tests/chrome/nonchrome_unsafeDereference.html b/devtools/server/tests/chrome/nonchrome_unsafeDereference.html new file mode 100644 index 0000000000..15e9fd9160 --- /dev/null +++ b/devtools/server/tests/chrome/nonchrome_unsafeDereference.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<script> +"use strict"; + +var xhr = new XMLHttpRequest(); +xhr.timeout = 1742; +xhr.expando = "Expando!"; +</script> +</html> diff --git a/devtools/server/tests/chrome/small-image.gif b/devtools/server/tests/chrome/small-image.gif Binary files differnew file mode 100644 index 0000000000..e702427a53 --- /dev/null +++ b/devtools/server/tests/chrome/small-image.gif diff --git a/devtools/server/tests/chrome/suspendTimeouts_content.html b/devtools/server/tests/chrome/suspendTimeouts_content.html new file mode 100644 index 0000000000..f3969fc10c --- /dev/null +++ b/devtools/server/tests/chrome/suspendTimeouts_content.html @@ -0,0 +1 @@ +<script src='suspendTimeouts_content.js'></script> diff --git a/devtools/server/tests/chrome/suspendTimeouts_content.js b/devtools/server/tests/chrome/suspendTimeouts_content.js new file mode 100644 index 0000000000..0e0cc19788 --- /dev/null +++ b/devtools/server/tests/chrome/suspendTimeouts_content.js @@ -0,0 +1,73 @@ +"use strict"; + +// To make it easier to follow, this code is arranged so that the functions are +// arranged in the order they are called. + +const worker = new Worker("suspendTimeouts_worker.js"); +worker.onerror = error => { + const message = `error from worker: ${error.filename}:${error.lineno}: ${error.message}`; + throw new Error(message); +}; + +// Create a message channel. Send one end to the worker, and return the other to +// the mochitest. +/* exported create_channel */ +function create_channel() { + const { port1, port2 } = new MessageChannel(); + info(`sending port to worker`); + worker.postMessage({ mochitestPort: port1 }, [port1]); + return port2; +} + +// Provoke the worker into sending us a message, and then refuse to receive said +// message, causing it to be delayed for later delivery. +// +// The worker will also post a message to the MessagePort we sent it earlier. +// That message should not be delayed, as it is handled by the mochitest window, +// not the content window. Its receipt signals that the test can assume that the +// runnable for step 2) is in the main thread's event queue, so the test can +// prepare for step 3). +/* exported start_worker */ +function start_worker() { + worker.onmessage = handle_echo; + + // This should prevent worker.onmessage from being called, until + // resumeTimeouts is called. + // + // This function is provided by test_suspendTimeouts.js. + // eslint-disable-next-line no-undef + suspendTimeouts(); + + // The worker should echo this message back to us and to the mochitest. + worker.postMessage("HALLOOOOOO"); // suitable message for echoing + info(`posted message to worker`); +} + +var resumeTimeouts_has_returned = false; + +// Resume timeouts. After this call, the worker's message should not be +// delivered to our onmessage handler until control returns to the event loop. +/* exported resume_timeouts */ +function resume_timeouts() { + // This function is provided by test_suspendTimeouts.js. + // eslint-disable-next-line no-undef + resumeTimeouts(); // onmessage handlers should not run from this call. + + resumeTimeouts_has_returned = true; + + // When this JavaScript invocation returns to the main thread's event loop, + // only then should onmessage handlers be invoked. +} + +// The buggy code calls this handler from the resumeTimeouts call, before the +// main thread returns to the event loop. The correct code calls this only once +// the JavaScript invocation that called resumeTimeouts has run to completion. +function handle_echo({ data }) { + ok( + resumeTimeouts_has_returned, + "worker message delivered from main event loop" + ); + + // Finish the mochitest. + finish(); +} diff --git a/devtools/server/tests/chrome/suspendTimeouts_worker.js b/devtools/server/tests/chrome/suspendTimeouts_worker.js new file mode 100644 index 0000000000..e008f7d0d3 --- /dev/null +++ b/devtools/server/tests/chrome/suspendTimeouts_worker.js @@ -0,0 +1,12 @@ +"use strict"; + +// Once content sends us a port connected to the mochitest, we simply echo every +// message we receive back to content and the mochitest. +onmessage = ({ data: { mochitestPort } }) => { + onmessage = ({ data }) => { + // Send a message to both content and the mochitest, which the main thread's + // event loop will attempt to deliver as step 2). + postMessage(`worker echo to content: ${data}`); + mochitestPort.postMessage(`worker echo to port: ${data}`); + }; +}; diff --git a/devtools/server/tests/chrome/test_Debugger.Script.prototype.global.html b/devtools/server/tests/chrome/test_Debugger.Script.prototype.global.html new file mode 100644 index 0000000000..7009f9eae2 --- /dev/null +++ b/devtools/server/tests/chrome/test_Debugger.Script.prototype.global.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=958646 + +Debugger.Script.prototype.global should return innerize globals, not WindowProxies. +--> +<head> + <meta charset="utf-8"> + <title>Debugger.Script.prototype.global should return inner windows</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm"); +addDebuggerToGlobal(this); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + const iframe = document.createElement("iframe"); + iframe.src = "data:text/html,<script>function glorp() { }<\/script>"; + iframe.onload = firstOnLoadHandler; + document.body.appendChild(iframe); + + function firstOnLoadHandler() { + const dbg = new Debugger(); + const iframeDO = dbg.addDebuggee(iframe.contentWindow); + + // For sanity: check that the debuggee global is the inner window, + // and that the outer window gets a distinct D.O. + const iframeWindowProxyDO = iframeDO.makeDebuggeeValue(iframe.contentWindow); + ok(iframeDO !== iframeWindowProxyDO); + + // The real test: Debugger.Script.prototype.global returns inner windows. + ok(iframeDO.getOwnPropertyDescriptor("glorp").value.script.global === iframeDO); + + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_Debugger.Source.prototype.element.html b/devtools/server/tests/chrome/test_Debugger.Source.prototype.element.html new file mode 100644 index 0000000000..6244bcb0de --- /dev/null +++ b/devtools/server/tests/chrome/test_Debugger.Source.prototype.element.html @@ -0,0 +1,182 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=941876 + +Debugger.Source.prototype.element and .elementAttributeName should report the DOM +element to which code is attached (if any), and how. +--> +<head> + <meta charset="utf-8"> + <title>Debugger.Source.prototype.element should return owning element</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +const {addSandboxedDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm"); +addSandboxedDebuggerToGlobal(this); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + let log = ""; + let doc, dieter, ulrich, isolde, albrecht; + let dbg, iframeDO, DOFor; + + // Create an iframe to debug. + // We can't use a data: URL here, because we want to test script elements + // that refer to the JavaScript via 'src' attributes, and data: documents + // can't refer to those. So we use a separate HTML document. + const iframe = document.createElement("iframe"); + iframe.src = "Debugger.Source.prototype.element.html"; + iframe.onload = onLoadHandler; + document.body.appendChild(iframe); + + function onLoadHandler() { + log += "l"; + + // Now that the iframe's window has been created, we can add + // it as a debuggee. + dbg = new Debugger(); + dbg.onDebuggerStatement = franzDebuggerHandler; + iframeDO = dbg.addDebuggee(iframe.contentWindow); + DOFor = iframeDO.makeDebuggeeValue.bind(iframeDO); + + // Send a click event to heidi. + doc = iframe.contentWindow.document; + doc.getElementById("heidi").dispatchEvent(new Event("click")); + } + + function franzDebuggerHandler(frame) { + log += "f"; + + // The top stack frame should be franz, belonging to the script element. + ok(frame.callee.displayName === "franz", "top frame is franz"); + ok(frame.script.source.element === DOFor(doc.getElementById("franz")), + "top frame source belongs to element franz"); + ok(frame.script.source.elementAttributeName === undefined, + "top frame source doesn't belong to an attribute"); + + // The second stack frame should belong to heinrich. + ok(frame.older.script.source.element === DOFor(doc.getElementById("heinrich")), + "second frame source belongs to element heinrich"); + ok(frame.older.script.source.elementAttributeName === undefined, + "second frame source doesn't belong to an attribute"); + + // The next stack frame should belong to heidi's onclick handler. + ok(frame.older.older.script.source.element === DOFor(doc.getElementById("heidi")), + "third frame source belongs to element heidi"); + ok(frame.older.older.script.source.elementAttributeName === "onclick", + "third frame source belongs to 'onclick' attribute"); + + // Try a dynamically inserted inline script element. + ulrich = doc.createElement("script"); + ulrich.text = "debugger;"; + dbg.onDebuggerStatement = ulrichDebuggerHandler; + doc.body.appendChild(ulrich); + } + + function ulrichDebuggerHandler(frame) { + log += "u"; + + // The top frame should be ulrich's text. + ok(frame.script.source.element === DOFor(ulrich), + "top frame belongs to ulrich"); + ok(frame.script.source.elementAttributeName === undefined, + "top frame is not on an attribute of ulrich"); + + // Try a dynamically inserted out-of-line script element. + isolde = doc.createElement("script"); + isolde.setAttribute("src", "Debugger.Source.prototype.element-2.js"); + isolde.setAttribute("id", "idolde, my dear"); + dbg.onDebuggerStatement = isoldeDebuggerHandler; + doc.body.appendChild(isolde); + } + + function isoldeDebuggerHandler(frame) { + log += "i"; + + // The top frame should belong to isolde. + ok(frame.script.source.element === DOFor(isolde), + "top frame belongs to isolde"); + info("frame.script.source.element is: " + uneval(frame.script.source.element)); + if (typeof frame.script.source.element.unsafeDereference() == "object") { + info(" toString: " + frame.script.source.element.unsafeDereference()); + info(" id: " + frame.script.source.element.unsafeDereference().id); + } + + ok(frame.script.source.elementAttributeName === undefined, + "top frame source is not an attribute of isolde"); + info("frame.script.source.elementAttributeName is: " + + uneval(frame.script.source.elementAttributeName)); + + // Try a dynamically created div element with a handler. + dieter = doc.createElement("div"); + dieter.setAttribute("id", "dieter"); + dieter.setAttribute("ondrag", "debugger;"); + dbg.onDebuggerStatement = dieterDebuggerHandler; + dieter.dispatchEvent(new Event("drag")); + } + + function dieterDebuggerHandler(frame) { + log += "d"; + + // The top frame should belong to dieter's ondrag handler. + ok(frame.script.source.element === DOFor(dieter), + "second event's handler belongs to dieter"); + ok(frame.script.source.elementAttributeName === "ondrag", + "second event's handler is on dieter's 'ondrag' element"); + + // Try sending an 'onresize' event to the window. + // + // Note that we only want Debugger to see the events we send, not any + // genuine resize events accidentally generated by the test harness (see bug + // 1162067). So we mark our events as cancelable; that seems to be the only + // bit chrome can fiddle on an Event that content code will see and that + // won't affect propagation. Then, the content event only runs its + // 'debugger' statement when the event is cancelable. It's a kludge. + dbg.onDebuggerStatement = resizeDebuggerHandler; + iframe.contentWindow.dispatchEvent(new Event("resize", { cancelable: true })); + } + + function resizeDebuggerHandler(frame) { + log += "e"; + + // The top frame should belong to the body's 'onresize' handler, even + // though we sent the message to the window and it was handled. + ok(frame.script.source.element === DOFor(doc.body), + "onresize event handler belongs to body element"); + ok(frame.script.source.elementAttributeName === "onresize", + "onresize event handler is on body element's 'onresize' attribute"); + + // In SVG, the event and the attribute that holds that event's handler + // have different names. Debugger.Source.prototype.elementAttributeName + // should report (as one might infer) the attribute name, not the event + // name. + albrecht = doc.createElementNS("http://www.w3.org/2000/svg", "svg"); + albrecht.setAttribute("onload", "debugger;"); + dbg.onDebuggerStatement = SVGLoadHandler; + albrecht.dispatchEvent(new Event("SVGLoad")); + } + + function SVGLoadHandler(frame) { + log += "s"; + + // The top frame's source should be on albrecht's 'onload' attribute. + ok(frame.script.source.element === DOFor(albrecht), + "SVGLoad event handler belongs to albrecht"); + ok(frame.script.source.elementAttributeName === "onload", + "SVGLoad event handler is on albrecht's 'onload' attribute"); + + ok(log === "lfuides", "all tests actually ran"); + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionScript.html b/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionScript.html new file mode 100644 index 0000000000..31a0c9efc7 --- /dev/null +++ b/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionScript.html @@ -0,0 +1,97 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=969786 + +Debugger.Source.prototype.introductionScript and .introductionOffset should +behave when 'eval' is called with no scripted frames active at all. +--> +<head> + <meta charset="utf-8"> + <title>Debugger.Source.prototype.introductionScript with no caller</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm"); +addDebuggerToGlobal(this); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + let dbg, iframeDO, doc, script2DO; + + // Create an iframe to debug. + const iframe = document.createElement("iframe"); + iframe.src = "data:text/html,<div>Hi!</div>"; + iframe.onload = onLoadHandler; + document.body.appendChild(iframe); + + function onLoadHandler() { + // Now that the iframe's window has been created, we can add + // it as a debuggee. + dbg = new Debugger(); + iframeDO = dbg.addDebuggee(iframe.contentWindow); + + doc = iframe.contentWindow.document; + const script = doc.createElement("script"); + script.text = "setTimeout(eval.bind(null, 'debugger;'), 0);"; + dbg.onDebuggerStatement = timerHandler; + doc.body.appendChild(script); + } + + function timerHandler(frame) { + // The top stack frame's source should have an undefined + // introduction script and introduction offset. + const source = frame.script.source; + ok(source.introductionScript === undefined, + "setTimeout eval introductionScript is undefined"); + ok(source.introductionOffset === undefined, + "setTimeout eval introductionOffset is undefined"); + + // Check that the above isn't just some quirk of iframes, or the + // browser milieu destroying information: an eval script should indeed + // have proper introduction information. + const script2 = doc.createElement("script"); + script2.text = "eval('debugger;');"; + script2DO = iframeDO.makeDebuggeeValue(script2); + dbg.onDebuggerStatement = evalHandler; + doc.body.appendChild(script2); + } + + function evalHandler(frame) { + // The top stack frame's source should be introduced by the script that + // called eval. + const source = frame.script.source; + const frame2 = frame.older; + const frame3 = frame2.older; + + ok(source.introductionType === "eval", + "top frame's source was introduced by 'eval'"); + ok(source.introductionScript === frame2.script, + "eval frame's introduction script is the older frame's script"); + ok(source.introductionOffset === frame2.offset, + "eval frame's introduction offset is current offset in older frame"); + ok(source.introductionScript.source.element === script2DO, + "eval frame's introducer belongs to script2 element"); + + // The frame that called eval, in turn, was introduced at the call that + // inserted the script element into the document. + ok(frame2.script.source.introductionType === "injectedScript", + "older frame has no introduction type"); + ok(frame2.script.source.introductionScript === frame3.script, + "older frame has introduction script"); + ok(frame2.script.source.introductionOffset === frame3.offset, + "older frame has introduction offset"); + + SimpleTest.finish(); + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionType.html b/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionType.html new file mode 100644 index 0000000000..1a5ec2d5cb --- /dev/null +++ b/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionType.html @@ -0,0 +1,169 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=935203 + +Debugger.Source.prototype.introductionType should return 'eventHandler' for +JavaScrip appearing in an inline event handler attribute. +--> +<head> + <meta charset="utf-8"> + <title>Debugger.Source.prototype.introductionType should identify event handlers</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="inspector-helpers.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +const {addSandboxedDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm"); +addSandboxedDebuggerToGlobal(this); + +let dbg; +let iframeDO, doc; +let Tootles, TootlesDO; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +addTest(function setup() { + // Create an iframe to debug. + const iframe = document.createElement("iframe"); + iframe.srcdoc = "<div id='Tootles' onclick='debugger;'>I'm a DIV!</div>" + + "<script id='Auddie'>function auddie() { debugger; }<\/script>"; + iframe.onload = onLoadHandler; + document.body.appendChild(iframe); + + function onLoadHandler() { + // Now that the iframe's window has been created, we can add + // it as a debuggee. + dbg = new Debugger(); + iframeDO = dbg.addDebuggee(iframe.contentWindow); + doc = iframe.contentWindow.document; + Tootles = doc.getElementById("Tootles"); + TootlesDO = iframeDO.makeDebuggeeValue(Tootles); + + runNextTest(); + } +}); + +// Check the introduction type of in-markup event handler code. +// Send a click event to Tootles, whose handler has a 'debugger' statement, +// and check that script's introduction type. +addTest(function ClickOnTootles() { + dbg.onDebuggerStatement = TootlesClickDebugger; + Tootles.dispatchEvent(new Event("click")); + + function TootlesClickDebugger(frame) { + // some sanity checks + ok(frame.script.source.element === TootlesDO, + "top frame source belongs to element 'Tootles'"); + is(frame.script.source.elementAttributeName, "onclick", + "top frame source belongs to 'onclick' attribute"); + + // And, the actual point of this test: + is(frame.script.source.introductionType, "eventHandler", + "top frame source's introductionType is 'eventHandler'"); + + runNextTest(); + } +}); + +// Check the introduction type of dynamically added event handler code. +// Add a drag event handler to Tootles as a string, and then send +// Tootles a drag event. +addTest(function DragTootles() { + dbg.onDebuggerStatement = TootlesDragDebugger; + Tootles.setAttribute("ondrag", "debugger;"); + Tootles.dispatchEvent(new Event("drag")); + + function TootlesDragDebugger(frame) { + // sanity checks + ok(frame.script.source.element === TootlesDO, + "top frame source belongs to element 'Tootles'"); + is(frame.script.source.elementAttributeName, "ondrag", + "top frame source belongs to 'ondrag' attribute"); + + // And, the actual point of this test: + is(frame.script.source.introductionType, "eventHandler", + "top frame source's introductionType is 'eventHandler'"); + + runNextTest(); + } +}); + +// Check the introduction type of an in-markup script element. +addTest(function checkAuddie() { + const fnDO = iframeDO.getOwnPropertyDescriptor("auddie").value; + const AuddieDO = iframeDO.makeDebuggeeValue(doc.getElementById("Auddie")); + + is(fnDO.class, "Function", + "Script element 'Auddie' defined function 'auddie'."); + ok(fnDO.script.source.element === AuddieDO, + "Function auddie's script belongs to script element 'Auddie'"); + is(fnDO.script.source.elementAttributeName, undefined, + "Function auddie's script doesn't belong to any attribute of 'Auddie'"); + is(fnDO.script.source.introductionType, "inlineScript", + "Function auddie's script's source was introduced by a script element"); + + runNextTest(); +}); + +// Check the introduction type of a dynamically inserted script element. +addTest(function InsertRover() { + dbg.onDebuggerStatement = RoverDebugger; + const rover = doc.createElement("script"); + const roverDO = iframeDO.makeDebuggeeValue(rover); + rover.text = "debugger;"; + doc.body.appendChild(rover); + + function RoverDebugger(frame) { + // sanity checks + ok(frame.script.source.element === roverDO, + "Rover script belongs to Rover"); + ok(frame.script.source.elementAttributeName === undefined, + "Rover script doesn't belong to an attribute of Rover"); + + // Check the introduction type. + ok(frame.script.source.introductionType === "injectedScript", + "Rover script's introduction type is 'injectedScript'"); + + runNextTest(); + } +}); + +// Creates a chrome document with a XUL script element, and check its introduction type. +addTest(function XULDocumentScript() { + const frame = document.createElement("iframe"); + frame.src = "doc_Debugger.Source.prototype.introductionType.xhtml"; + frame.onload = docLoaded; + info("Appending iframe containing a document with a XUL script tag"); + document.body.appendChild(frame); + + function docLoaded() { + info("Loaded chrome document"); + const xulFrameDO = dbg.addDebuggee(frame.contentWindow); + const xulFnDO = xulFrameDO.getOwnPropertyDescriptor("xulScriptFunc").value; + is(typeof xulFnDO, "object", "XUL script element defined 'xulScriptFunc'"); + is(xulFnDO.class, "Function", + "XUL global 'xulScriptFunc' is indeed a function"); + + // A XUL script elements' code gets shared amongst all + // instantiations of the document, so there's no specific DOM element + // we can attribute the code to. + is(xulFnDO.script.source.element, undefined, + "XUL script code should not be attributed to any individual element"); + + is(xulFnDO.script.source.introductionType, "inlineScript", + "xulScriptFunc's introduction type is 'inlineScript'"); + runNextTest(); + } +}); +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_animation-type-longhand.html b/devtools/server/tests/chrome/test_animation-type-longhand.html new file mode 100644 index 0000000000..98ae0ffe9d --- /dev/null +++ b/devtools/server/tests/chrome/test_animation-type-longhand.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>Test animation-type-longhand</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<body> +<script> + "use strict"; + + // This test checks the content of animation type for longhands table that + // * every longhand property is included + // * nothing else is included + // * no property is mapped to more than one animation type + window.onload = function() { + const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); + const { ANIMATION_TYPE_FOR_LONGHANDS } = + require("devtools/server/actors/animation-type-longhand"); + const InspectorUtils = SpecialPowers.InspectorUtils; + + const all_longhands = InspectorUtils.getCSSPropertyNames({ + includeShorthands: false, + includeExperimentals: true, + }); + + const unseen_longhands = new Set(all_longhands); + const seen_longhands = new Set(); + for (const [, names] of ANIMATION_TYPE_FOR_LONGHANDS) { + for (const name of names) { + ok(!seen_longhands.has(name), + `${name} should have only one animation type`); + ok(unseen_longhands.has(name), + `${name} is an unseen longhand property`); + unseen_longhands.delete(name); + seen_longhands.add(name); + } + } + is(unseen_longhands.size, 0, + "All longhands should be mapped to some animation type"); + + SimpleTest.finish(); + }; +</script> +</body> diff --git a/devtools/server/tests/chrome/test_css-logic-media-queries.html b/devtools/server/tests/chrome/test_css-logic-media-queries.html new file mode 100644 index 0000000000..947062c9cb --- /dev/null +++ b/devtools/server/tests/chrome/test_css-logic-media-queries.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that css-logic handles media-queries correctly +--> +<head> + <meta charset="utf-8"> + <title>Test css-logic media-queries</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <style> + div { + width: 1000px; + height: 100px; + background-color: #f00; + } + + @media screen and (min-width: 1px) { + div { + width: 200px; + } + } + </style> +</head> +<body> + <div></div> + <script type="application/javascript"> + "use strict"; + + window.onload = function() { + const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); + const Services = require("Services"); + const {CssLogic} = require("devtools/server/actors/inspector/css-logic"); + + SimpleTest.waitForExplicitFinish(); + + const div = document.querySelector("div"); + const cssLogic = new CssLogic(); + cssLogic.highlight(div); + cssLogic.processMatchedSelectors(); + + const _strings = Services.strings + .createBundle("chrome://devtools-shared/locale/styleinspector.properties"); + + const inline = _strings.GetStringFromName("rule.sourceInline"); + + const source1 = inline + ":12"; + const source2 = inline + ":19 @media screen and (min-width: 1px)"; + is(cssLogic._matchedRules[0][0].source, source1, + "rule.source gives correct output for rule 1"); + is(cssLogic._matchedRules[1][0].source, source2, + "rule.source gives correct output for rule 2"); + + SimpleTest.finish(); + }; + </script> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_css-logic-specificity.html b/devtools/server/tests/chrome/test_css-logic-specificity.html new file mode 100644 index 0000000000..e4ec21aeda --- /dev/null +++ b/devtools/server/tests/chrome/test_css-logic-specificity.html @@ -0,0 +1,82 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that css-logic calculates CSS specificity properly +--> +<head> + <meta charset="utf-8"> + <title>Test css-logic specificity</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +</head> +<body style="background:blue;"> + <script type="application/javascript"> + "use strict"; + + window.onload = function() { + const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); + const {CssLogic, CssSelector} = require("devtools/server/actors/inspector/css-logic"); + const InspectorUtils = SpecialPowers.InspectorUtils; + + const TEST_DATA = [ + {text: "*", expected: 0}, + {text: "LI", expected: 1}, + {text: "UL LI", expected: 2}, + {text: "UL OL + LI", expected: 3}, + {text: "H1 + [REL=\"up\"]", expected: 1025}, + {text: "UL OL LI.red", expected: 1027}, + {text: "LI.red.level", expected: 2049}, + {text: ".red .level", expected: 2048}, + {text: "#x34y", expected: 1048576}, + {text: "#s12:not(FOO)", expected: 1048577}, + {text: "body#home div#warning p.message", expected: 2098179}, + {text: "* body#home div#warning p.message", expected: 2098179}, + {text: "#footer :not(nav) li", expected: 1048578}, + {text: "bar:nth-child(n)", expected: 1025}, + {text: "li::marker", expected: 2}, + {text: "a:hover", expected: 1025}, + ]; + + function createDocument() { + let text = TEST_DATA.map(i=>i.text).join(","); + text = '<style type="text/css">' + text + " {color:red;}</style>"; + // eslint-disable-next-line no-unsanitized/property + document.body.innerHTML = text; + } + + function getExpectedSpecificity(selectorText) { + return TEST_DATA.filter(i => i.text === selectorText)[0].expected; + } + + SimpleTest.waitForExplicitFinish(); + + createDocument(); + const cssLogic = new CssLogic(); + + cssLogic.highlight(document.body); + const cssSheet = cssLogic.sheets[0]; + const cssRule = cssSheet.domSheet.cssRules[0]; + const selectors = CssLogic.getSelectors(cssRule); + + info("Iterating over the test selectors"); + for (let i = 0; i < selectors.length; i++) { + const selectorText = selectors[i]; + info("Testing selector " + selectorText); + + const selector = new CssSelector(cssRule, selectorText, i); + const expected = getExpectedSpecificity(selectorText); + const specificity = InspectorUtils.getSpecificity(selector.cssRule, + selector.selectorIndex); + is(specificity, expected, + 'Selector "' + selectorText + '" has a specificity of ' + expected); + } + + info("Testing specificity of element.style"); + const colorProp = cssLogic.getPropertyInfo("background"); + is(colorProp.matchedSelectors[0].specificity, 0x40000000, + "Element styles have specificity of 0x40000000 (1073741824)."); + + SimpleTest.finish(); + }; + </script> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_css-logic.html b/devtools/server/tests/chrome/test_css-logic.html new file mode 100644 index 0000000000..3bbc571e7d --- /dev/null +++ b/devtools/server/tests/chrome/test_css-logic.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +const {CssLogic} = require("devtools/server/actors/inspector/css-logic"); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +addTest(function getComputedStyle() { + const node = document.querySelector("#computed-style"); + is(CssLogic.getComputedStyle(node).getPropertyValue("width"), + "50px", "Computed style on a normal node works (width)"); + is(CssLogic.getComputedStyle(node).getPropertyValue("height"), + "10px", "Computed style on a normal node works (height)"); + + const firstChild = new _documentWalker(node, window).firstChild(); + is(CssLogic.getComputedStyle(firstChild).getPropertyValue("content"), + "\"before\"", "Computed style on a ::before node works (content)"); + const lastChild = new _documentWalker(node, window).lastChild(); + is(CssLogic.getComputedStyle(lastChild).getPropertyValue("content"), + "\"after\"", "Computed style on a ::after node works (content)"); + + runNextTest(); +}); + +addTest(function getBindingElementAndPseudo() { + const node = document.querySelector("#computed-style"); + let {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(node); + + is(bindingElement, node, + "Binding element is the node itself for a normal node"); + ok(!pseudo, "Pseudo is null for a normal node"); + + const firstChild = new _documentWalker(node, window).firstChild(); + ({ bindingElement, pseudo } = CssLogic.getBindingElementAndPseudo(firstChild)); + is(bindingElement, node, + "Binding element is the parent for a pseudo node"); + is(pseudo, ":before", "Pseudo is correct for a ::before node"); + + const lastChild = new _documentWalker(node, window).lastChild(); + ({ bindingElement, pseudo } = CssLogic.getBindingElementAndPseudo(lastChild)); + is(bindingElement, node, + "Binding element is the parent for a pseudo node"); + is(pseudo, ":after", "Pseudo is correct for a ::after node"); + + runNextTest(); +}); + + </script> +</head> +<body> + <style type="text/css"> + #computed-style { width: 50px; height: 10px; } + #computed-style::before { content: "before"; } + #computed-style::after { content: "after"; } + </style> + <div id="computed-style"></div> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_css-properties.html b/devtools/server/tests/chrome/test_css-properties.html new file mode 100644 index 0000000000..8d1dc9022f --- /dev/null +++ b/devtools/server/tests/chrome/test_css-properties.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1265798 - Replace inIDOMUtils.cssPropertyIsShorthand +--> +<head> + <meta charset="utf-8"> + <title>Test CSS Properties Actor</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + function toSortedString(array) { + return JSON.stringify(array.sort()); + } + + const runCssPropertiesTests = async function(url) { + info(`Opening tab with CssPropertiesActor support.`); + // Open a new tab. The only property we are interested in is `target`. + const { target } = await attachURL(url); + const { cssProperties } = await target.getFront("cssProperties"); + + ok(cssProperties.isKnown("border"), + "The `border` shorthand property is known."); + ok(cssProperties.isKnown("display"), + "The `display` property is known."); + ok(!cssProperties.isKnown("foobar"), + "A fake property is not known."); + ok(cssProperties.isKnown("--foobar"), + "A CSS variable properly evaluates."); + ok(cssProperties.isKnown("--foob\\{ar"), + "A CSS variable with escaped character properly evaluates."); + ok(cssProperties.isKnown("--fübar"), + "A CSS variable unicode properly evaluates."); + ok(!cssProperties.isKnown("--foo bar"), + "A CSS variable with spaces fails"); + + is(toSortedString(cssProperties.getValues("margin")), + toSortedString(["auto", "inherit", "initial", "unset", "revert"]), + "Can get values for the CSS margin."); + is(cssProperties.getValues("foobar").length, 0, + "Unknown values return an empty array."); + + const bgColorValues = cssProperties.getValues("background-color"); + ok(bgColorValues.includes("blanchedalmond"), + "A property with color values includes blanchedalmond."); + ok(bgColorValues.includes("papayawhip"), + "A property with color values includes papayawhip."); + ok(bgColorValues.includes("rgb"), + "A property with color values includes non-colors."); + }; + + addAsyncTest(async function setup() { + const url = document.getElementById("cssProperties").href; + await runCssPropertiesTests(url); + + runNextTest(); + }); + + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + </script> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265798">Mozilla Bug 1265798</a> + <a id="cssProperties" target="_blank" href="inspector_css-properties.html">Test Document</a> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_device.html b/devtools/server/tests/chrome/test_device.html new file mode 100644 index 0000000000..4567636b47 --- /dev/null +++ b/devtools/server/tests/chrome/test_device.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 895360 - [app manager] Device meta data actor +--> +<head> + <meta charset="utf-8"> + <title>Mozilla Bug</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +window.onload = function() { + const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); + const {DevToolsClient} = require("devtools/client/devtools-client"); + const {DevToolsServer} = require("devtools/server/devtools-server"); + const Services = require("Services"); + + SimpleTest.waitForExplicitFinish(); + + DevToolsServer.init(); + DevToolsServer.registerAllActors(); + + const client = new DevToolsClient(DevToolsServer.connectPipe()); + client.connect().then(function onConnect() { + return client.mainRoot.getFront("device"); + }).then(function(d) { + let desc; + const appInfo = Services.appinfo; + const utils = window.windowUtils; + + const localDesc = { + appid: appInfo.ID, + vendor: appInfo.vendor, + name: appInfo.name, + version: appInfo.version, + appbuildid: appInfo.appBuildID, + platformbuildid: appInfo.platformBuildID, + platformversion: appInfo.platformVersion, + geckobuildid: appInfo.platformBuildID, + geckoversion: appInfo.platformVersion, + useragent: window.navigator.userAgent, + locale: Services.locale.appLocaleAsBCP47, + os: appInfo.OS, + processor: appInfo.XPCOMABI.split("-")[0], + compiler: appInfo.XPCOMABI.split("-")[1], + dpi: utils.displayDPI, + width: window.screen.width, + height: window.screen.height, + }; + + function checkValues() { + for (const key in localDesc) { + is(desc[key], localDesc[key], "valid field (" + key + ")"); + } + + const currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile); + const profileDir = currProfD.path; + ok(profileDir.includes(desc.profile.length > 0 && desc.profile), + "valid profile name"); + + client.close().then(() => { + DevToolsServer.destroy(); + SimpleTest.finish(); + }); + } + + d.getDescription().then(function(v) { + desc = v; + }).then(checkValues); + }); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_executeInGlobal-outerized_this.html b/devtools/server/tests/chrome/test_executeInGlobal-outerized_this.html new file mode 100644 index 0000000000..f16ab33a77 --- /dev/null +++ b/devtools/server/tests/chrome/test_executeInGlobal-outerized_this.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=837060 + +When we use Debugger.Object.prototype.executeInGlobal, the 'this' value seen +by the evaluated code should be the WindowProxy, not the inner window +object. +--> +<head> + <meta charset="utf-8"> + <title>Mozilla Bug 837060</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm"); +addDebuggerToGlobal(this); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + const iframe = document.createElement("iframe"); + iframe.src = "data:text/html,<script>var me = 'page 1';<\/script>"; + iframe.onload = firstOnLoadHandler; + document.body.appendChild(iframe); + + function firstOnLoadHandler() { + const dbg = new Debugger(); + const page1DO = dbg.addDebuggee(iframe.contentWindow); + iframe.src = "data:text/html,<script>var me = 'page 2';<\/script>"; + iframe.onload = function() { + const page2DO = dbg.addDebuggee(iframe.contentWindow); + ok(page1DO !== page2DO, "the two pages' globals get distinct D.O's"); + ok(page1DO.unsafeDereference() === page2DO.unsafeDereference(), + "unwrapping page1DO and page2DO outerizes both, yielding the same outer window"); + + is(page1DO.executeInGlobal("me").return, + "page 1", "page1DO continues to refer to original page"); + is(page2DO.executeInGlobal("me").return, "page 2", + "page2DO refers to current page"); + + is(page1DO.executeInGlobal("this === window").return, true, + "page 1: Debugger.Object.prototype.executeInGlobal should outerize 'this'"); + is(page1DO.executeInGlobalWithBindings("this === window", {x: 2}).return, true, + "page 1: Debugger.Object.prototype.executeInGlobal should outerize 'this'"); + + is(page2DO.executeInGlobal("this === window").return, true, + "page 2: Debugger.Object.prototype.executeInGlobal should outerize 'this'"); + is(page2DO.executeInGlobalWithBindings("this === window", {x: 2}).return, true, + "page 2: Debugger.Object.prototype.executeInGlobal should outerize 'this'"); + + // Debugger doesn't let one use outer windows as globals. You have to innerize. + const outerDO = page1DO.makeDebuggeeValue(page1DO.unsafeDereference()); + ok(outerDO !== page1DO, + "outer window gets its own D.O, distinct from page 1's global"); + ok(outerDO !== page2DO, + "outer window gets its own D.O, distinct from page 2's global"); + SimpleTest.doesThrow(() => outerDO.executeInGlobal("me"), + "outer window D.Os can't be used as globals"); + + SimpleTest.finish(); + }; + } +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_framerate_01.html b/devtools/server/tests/chrome/test_framerate_01.html new file mode 100644 index 0000000000..a306eb7ea5 --- /dev/null +++ b/devtools/server/tests/chrome/test_framerate_01.html @@ -0,0 +1,70 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1007200 - Create a framerate actor +--> +<head> + <meta charset="utf-8"> + <title>Framerate actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="framerate-helpers.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +window.onload = async function() { + const target = await getTargetForSelectedTab(); + const front = await target.getFront("framerate"); + const TICK = 1000; + + await waitFor(TICK); + await front.startRecording(); + await waitFor(TICK); + const rawData = await front.stopRecording(); + await onRecordingStopped(front, rawData); + await target.destroy(); + SimpleTest.finish(); +}; + +// Local Helpers +async function onRecordingStopped(front, rawData) { + ok(rawData, "There should be a recording available."); + + const timeline = plotFPS(rawData); + ok(timeline.length >= 2, + "There should be at least one measurement available, with two entries."); + + let prevTimeStart = timeline[0].delta; + + for (let i = 0; i < timeline.length; i += 2) { + const currTimeStart = timeline[i].delta; + const currTimeEnd = timeline[i + 1].delta; + info("Testing delta: " + currTimeStart + " vs. " + currTimeEnd); + + ok(currTimeStart < currTimeEnd, + "The start and end time deltas should be consecutive."); + is(currTimeStart, prevTimeStart, + "There should be two time deltas for each framerate value."); + + prevTimeStart = currTimeEnd; + } + + for (let i = 0; i < timeline.length; i += 2) { + const currFramerateStart = timeline[i].value; + const currFramerateEnd = timeline[i + 1].value; + info("Testing framerate: " + currFramerateStart); + + is(currFramerateStart, currFramerateEnd, + "The start and end framerate values should be equal."); + + is(typeof currFramerateStart, "number", "All values should be numbers."); + ok(currFramerateStart <= 60, "All values were correctly clamped."); + } +} +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_framerate_02.html b/devtools/server/tests/chrome/test_framerate_02.html new file mode 100644 index 0000000000..346ba0b22c --- /dev/null +++ b/devtools/server/tests/chrome/test_framerate_02.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1007200 - Create a framerate actor +--> +<head> + <meta charset="utf-8"> + <title>Framerate actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="framerate-helpers.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +window.onload = async function() { + const target = await getTargetForSelectedTab(); + const front = await target.getFront("framerate"); + + const rawData = await front.stopRecording(); + ok(rawData, "There should be a recording available."); + is(rawData.length, 0, "...but it should be empty."); + + const timeline = plotFPS(rawData); + is(timeline.length, 2, + "There should be one measurement plotted, with two entries."); + + info("The framerate should be assumed to be 0 if the recording is empty."); + + is(timeline[0].delta, 0, + "The first time delta should be 0."); + is(timeline[0].value, 0, + "The first framerate value should be 0."); + + is(timeline[1].delta, 100, + "The last time delta should be 100 (the default interval value)."); + is(timeline[1].value, 0, + "The last framerate value should be 0."); + + await target.destroy(); + SimpleTest.finish(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_framerate_03.html b/devtools/server/tests/chrome/test_framerate_03.html new file mode 100644 index 0000000000..b8eca6c428 --- /dev/null +++ b/devtools/server/tests/chrome/test_framerate_03.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1023018 - Tests whether or not the framerate actor can handle time ranges. +--> +<head> + <meta charset="utf-8"> + <title>Framerate actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="framerate-helpers.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +const START_TICK = 2000; +const STOP_TICK = 3000; +const TOTAL_TIME = 5000; + +window.onload = async function() { + const target = await getTargetForSelectedTab(); + const front = await target.getFront("framerate"); + + await front.startRecording(); + await waitFor(TOTAL_TIME); + const rawData = await front.stopRecording(START_TICK, STOP_TICK); + await onRecordingStopped(front, rawData); + await target.destroy(); + SimpleTest.finish(); +}; + +// Local Helper Functions +async function onRecordingStopped(front, rawData) { + ok(rawData, "There should be a recording available."); + + ok(!rawData.find(e => e < START_TICK), + "There should be no tick before 2000ms."); + ok(!rawData.find(e => e > STOP_TICK), + "There should be no tick after 3000ms."); + + for (const tick of rawData) { + info("Testing tick: " + tick); + is(typeof tick, "number", "All values should be numbers."); + } +} +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_framerate_04.html b/devtools/server/tests/chrome/test_framerate_04.html new file mode 100644 index 0000000000..6fbc0e6969 --- /dev/null +++ b/devtools/server/tests/chrome/test_framerate_04.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1023018 - Tests if the framerate actor keeps recording after navigations. +--> +<head> + <meta charset="utf-8"> + <title>Framerate actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="inspector-helpers.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +window.onload = async function() { + // inspector-helpers doesnt wait for explicit finish + SimpleTest.waitForExplicitFinish(); + + const TICK = 1000; + const url = document.getElementById("testContent").href; + const { target, doc } = await attachURL(url); + const contentWin = doc.defaultView; + const front = await target.getFront("framerate"); + + await front.startRecording(); + await waitFor(TICK); + const firstBatch = await front.getPendingTicks(); + await waitFor(TICK); + + const onWillNavigate = target.once("will-navigate"); + contentWin.location.reload(); + await onWillNavigate; + + await waitFor(TICK); + const secondBatch = await front.stopRecording(); + await onRecordingStopped(firstBatch, secondBatch); + target.destroy(); + SimpleTest.finish(); +}; + +// Local Helpers +function waitFor(time) { + return new Promise(resolve => setTimeout(resolve, time)); +} + +function onRecordingStopped(firstBatch, secondBatch) { + ok(firstBatch, "There should be a first batch recording available."); + ok(secondBatch, "There should be a second batch recording available."); + + const diff = secondBatch.length - firstBatch.length; + info("Difference in ticks: " + diff); + ok(diff > 0, "More ticks should be recorded in the second batch."); + + ok(firstBatch.every((e) => secondBatch.includes(e)), + "All the ticks in the first batch should be in the second batch as well."); + ok(secondBatch.every((e, i, array) => i < array.length - 1 ? e < array[i + 1] : true), + "All the ticks in the final batch should be ascending in value."); +} +</script> +</pre> +<a id="testContent" target="_blank" href="inspector_getImageData.html">Test Document</a> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_framerate_05.html b/devtools/server/tests/chrome/test_framerate_05.html new file mode 100644 index 0000000000..574b86719a --- /dev/null +++ b/devtools/server/tests/chrome/test_framerate_05.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1034648 - Tests whether a framerate recording can be cancelled. +--> +<head> + <meta charset="utf-8"> + <title>Framerate actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="framerate-helpers.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +window.onload = async function() { + const target = await getTargetForSelectedTab(); + const front = await target.getFront("framerate"); + const TICK = 1000; + + await front.startRecording(); + await waitFor(TICK); + + await front.cancelRecording(); + await waitFor(TICK); + + const rawTicks = await front.getPendingTicks(); + ok(rawTicks, + "The returned pending ticks should be empty (1)."); + is(rawTicks.length, 0, + "The returned pending ticks should be empty (2)."); + + const newRawData = await front.stopRecording(); + ok(newRawData, + "The returned raw data should be an empty array (1)."); + is(newRawData.length, 0, + "The returned raw data should be an empty array (2)."); + + await target.destroy(); + SimpleTest.finish(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_framerate_06.html b/devtools/server/tests/chrome/test_framerate_06.html new file mode 100644 index 0000000000..6c8040c381 --- /dev/null +++ b/devtools/server/tests/chrome/test_framerate_06.html @@ -0,0 +1,87 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1171489 - Tests if the framerate actor does not record timestamps from multiple frames. +--> +<head> + <meta charset="utf-8"> + <title>Framerate actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="inspector-helpers.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +window.onload = async function() { + // inspector-helpers doesnt wait for explicit finish + SimpleTest.waitForExplicitFinish(); + + const TICK = 1000; + const url = document.getElementById("testContent").href; + const { target, doc } = await attachURL(url); + const contentWin = doc.defaultView; + + const front = await target.getFront("framerate"); + + await front.startRecording(); + await waitFor(TICK); + const onWindowReady = waitForWindowReady(); + contentWin.location.reload(); + + // Wait for the iframe to be loaded again + await onWindowReady; + await waitFor(TICK); + const ticks = await front.stopRecording(); + await onRecordingStopped(ticks); + + await target.destroy(); + SimpleTest.finish(); +}; + +// Local Helpers +function waitFor(time) { + return new Promise(resolve => setTimeout(resolve, time)); +} + +function waitForWindowReady() { + return new Promise(resolve => { + window.addEventListener("message", function loaded(event) { + if (event.data === "ready") { + window.removeEventListener("message", loaded); + resolve(); + } + }); + }); +} + +function onRecordingStopped(ticks) { + const diffs = []; + + info(`Got ${ticks.length} ticks.`); + + for (let i = 1; i < ticks.length; i++) { + const prev = ticks[i - 1]; + const curr = ticks[i]; + diffs.push(curr - prev); + info(curr + " - " + (curr - prev)); + } + + // 1000 / 60 => 16.666... so we shouldn't get more than diffs of 16.66.. but + // when we get ticks from other frames they're usually at diffs of < 1. Sometimes + // ticks can still be less than 16ms even on one frame (usually following a very slow + // frame), so use a low number (2) to be our threshold + const THRESHOLD = 2; + ok(ticks.length >= 20, + "we should have atleast 20 ticks over the course of two seconds."); + const belowThreshold = diffs.filter(v => v <= THRESHOLD); + ok(belowThreshold.length <= 10, + "we should have very few frames less than the threshold"); +} +</script> +</pre> +<a id="testContent" target="_blank" href="inspector-traversal-data.html">Test Document</a> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_highlighter_paused_debugger.html b/devtools/server/tests/chrome/test_highlighter_paused_debugger.html new file mode 100644 index 0000000000..ad8239a9e1 --- /dev/null +++ b/devtools/server/tests/chrome/test_highlighter_paused_debugger.html @@ -0,0 +1,88 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test the PausedDebuggerOverlay highlighter. +--> +<head> + <meta charset="utf-8"> + <title>PausedDebuggerOverlay test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +window.onload = async function() { + SimpleTest.waitForExplicitFinish(); + + const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); + require("devtools/server/actors/inspector/inspector"); + const {HighlighterEnvironment} = require("devtools/server/actors/highlighters"); + const {PausedDebuggerOverlay} = require("devtools/server/actors/highlighters/paused-debugger"); + + const env = new HighlighterEnvironment(); + env.initFromWindow(window); + + const highlighter = new PausedDebuggerOverlay(env); + await highlighter.isReady; + const anonymousContent = highlighter.markup.content; + + const id = elementID => `${highlighter.ID_CLASS_PREFIX}${elementID}`; + + function isHidden(elementID) { + const attr = anonymousContent.getAttributeForElement(id(elementID), "hidden"); + return typeof attr === "string" && attr == "true"; + } + + function getReason() { + return anonymousContent.getTextContentForElement(id("reason")); + } + + function isOverlayShown() { + const attr = anonymousContent.getAttributeForElement(id("root"), "overlay"); + return typeof attr === "string" && attr == "true"; + } + + info("Test that the various elements with IDs exist"); + ok(highlighter.getElement("root"), "The root wrapper element exists"); + ok(highlighter.getElement("toolbar"), "The toolbar element exists"); + ok(highlighter.getElement("reason"), "The reason label element exists"); + + info("Test that the highlighter is hidden by default"); + ok(isHidden("root"), "The highlighter is hidden"); + + info("Show the highlighter with overlay and toolbar"); + let didShow = highlighter.show("breakpoint"); + ok(didShow, "Calling show returned true"); + ok(!isHidden("root"), "The highlighter is shown"); + ok(isOverlayShown(), "The overlay is shown"); + is( + getReason(), + "Paused on breakpoint", + "The reason displayed in the toolbar is correct" + ); + + info("Call show again with another reason"); + didShow = highlighter.show("debuggerStatement"); + ok(didShow, "Calling show returned true too"); + ok(!isHidden("root"), "The highlighter is still shown"); + is(getReason(), "Paused on debugger statement", + "The reason displayed in the toolbar is correct again"); + ok(isOverlayShown(), "The overlay is still shown too"); + + info("Call show again but with no reason"); + highlighter.show(); + ok(isOverlayShown(), "The overlay is shown however"); + + info("Hide the highlighter"); + highlighter.hide(); + ok(isHidden("root"), "The highlighter is now hidden"); + + SimpleTest.finish(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-changeattrs.html b/devtools/server/tests/chrome/test_inspector-changeattrs.html new file mode 100644 index 0000000000..94c4c3dc1b --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-changeattrs.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gInspectee = null; +let gWalker = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + gInspectee = doc; + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + runNextTest(); +}); + +addTest(function testChangeAttrs() { + const attrNode = gInspectee.querySelector("#a"); + let attrFront; + promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => { + attrFront = front; + dump("attrFront is: " + attrFront + "\n"); + // Add a few attributes. + const list = attrFront.startModifyingAttributes(); + list.setAttribute("data-newattr", "newvalue"); + list.setAttribute("data-newattr2", "newvalue"); + return list.apply(); + }).then(() => { + // We're only going to test that the change hit the document. + // There are other tests that make sure changes are propagated + // to the client. + is(attrNode.getAttribute("data-newattr"), "newvalue", + "Node should have the first new attribute"); + is(attrNode.getAttribute("data-newattr2"), "newvalue", + "Node should have the second new attribute."); + }).then(() => { + // Change an attribute. + const list = attrFront.startModifyingAttributes(); + list.setAttribute("data-newattr", "changedvalue"); + return list.apply(); + }).then(() => { + is(attrNode.getAttribute("data-newattr"), "changedvalue", + "Node should have the changed first value."); + is(attrNode.getAttribute("data-newattr2"), "newvalue", + "Second value should remain unchanged."); + }).then(() => { + const list = attrFront.startModifyingAttributes(); + list.removeAttribute("data-newattr2"); + return list.apply(); + }).then(() => { + is(attrNode.getAttribute("data-newattr"), "changedvalue", + "Node should have the changed first value."); + ok(!attrNode.hasAttribute("data-newattr2"), "Second value should be removed."); + }).then(runNextTest)); +}); + +addTest(function cleanup() { + gWalker = null; + gInspectee = null; + runNextTest(); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-changevalue.html b/devtools/server/tests/chrome/test_inspector-changevalue.html new file mode 100644 index 0000000000..f5aee52881 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-changevalue.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gInspectee = null; +let gWalker = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + gInspectee = doc; + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + runNextTest(); +}); + +addTest(function testChangeValue() { + const contentNode = gInspectee.querySelector("#a").firstChild; + let nodeFront; + promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => { + // Get the text child + return gWalker.children(front, { maxNodes: 1 }); + }).then(children => { + nodeFront = children.nodes[0]; + is(nodeFront.nodeType, Node.TEXT_NODE); + return nodeFront.setNodeValue("newvalue"); + }).then(() => { + // We're only going to test that the change hit the document. + // There are other tests that make sure changes are propagated + // to the client. + is(contentNode.nodeValue, "newvalue", "Node should have a new value."); + }).then(runNextTest)); +}); + +addTest(function cleanup() { + gWalker = null; + gInspectee = null; + runNextTest(); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-dead-nodes.html b/devtools/server/tests/chrome/test_inspector-dead-nodes.html new file mode 100644 index 0000000000..53c8b7eb3d --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-dead-nodes.html @@ -0,0 +1,332 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1121528 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1121528</title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gWalker = null; +let gDoc = null; +let gResourceWatcher = null; +let gRootNodeResolve = null; + +async function reloadTarget() { + const rootNodePromise = new Promise(r => (gRootNodeResolve = r)); + gDoc.defaultView.location.reload(); + await rootNodePromise; +} + +addAsyncTest(async function() { + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + gDoc = doc; + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + gResourceWatcher = createResourceWatcher(target); + + info("Start watching for root nodes and wait for the initial root node"); + const rootNodePromise = new Promise(r => (gRootNodeResolve = r)); + const onAvailable = rootNodeFront => gRootNodeResolve(rootNodeFront); + await gResourceWatcher.watchResources([gResourceWatcher.TYPES.ROOT_NODE], { + onAvailable, + }); + await rootNodePromise; + + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.children(nodeFront) before the load completes shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "body"); + + await reloadTarget(); + await gWalker.children(nodeFront); + + ok(true, "The call to walker.children() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.nextSibling(nodeFront) before the load completes shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.nextSibling(nodeFront); + + ok(true, "The call to walker.nextSibling() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.previousSibling(nodeFront) before the load completes shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.previousSibling(nodeFront); + + ok(true, "The call to walker.previousSibling() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.addPseudoClassLock(nodeFront) before the load completes " + + "shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.addPseudoClassLock(nodeFront, ":hover"); + + ok(true, "The call to walker.addPseudoClassLock() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.removePseudoClassLock(nodeFront) before the load completes " + + "shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.removePseudoClassLock(nodeFront, ":hover"); + + ok(true, "The call to walker.removePseudoClassLock() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.clearPseudoClassLocks(nodeFront) before the load completes " + + "shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.clearPseudoClassLocks(nodeFront); + + ok(true, "The call to walker.clearPseudoClassLocks() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.innerHTML(nodeFront) before the load completes shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.innerHTML(nodeFront); + + ok(true, "The call to walker.innerHTML() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.setInnerHTML(nodeFront) before the load completes shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.setInnerHTML(nodeFront, "<span>innerHTML changed</span>"); + + ok(true, "The call to walker.setInnerHTML() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.outerHTML(nodeFront) before the load completes shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.outerHTML(nodeFront); + + ok(true, "The call to walker.outerHTML() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.setOuterHTML(nodeFront) before the load completes shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.setOuterHTML(nodeFront, "<h1><span>innerHTML changed</span></h1>"); + + ok(true, "The call to walker.setOuterHTML() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.insertAdjacentHTML(nodeFront) before the load completes shouldn't " + + "fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.insertAdjacentHTML(nodeFront, "afterEnd", + "<span>new adjacent HTML</span>"); + + ok(true, "The call to walker.insertAdjacentHTML() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.removeNode(nodeFront) before the load completes should throw"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + let hasThrown = false; + try { + await gWalker.removeNode(nodeFront); + } catch (e) { + hasThrown = true; + } + + ok(hasThrown, "The call to walker.removeNode() threw"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.removeNodes([nodeFront]) before the load completes should throw"); + + const nodeFront1 = await gWalker.querySelector(gWalker.rootNode, "h1"); + const nodeFront2 = await gWalker.querySelector(gWalker.rootNode, "#longstring"); + const nodeFront3 = await gWalker.querySelector(gWalker.rootNode, "#shortstring"); + await reloadTarget(); + let hasThrown = false; + try { + await gWalker.removeNodes([nodeFront1, nodeFront2, nodeFront3]); + } catch (e) { + hasThrown = true; + } + + ok(hasThrown, "The call to walker.removeNodes() threw"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.insertBefore(nodeFront, parent, null) before the load completes " + + "shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + const newParentFront = await gWalker.querySelector(gWalker.rootNode, "#longlist"); + await reloadTarget(); + await gWalker.insertBefore(nodeFront, newParentFront); + + ok(true, "The call to walker.insertBefore() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.insertBefore(nodeFront, parent, sibling) before the load completes " + + "shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + const newParentFront = await gWalker.querySelector(gWalker.rootNode, "#longlist"); + const siblingFront = await gWalker.querySelector(gWalker.rootNode, "#b"); + await reloadTarget(); + await gWalker.insertBefore(nodeFront, newParentFront, siblingFront); + + ok(true, "The call to walker.insertBefore() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.editTagName(nodeFront) before the load completes shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.editTagName(nodeFront, "h2"); + + ok(true, "The call to walker.editTagName() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.hideNode(nodeFront) before the load completes shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.hideNode(nodeFront); + + ok(true, "The call to walker.hideNode() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.unhideNode(nodeFront) before the load completes shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.unhideNode(nodeFront); + + ok(true, "The call to walker.unhideNode() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.releaseNode(nodeFront) before the load completes shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1"); + await reloadTarget(); + await gWalker.releaseNode(nodeFront); + + ok(true, "The call to walker.releaseNode() didn't fail"); + runNextTest(); +}); + +addAsyncTest(async function() { + info("Getting a nodeFront, reloading the page, and calling " + + "walker.querySelector(nodeFront) before the load completes shouldn't fail"); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "body"); + await reloadTarget(); + await gWalker.querySelector(nodeFront, "h1"); + + ok(true, "The call to walker.querySelector() didn't fail"); + runNextTest(); +}); + +addTest(function cleanup() { + gWalker = null; + gDoc = null; + gResourceWatcher = null; + runNextTest(); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=932937">Mozilla Bug 1121528</a> +<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-display-type.html b/devtools/server/tests/chrome/test_inspector-display-type.html new file mode 100644 index 0000000000..a8bbedc22a --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-display-type.html @@ -0,0 +1,81 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1431900 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1431900</title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +var gWalker; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + runNextTest(); +}); + +addAsyncTest(async function testInlineBlockDisplayType() { + info("Test getting the display type of an inline block element."); + const node = await gWalker.querySelector(gWalker.rootNode, "#inline-block"); + const displayType = node.displayType; + is(displayType, "inline-block", "The node has a display type of 'inline-block'."); + runNextTest(); +}); + +addAsyncTest(async function testInlineTextChildDisplayType() { + info("Test getting the display type of an inline text child."); + const node = await gWalker.querySelector(gWalker.rootNode, "#inline-block"); + const children = await gWalker.children(node); + const inlineTextChild = children.nodes[0]; + const displayType = inlineTextChild.displayType; + ok(!displayType, "No display type for inline text child."); + runNextTest(); +}); + +addAsyncTest(async function testGridDisplayType() { + info("Test getting the display type of an grid container."); + const node = await gWalker.querySelector(gWalker.rootNode, "#grid"); + const displayType = node.displayType; + is(displayType, "grid", "The node has a display type of 'grid'."); + runNextTest(); +}); + +addAsyncTest(async function testBlockDisplayType() { + info("Test getting the display type of a block element."); + const node = await gWalker.querySelector(gWalker.rootNode, "#block"); + const displayType = await node.displayType; + is(displayType, "block", "The node has a display type of 'block'."); + runNextTest(); +}); + +addTest(function() { + gWalker = null; + runNextTest(); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1431900">Mozilla Bug 1431900</a> +<a id="inspectorContent" target="_blank" href="inspector_display-type.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-duplicate-node.html b/devtools/server/tests/chrome/test_inspector-duplicate-node.html new file mode 100644 index 0000000000..205e11629e --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-duplicate-node.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1208864 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1208864</title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gWalker = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + runNextTest(); +}); + +addTest(async function testDuplicateNode() { + const className = ".node-to-duplicate"; + let matches = await gWalker.querySelectorAll(gWalker.rootNode, className); + is(matches.length, 1, "There should initially be one node to duplicate."); + + const nodeFront = await gWalker.querySelector(gWalker.rootNode, className); + await gWalker.duplicateNode(nodeFront); + + matches = await gWalker.querySelectorAll(gWalker.rootNode, className); + is(matches.length, 2, "The node should now be duplicated."); + + runNextTest(); +}); + +addTest(function cleanup() { + gWalker = null; + runNextTest(); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1208864">Mozilla Bug 1208864</a> +<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-hide.html b/devtools/server/tests/chrome/test_inspector-hide.html new file mode 100644 index 0000000000..e699400ee0 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-hide.html @@ -0,0 +1,71 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gWalker = null; +let gInspectee = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gInspectee = doc; + gWalker = inspector.walker; + runNextTest(); +}); + +addTest(function testRearrange() { + let listFront = null; + const listNode = gInspectee.querySelector("#longlist"); + + promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(front => { + listFront = front; + }).then(() => { + const computed = gInspectee.defaultView.getComputedStyle(listNode); + is(computed.visibility, "visible", "Node should be visible to start with"); + return gWalker.hideNode(listFront); + }).then(response => { + const computed = gInspectee.defaultView.getComputedStyle(listNode); + is(computed.visibility, "hidden", "Node should be hidden"); + return gWalker.unhideNode(listFront); + }).then(() => { + const computed = gInspectee.defaultView.getComputedStyle(listNode); + is(computed.visibility, "visible", "Node should be visible again."); + }).then(runNextTest)); +}); + +addTest(function cleanup() { + gWalker = null; + gInspectee = null; + runNextTest(); +}); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html b/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html new file mode 100644 index 0000000000..168a752e9c --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html @@ -0,0 +1,104 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <title>Test for InactivePropertyHelper</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript"> +"use strict"; +SimpleTest.waitForExplicitFinish(); + +(async function() { + const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); + const Services = require("Services"); + const { inactivePropertyHelper } = require("devtools/server/actors/utils/inactive-property-helper"); + let { isPropertyUsed } = inactivePropertyHelper; + isPropertyUsed = isPropertyUsed.bind(inactivePropertyHelper); + + const INACTIVE_CSS_PREF = "devtools.inspector.inactive.css.enabled"; + Services.prefs.setBoolPref(INACTIVE_CSS_PREF, true); + SimpleTest.registerCleanupFunction(() => { + Services.prefs.clearUserPref(INACTIVE_CSS_PREF); + }); + + const FOLDER = "./inactive-property-helper"; + + // Each file should `export default` an array of objects, representing each test case. + // A single test case is an object of the following shape: + // - {String} info: a summary of the test case + // - {String} property: the CSS property that should be tested + // - {String|undefined} tagName: the tagName of the element we're going to test. + // Optional only if there's a createTestElement property. + // - {Function|undefined} createTestElement: A function that takes a node as a parameter + // where elements used for the test case will + // be appended. The function should return the + // element that will be passed to + // isPropertyUsed. + // Optional only if there's a tagName property + // - {Array<String>} rules: An array of the rules that will be applied on the element. + // This can't be empty as isPropertyUsed need a rule. + // - {Integer|undefined} ruleIndex: If there are multiples rules in `rules`, the index + // of the one that should be tested in isPropertyUsed. + // - {Boolean} isActive: should the property be active (isPropertyUsed `used` result). + const testFiles = [ + "align-content.js", + "flex-grid-item-properties.js", + "float.js", + "gap.js", + "grid-with-absolute-properties.js", + "margin-padding.js", + "max-min-width-height.js", + "place-items-content.js", + "positioned.js", + "vertical-align.js", + "text-overflow.js", + "outline-radius.js", + ].map(file => `${FOLDER}/${file}`); + + // Import all the test cases + const tests = + // eslint-disable-next-line no-unsanitized/method + (await Promise.all(testFiles.map(f => import(f).then(data => data.default)))).flat(); + + for (const { + info: summary, + property, + tagName, + createTestElement, + rules, + ruleIndex, + isActive + } of tests) { + // Create an element which will contain the test elements. + const main = document.createElement("main"); + document.firstElementChild.appendChild(main); + + // Apply the CSS rules to the document. + const style = document.createElement("style"); + main.append(style); + for (const dataRule of rules) { + style.sheet.insertRule(dataRule); + } + const rule = style.sheet.cssRules[ruleIndex || 0]; + + // Create the test elements + let el; + if (createTestElement) { + el = createTestElement(main); + } else { + el = document.createElement(tagName); + main.append(el); + } + + const { used } = isPropertyUsed(el, getComputedStyle(el), rule, property); + ok(used === isActive, summary); + + main.remove(); + } + SimpleTest.finish(); +})(); + </script> + </head> + <body></body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-mutations-attr.html b/devtools/server/tests/chrome/test_inspector-mutations-attr.html new file mode 100644 index 0000000000..133d13a116 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-mutations-attr.html @@ -0,0 +1,168 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gInspectee = null; +let gWalker = null; +let attrNode; +let attrFront; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gInspectee = doc; + gWalker = inspector.walker; + runNextTest(); +}); + +addTest(setupAttrTest); +addTest(testAddAttribute); +addTest(testChangeAttribute); +addTest(testRemoveAttribute); +addTest(testQueuedMutations); +addTest(setupFrameAttrTest); +addTest(testAddAttribute); +addTest(testChangeAttribute); +addTest(testRemoveAttribute); +addTest(testQueuedMutations); + +function setupAttrTest() { + attrNode = gInspectee.querySelector("#a"); + promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(node => { + attrFront = node; + }).then(runNextTest)); +} + +function setupFrameAttrTest() { + const frame = gInspectee.querySelector("#childFrame"); + attrNode = frame.contentDocument.querySelector("#a"); + + promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => { + return gWalker.children(childFrame); + }).then(children => { + const nodes = children.nodes; + is(nodes.length, 1, "There should be only one child of the iframe"); + is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node"); + return gWalker.querySelector(nodes[0], "#a"); + }).then(node => { + attrFront = node; + }).then(runNextTest)); +} + +function testAddAttribute() { + attrNode.setAttribute("data-newattr", "newvalue"); + attrNode.setAttribute("data-newattr2", "newvalue"); + gWalker.once("mutations", () => { + is(attrFront.attributes.length, 3, "Should have id and two new attributes."); + is(attrFront.getAttribute("data-newattr"), "newvalue", + "Node front should have the first new attribute"); + is(attrFront.getAttribute("data-newattr2"), "newvalue", + "Node front should have the second new attribute."); + runNextTest(); + }); +} + +function testChangeAttribute() { + attrNode.setAttribute("data-newattr", "changedvalue1"); + attrNode.setAttribute("data-newattr", "changedvalue2"); + attrNode.setAttribute("data-newattr", "changedvalue3"); + gWalker.once("mutations", mutations => { + is(mutations.length, 1, + "Only one mutation is sent for multiple queued attribute changes"); + is(attrFront.attributes.length, 3, "Should have id and two new attributes."); + is(attrFront.getAttribute("data-newattr"), "changedvalue3", + "Node front should have the changed first value"); + is(attrFront.getAttribute("data-newattr2"), "newvalue", + "Second value should remain unchanged."); + runNextTest(); + }); +} + +function testRemoveAttribute() { + attrNode.removeAttribute("data-newattr2"); + gWalker.once("mutations", () => { + is(attrFront.attributes.length, 2, "Should have id and one remaining attribute."); + is(attrFront.getAttribute("data-newattr"), "changedvalue3", + "Node front should still have the first value"); + ok(!attrFront.hasAttribute("data-newattr2"), "Second value should be removed."); + runNextTest(); + }); +} + +function testQueuedMutations() { + // All modifications to each attribute should be queued in one mutation event. + + attrNode.removeAttribute("data-newattr"); + attrNode.setAttribute("data-newattr", "1"); + attrNode.removeAttribute("data-newattr"); + attrNode.setAttribute("data-newattr", "2"); + attrNode.removeAttribute("data-newattr"); + + for (let i = 0; i <= 1000; i++) { + attrNode.setAttribute("data-newattr2", i); + } + + attrNode.removeAttribute("data-newattr3"); + attrNode.setAttribute("data-newattr3", "1"); + attrNode.removeAttribute("data-newattr3"); + attrNode.setAttribute("data-newattr3", "2"); + attrNode.removeAttribute("data-newattr3"); + attrNode.setAttribute("data-newattr3", "3"); + + // This shouldn't be added in the attribute set, since it's a new + // attribute that's been added and removed. + attrNode.setAttribute("data-newattr4", "4"); + attrNode.removeAttribute("data-newattr4"); + + gWalker.once("mutations", mutations => { + is(mutations.length, 4, + "Only one mutation each is sent for multiple queued attribute changes"); + is(attrFront.attributes.length, 3, + "Should have id, data-newattr2, and data-newattr3."); + + is(attrFront.getAttribute("data-newattr2"), "1000", + "Node front should still have the correct value"); + is(attrFront.getAttribute("data-newattr3"), "3", + "Node front should still have the correct value"); + ok(!attrFront.hasAttribute("data-newattr"), "Attribute value should be removed."); + ok(!attrFront.hasAttribute("data-newattr4"), "Attribute value should be removed."); + + runNextTest(); + }); +} + +addTest(function cleanup() { + gInspectee = null; + gWalker = null; + runNextTest(); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-mutations-events.html b/devtools/server/tests/chrome/test_inspector-mutations-events.html new file mode 100644 index 0000000000..b48952c4d9 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-mutations-events.html @@ -0,0 +1,187 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1157469 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1157469</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + const prevPrefValue = Services.prefs.getBoolPref("devtools.chrome.enabled"); + Services.prefs.setBoolPref("devtools.chrome.enabled", true); + + let inspectee = null; + let inspector = null; + let walker = null; + const eventListener1 = function() {}; + const eventListener2 = function() {}; + let eventNode1; + let eventNode2; + let eventFront1; + let eventFront2; + + addAsyncTest(async function setup() { + info("Setting up inspector and walker actors."); + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + inspectee = doc; + inspector = await target.getFront("inspector"); + walker = inspector.walker; + + runNextTest(); + }); + + addAsyncTest(async function setupEventTest() { + eventNode1 = inspectee.querySelector("#a"); + eventNode2 = inspectee.querySelector("#b"); + + eventFront1 = await walker.querySelector(walker.rootNode, "#a"); + eventFront2 = await walker.querySelector(walker.rootNode, "#b"); + + runNextTest(); + }); + + addAsyncTest(async function testChangeEventListenerOnSingleNode() { + checkNodesHaveNoEventListener(); + + info("add event listener on a single node"); + eventNode1.addEventListener("click", eventListener1); + + let mutations = await waitForMutations(); + is(mutations.length, 1, "one mutation expected"); + is(mutations[0].target, eventFront1, "mutation targets eventFront1"); + is(mutations[0].type, "events", "mutation type is events"); + is(mutations[0].hasEventListeners, true, + "mutation target should have event listeners"); + is(eventFront1.hasEventListeners, true, "eventFront1 should have event listeners"); + + info("remove event listener on a single node"); + eventNode1.removeEventListener("click", eventListener1); + + mutations = await waitForMutations(); + is(mutations.length, 1, "one mutation expected"); + is(mutations[0].target, eventFront1, "mutation targets eventFront1"); + is(mutations[0].type, "events", "mutation type is events"); + is(mutations[0].hasEventListeners, false, + "mutation target should have no event listeners"); + is(eventFront1.hasEventListeners, false, + "eventFront1 should have no event listeners"); + + info("perform several event listener changes on a single node"); + eventNode1.addEventListener("click", eventListener1); + eventNode1.addEventListener("click", eventListener2); + eventNode1.removeEventListener("click", eventListener1); + eventNode1.removeEventListener("click", eventListener2); + + mutations = await waitForMutations(); + is(mutations.length, 1, "one mutation expected"); + is(mutations[0].target, eventFront1, "mutation targets eventFront1"); + is(mutations[0].type, "events", "mutation type is events"); + is(mutations[0].hasEventListeners, false, + "no event listener expected on mutation target"); + is(eventFront1.hasEventListeners, false, "no event listener expected on node"); + + runNextTest(); + }); + + addAsyncTest(async function testChangeEventsOnSeveralNodes() { + checkNodesHaveNoEventListener(); + + info("add event listeners on both nodes"); + eventNode1.addEventListener("click", eventListener1); + eventNode2.addEventListener("click", eventListener2); + + let mutations = await waitForMutations(); + is(mutations.length, 2, "two mutations expected, one for each modified node"); + // first mutation + is(mutations[0].target, eventFront1, "first mutation targets eventFront1"); + is(mutations[0].type, "events", "mutation type is events"); + is(mutations[0].hasEventListeners, true, + "mutation target should have event listeners"); + is(eventFront1.hasEventListeners, true, "eventFront1 should have event listeners"); + // second mutation + is(mutations[1].target, eventFront2, "second mutation targets eventFront2"); + is(mutations[1].type, "events", "mutation type is events"); + is(mutations[1].hasEventListeners, true, + "mutation target should have event listeners"); + is(eventFront2.hasEventListeners, true, "eventFront1 should have event listeners"); + + info("remove event listeners on both nodes"); + eventNode1.removeEventListener("click", eventListener1); + eventNode2.removeEventListener("click", eventListener2); + + mutations = await waitForMutations(); + is(mutations.length, 2, "one mutation registered for event listener change"); + // first mutation + is(mutations[0].target, eventFront1, "first mutation targets eventFront1"); + is(mutations[0].type, "events", "mutation type is events"); + is(mutations[0].hasEventListeners, false, + "mutation target should have no event listeners"); + is(eventFront1.hasEventListeners, false, + "eventFront2 should have no event listeners"); + // second mutation + is(mutations[1].target, eventFront2, "second mutation targets eventFront2"); + is(mutations[1].type, "events", "mutation type is events"); + is(mutations[1].hasEventListeners, false, + "mutation target should have no event listeners"); + is(eventFront2.hasEventListeners, false, + "eventFront2 should have no event listeners"); + + runNextTest(); + }); + + addAsyncTest(async function testRemoveMissingEvent() { + checkNodesHaveNoEventListener(); + + info("try to remove an event listener not previously added"); + eventNode1.removeEventListener("click", eventListener1); + + info("set any attribute on the node to trigger a mutation"); + eventNode1.setAttribute("data-attr", "somevalue"); + + const mutations = await waitForMutations(); + is(mutations.length, 1, "expect only one mutation"); + isnot(mutations.type, "events", "mutation type should not be events"); + + Services.prefs.setBoolPref("devtools.chrome.enabled", prevPrefValue); + runNextTest(); + }); + + function checkNodesHaveNoEventListener() { + is(eventFront1.hasEventListeners, false, + "eventFront1 hasEventListeners should be false"); + is(eventFront2.hasEventListeners, false, + "eventFront2 hasEventListeners should be false"); + } + + function waitForMutations() { + return new Promise(resolve => { + walker.once("mutations", mutations => { + resolve(mutations); + }); + }); + } + + runNextTest(); +}; + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1157469">Mozilla Bug 1157469</a> +<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-mutations-value.html b/devtools/server/tests/chrome/test_inspector-mutations-value.html new file mode 100644 index 0000000000..1f0b1dfc23 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-mutations-value.html @@ -0,0 +1,162 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +const WalkerActor = require("devtools/server/actors/inspector/walker"); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +const testSummaryLength = 10; +WalkerActor.setValueSummaryLength(testSummaryLength); +SimpleTest.registerCleanupFunction(function() { + WalkerActor.setValueSummaryLength(WalkerActor.DEFAULT_VALUE_SUMMARY_LENGTH); +}); + +let gInspectee = null; +let gWalker = null; +let valueNode; +var valueFront; +var longStringFront; +var longString = "stringstringstringstringstringstringstringstringstringstringstring"; +var shortString = "str"; +var shortString2 = "str2"; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gInspectee = doc; + gWalker = inspector.walker; + runNextTest(); +}); + +addTest(setupValueTest); +addTest(testKeepLongValue); +addTest(testSetShortValue); +addTest(testKeepShortValue); +addTest(testSetLongValue); +addTest(setupFrameValueTest); +addTest(testKeepLongValue); +addTest(testSetShortValue); +addTest(testKeepShortValue); +addTest(testSetLongValue); + +function setupValueTest() { + valueNode = gInspectee.querySelector("#longstring").firstChild; + promiseDone(gWalker.querySelector(gWalker.rootNode, "#longstring").then(node => { + longStringFront = node; + return gWalker.children(node); + }).then(children => { + valueFront = children.nodes[0]; + }).then(runNextTest)); +} + +function setupFrameValueTest() { + const frame = gInspectee.querySelector("#childFrame"); + valueNode = frame.contentDocument.querySelector("#longstring").firstChild; + + promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => { + return gWalker.children(childFrame); + }).then(children => { + const nodes = children.nodes; + is(nodes.length, 1, "There should be only one child of the iframe"); + is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node"); + return gWalker.querySelector(nodes[0], "#longstring"); + }).then(node => { + longStringFront = node; + return gWalker.children(node); + }).then(children => { + valueFront = children.nodes[0]; + }).then(runNextTest)); +} + +function checkNodeFrontValue(front, expectedValue) { + return front.getNodeValue().then(longstring => { + return longstring.string(); + }).then(str => { + is(str, expectedValue, "Node value is as expected"); + }); +} + +function testKeepLongValue() { + // After first setup we should have a long string in the node + ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined."); + + valueNode.nodeValue = longString; + gWalker.once("mutations", (changes) => { + ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined."); + ok(!changes.some(change => change.type === "inlineTextChild"), + "No inline text child mutation was fired."); + checkNodeFrontValue(valueFront, longString).then(runNextTest); + }); +} + +function testSetShortValue() { + ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined."); + + valueNode.nodeValue = shortString; + gWalker.once("mutations", (changes) => { + ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined."); + ok(changes.some(change => change.type === "inlineTextChild"), + "An inlineTextChild mutation was fired."); + checkNodeFrontValue(valueFront, shortString).then(runNextTest); + }); +} + +function testKeepShortValue() { + ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined."); + + valueNode.nodeValue = shortString2; + gWalker.once("mutations", (changes) => { + ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined."); + ok(!changes.some(change => change.type === "inlineTextChild"), + "No inline text child mutation was fired."); + checkNodeFrontValue(valueFront, shortString2).then(runNextTest); + }); +} + +function testSetLongValue() { + ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined."); + + valueNode.nodeValue = longString; + gWalker.once("mutations", (changes) => { + ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined."); + ok(changes.some(change => change.type === "inlineTextChild"), + "An inlineTextChild mutation was fired."); + checkNodeFrontValue(valueFront, longString).then(runNextTest); + }); +} + +addTest(function cleanup() { + gInspectee = null; + gWalker = null; + runNextTest(); +}); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-pick-color.html b/devtools/server/tests/chrome/test_inspector-pick-color.html new file mode 100644 index 0000000000..df6d9d8228 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-pick-color.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that the inspector actor has the pickColorFromPage and cancelPickColorFromPage +methods and that when a color is picked the color-picked event is emitted and that when +the eyedropper is dimissed, the color-pick-canceled event is emitted. +https://bugzilla.mozilla.org/show_bug.cgi?id=1262439 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1262439</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + let win = null; + let inspector = null; + + addAsyncTest(async function() { + info("Setting up inspector actor"); + + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + inspector = await target.getFront("inspector"); + win = doc.defaultView; + runNextTest(); + }); + + addAsyncTest(async function() { + info("Start picking a color from the page"); + await inspector.pickColorFromPage(); + + info("Click in the page and make sure a color-picked event is received"); + const onColorPicked = waitForEvent("color-picked"); + win.document.body.click(); + const color = await onColorPicked; + + is(color, "#000000", "The color-picked event was received with the right color"); + + runNextTest(); + }); + + addAsyncTest(async function() { + info("Start picking a color from the page"); + await inspector.pickColorFromPage(); + + info("Use the escape key to dismiss the eyedropper"); + const onPickCanceled = waitForEvent("color-pick-canceled"); + + const keyboardEvent = win.document.createEvent("KeyboardEvent"); + keyboardEvent.initKeyEvent("keydown", true, true, win, false, false, + false, false, 27, 0); + win.document.dispatchEvent(keyboardEvent); + + await onPickCanceled; + ok(true, "The color-pick-canceled event was received"); + + runNextTest(); + }); + + addAsyncTest(async function() { + info("Start picking a color from the page"); + await inspector.pickColorFromPage(); + + info("And cancel the color picking"); + await inspector.cancelPickColorFromPage(); + + runNextTest(); + }); + + function waitForEvent(name) { + return new Promise(resolve => inspector.once(name, resolve)); + } + + runNextTest(); +}; + </script> +</head> +<body> +<a id="inspectorContent" target="_blank" href="inspector-eyedropper.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html b/devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html new file mode 100644 index 0000000000..1be13cd4e5 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html @@ -0,0 +1,187 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +const { PSEUDO_CLASSES } = require("devtools/shared/css/constants"); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +const InspectorUtils = require("InspectorUtils"); + +let gInspectee = null; +let gWalker = null; + +async function setup(callback) { + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + gInspectee = doc; + const inspector = await target.getFront("inspector"); + ok(inspector.walker, "getWalker() should return an actor."); + gWalker = inspector.walker; + runNextTest(); +} + +function teardown() { + gWalker = null; + gInspectee = null; +} + +function checkChange(change, expectation) { + is(change.type, "pseudoClassLock", "Expect a pseudoclass lock change."); + const target = change.target; + if (expectation.id) { + is(target.id, expectation.id, "Expect a change on node id " + expectation.id); + } + if (expectation.nodeName) { + is(target.nodeName, expectation.nodeName, + "Expect a change on node name " + expectation.nodeName); + } + + is(target.pseudoClassLocks.length, expectation.pseudos.length, + "Expect " + expectation.pseudos.length + " pseudoclass locks."); + for (let i = 0; i < expectation.pseudos.length; i++) { + const pseudo = expectation.pseudos[i]; + const enabled = expectation.enabled === undefined ? true : expectation.enabled[i]; + ok(target.hasPseudoClassLock(pseudo), "Expect lock: " + pseudo); + const rawNode = target.rawNode(); + ok(InspectorUtils.hasPseudoClassLock(rawNode, pseudo), + "Expect lock in dom: " + pseudo); + + is(rawNode.matches(pseudo), enabled, + `Target should match pseudoclass, '${pseudo}', if enabled (with .matches())`); + } + + for (const pseudo of PSEUDO_CLASSES) { + if (!expectation.pseudos.some(expected => pseudo === expected)) { + ok(!target.hasPseudoClassLock(pseudo), "Don't expect lock: " + pseudo); + ok(!InspectorUtils.hasPseudoClassLock(target.rawNode(), pseudo), + "Don't expect lock in dom: " + pseudo); + } + } +} + +function checkMutations(mutations, expectations) { + is(mutations.length, expectations.length, "Should get the right number of mutations."); + for (let i = 0; i < mutations.length; i++) { + checkChange(mutations[i], expectations[i]); + } +} + +addTest(function testPseudoClassLock() { + let contentNode; + let nodeFront; + setup(() => { + contentNode = gInspectee.querySelector("#b"); + return promiseDone(gWalker.querySelector(gWalker.rootNode, "#b").then(front => { + nodeFront = front; + // Lock the pseudoclass alone, no parents. + gWalker.addPseudoClassLock(nodeFront, ":active"); + // Expect a single pseudoClassLock mutation. + return promiseOnce(gWalker, "mutations"); + }).then(mutations => { + is(mutations.length, 1, "Should get one mutation"); + is(mutations[0].target, nodeFront, "Should be the node we tried to apply to"); + checkChange(mutations[0], { + id: "b", + nodeName: "DIV", + pseudos: [":active"], + }); + }).then(() => { + // Now add :hover, this time with parents. + gWalker.addPseudoClassLock(nodeFront, ":hover", {parents: true}); + return promiseOnce(gWalker, "mutations"); + }).then(mutations => { + const expectedMutations = [{ + id: "b", + nodeName: "DIV", + pseudos: [":hover", ":active"], + }, + { + id: "longlist", + nodeName: "DIV", + pseudos: [":hover"], + }, + { + nodeName: "BODY", + pseudos: [":hover"], + }, + { + nodeName: "HTML", + pseudos: [":hover"], + }]; + checkMutations(mutations, expectedMutations); + }).then(() => { + // Now remove the :hover on all parents + gWalker.removePseudoClassLock(nodeFront, ":hover", {parents: true}); + return promiseOnce(gWalker, "mutations"); + }).then(mutations => { + const expectedMutations = [{ + id: "b", + nodeName: "DIV", + // Should still have :active on the original node. + pseudos: [":active"], + }, + { + id: "longlist", + nodeName: "DIV", + pseudos: [], + }, + { + nodeName: "BODY", + pseudos: [], + }, + { + nodeName: "HTML", + pseudos: [], + }]; + checkMutations(mutations, expectedMutations); + }).then(() => { + gWalker.addPseudoClassLock(nodeFront, ":hover", {enabled: false}); + return promiseOnce(gWalker, "mutations"); + }).then(mutations => { + is(mutations.length, 1, "Should get one mutation"); + is(mutations[0].target, nodeFront, "Should be the node we tried to apply to"); + checkChange(mutations[0], { + id: "b", + nodeName: "DIV", + pseudos: [":hover", ":active"], + enabled: [false, true], + }); + }).then(() => { + // Now shut down the walker and make sure that clears up the remaining lock. + return gWalker.release(); + }).then(() => { + ok(!InspectorUtils.hasPseudoClassLock(contentNode, ":active"), + "Pseudoclass should have been removed during destruction."); + teardown(); + }).then(runNextTest)); + }); +}); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-reload.html b/devtools/server/tests/chrome/test_inspector-reload.html new file mode 100644 index 0000000000..b7f490bd94 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-reload.html @@ -0,0 +1,86 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gInspectee = null; +let gWalker = null; +let gResourceWatcher = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gInspectee = doc; + const walker = inspector.walker; + gWalker = await inspector.getWalker(); + gResourceWatcher = createResourceWatcher(target); + + ok(walker === gWalker, "getWalker() twice should return the same walker."); + runNextTest(); +}); + +addTest(async function testReload() { + const oldRootID = gWalker.rootNode.actorID; + + info("Start watching for root nodes and wait for the initial root node"); + let rootNodeResolve; + let rootNodePromise = new Promise(r => (rootNodeResolve = r)); + const onAvailable = rootNodeFront => rootNodeResolve(rootNodeFront); + await gResourceWatcher.watchResources([gResourceWatcher.TYPES.ROOT_NODE], { + onAvailable, + }); + await rootNodePromise; + + info("Retrieve the node front for the selector `#a`"); + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "#a"); + ok(nodeFront.actorID, "Node front has a valid actor ID"); + + info("Reload the page and wait for the newRoot mutation"); + rootNodePromise = new Promise(r => (rootNodeResolve = r)); + + gInspectee.defaultView.location.reload(); + await rootNodePromise; + + info("Retrieve the (new) node front for the selector `#a`"); + const newNodeFront = await gWalker.querySelector(gWalker.rootNode, "#a"); + ok(newNodeFront.actorID, "Got a new actor ID"); + ok(gWalker.rootNode.actorID != oldRootID, "Root node should have changed."); + + runNextTest(); +}); + +addTest(function cleanup() { + gWalker = null; + gInspectee = null; + gResourceWatcher = null; + runNextTest(); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-resize.html b/devtools/server/tests/chrome/test_inspector-resize.html new file mode 100644 index 0000000000..e0cf9abade --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-resize.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that the inspector actor emits "resize" events when the page is resized. +https://bugzilla.mozilla.org/show_bug.cgi?id=1222409 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1222409</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + let win = null; + let inspector = null; + + addAsyncTest(async function setup() { + info("Setting up inspector and walker actors."); + + const url = document.getElementById("inspectorContent").href; + + const { target, doc } = await attachURL(url); + inspector = await target.getFront("inspector"); + win = doc.defaultView; + runNextTest(); + }); + + addAsyncTest(async function() { + const walker = inspector.walker; + + // We can't receive events from the walker if we haven't first executed a + // method on the actor to initialize it. + await walker.querySelector(walker.rootNode, "img"); + + const {outerWidth, outerHeight} = win; + // eslint-disable-next-line new-cap + const onResize = new Promise(resolve => { + walker.once("resize", () => { + resolve(); + }); + }); + win.resizeTo(800, 600); + await onResize; + + ok(true, "The resize event was emitted"); + win.resizeTo(outerWidth, outerHeight); + + runNextTest(); + }); + + runNextTest(); +}; + </script> +</head> +<body> +<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-resolve-url.html b/devtools/server/tests/chrome/test_inspector-resolve-url.html new file mode 100644 index 0000000000..ddf68f56ed --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-resolve-url.html @@ -0,0 +1,87 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=921102 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 921102</title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gInspector; +let gDoc; + +addTest(async function() { + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + gInspector = await target.getFront("inspector"); + gDoc = doc; + runNextTest(); +}); + +addTest(function() { + info("Resolve a relative URL without providing a context node"); + gInspector.resolveRelativeURL("test.png?id=4#wow").then(url => { + is(url, "chrome://mochitests/content/chrome/devtools/server/tests/" + + "chrome/test.png?id=4#wow"); + runNextTest(); + }); +}); + +addTest(function() { + info("Resolve an absolute URL without providing a context node"); + gInspector.resolveRelativeURL("chrome://mochitests/content/chrome/" + + "devtools/server/").then(url => { + is(url, "chrome://mochitests/content/chrome/devtools/server/"); + runNextTest(); + }); +}); + +addTest(function() { + info("Resolve a relative URL providing a context node"); + const node = gDoc.querySelector(".big-horizontal"); + gInspector.resolveRelativeURL("test.png?id=4#wow", node).then(url => { + is(url, "chrome://mochitests/content/chrome/devtools/server/tests/" + + "chrome/test.png?id=4#wow"); + runNextTest(); + }); +}); + +addTest(function() { + info("Resolve an absolute URL providing a context node"); + const node = gDoc.querySelector(".big-horizontal"); + gInspector.resolveRelativeURL("chrome://mochitests/content/chrome/" + + "devtools/server/", node).then(url => { + is(url, "chrome://mochitests/content/chrome/devtools/server/"); + runNextTest(); + }); +}); + +addTest(function() { + gInspector = null; + gDoc = null; + runNextTest(); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=921102">Mozilla Bug 921102</a> +<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-scroll-into-view.html b/devtools/server/tests/chrome/test_inspector-scroll-into-view.html new file mode 100644 index 0000000000..0b3eb863ac --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-scroll-into-view.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=901250 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 901250</title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gInspectee = null; +let gWalker = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + gInspectee = doc; + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + runNextTest(); +}); + +addTest(async function testScrollIntoView() { + const id = "#scroll-into-view"; + let rect = gInspectee.querySelector(id).getBoundingClientRect(); + const nodeFront = await gWalker.querySelector(gWalker.rootNode, id); + let inViewport = rect.x >= 0 && + rect.y >= 0 && + rect.y <= gInspectee.defaultView.innerHeight && + rect.x <= gInspectee.defaultView.innerWidth; + + ok(!inViewport, "Element is not in viewport."); + + await nodeFront.scrollIntoView(); + + SimpleTest.executeSoon(() => { + rect = gInspectee.querySelector(id).getBoundingClientRect(); + inViewport = rect.x >= 0 && + rect.y >= 0 && + rect.y <= gInspectee.defaultView.innerHeight && + rect.x <= gInspectee.defaultView.innerWidth; + + ok(inViewport, "Element is in viewport."); + + runNextTest(); + }); +}); + +addTest(function cleanup() { + gWalker = null; + gInspectee = null; + runNextTest(); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=901250">Mozilla Bug 901250</a> +<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-search-front.html b/devtools/server/tests/chrome/test_inspector-search-front.html new file mode 100644 index 0000000000..a8c24135b2 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-search-front.html @@ -0,0 +1,172 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=835896 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 835896</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + let walkerFront = null; + let inspector = null; + + // WalkerFront specific tests. These aren't to excercise search + // edge cases so much as to test the state the Front maintains between + // searches. + // See also test_inspector-search.html + + addAsyncTest(async function setup() { + info("Setting up inspector and walker actors."); + + const url = document.getElementById("inspectorContent").href; + + const { target } = await attachURL(url); + inspector = await target.getFront("inspector"); + walkerFront = inspector.walker; + + runNextTest(); + }); + + addAsyncTest(async function testWalkerFrontDefaults() { + info("Testing search API using WalkerFront."); + const nodes = await walkerFront.querySelectorAll(walkerFront.rootNode, "h2"); + const fronts = await nodes.items(); + + const frontResult = await walkerFront.search(""); + ok(!frontResult, "Null result on front when searching for ''"); + + let results = await walkerFront.search("h2"); + isDeeply(results, { + node: fronts[0], + type: "search", + resultsIndex: 0, + resultsLength: 3, + }, "Default options work"); + + results = await walkerFront.search("h2", { }); + isDeeply(results, { + node: fronts[1], + type: "search", + resultsIndex: 1, + resultsLength: 3, + }, "Search works with empty options"); + + // Clear search data to remove result state on the front + await walkerFront.search(""); + runNextTest(); + }); + + addAsyncTest(async function testMultipleSearches() { + info("Testing search API using WalkerFront (reverse=false)"); + const nodes = await walkerFront.querySelectorAll(walkerFront.rootNode, "h2"); + const fronts = await nodes.items(); + + let results = await walkerFront.search("h2"); + isDeeply(results, { + node: fronts[0], + type: "search", + resultsIndex: 0, + resultsLength: 3, + }, "Search works with multiple results (reverse=false)"); + + results = await walkerFront.search("h2"); + isDeeply(results, { + node: fronts[1], + type: "search", + resultsIndex: 1, + resultsLength: 3, + }, "Search works with multiple results (reverse=false)"); + + results = await walkerFront.search("h2"); + isDeeply(results, { + node: fronts[2], + type: "search", + resultsIndex: 2, + resultsLength: 3, + }, "Search works with multiple results (reverse=false)"); + + results = await walkerFront.search("h2"); + isDeeply(results, { + node: fronts[0], + type: "search", + resultsIndex: 0, + resultsLength: 3, + }, "Search works with multiple results (reverse=false)"); + + // Clear search data to remove result state on the front + await walkerFront.search(""); + runNextTest(); + }); + + addAsyncTest(async function testMultipleSearchesReverse() { + info("Testing search API using WalkerFront (reverse=true)"); + const nodes = await walkerFront.querySelectorAll(walkerFront.rootNode, "h2"); + const fronts = await nodes.items(); + + let results = await walkerFront.search("h2", {reverse: true}); + isDeeply(results, { + node: fronts[2], + type: "search", + resultsIndex: 2, + resultsLength: 3, + }, "Search works with multiple results (reverse=true)"); + + results = await walkerFront.search("h2", {reverse: true}); + isDeeply(results, { + node: fronts[1], + type: "search", + resultsIndex: 1, + resultsLength: 3, + }, "Search works with multiple results (reverse=true)"); + + results = await walkerFront.search("h2", {reverse: true}); + isDeeply(results, { + node: fronts[0], + type: "search", + resultsIndex: 0, + resultsLength: 3, + }, "Search works with multiple results (reverse=true)"); + + results = await walkerFront.search("h2", {reverse: true}); + isDeeply(results, { + node: fronts[2], + type: "search", + resultsIndex: 2, + resultsLength: 3, + }, "Search works with multiple results (reverse=true)"); + + results = await walkerFront.search("h2", {reverse: false}); + isDeeply(results, { + node: fronts[0], + type: "search", + resultsIndex: 0, + resultsLength: 3, + }, "Search works with multiple results (reverse=false)"); + + // Clear search data to remove result state on the front + await walkerFront.search(""); + runNextTest(); + }); + + runNextTest(); +}; + </script> +</head> +<body> +<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector-template.html b/devtools/server/tests/chrome/test_inspector-template.html new file mode 100644 index 0000000000..6fbc7742c6 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector-template.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1078374 +Display template tag content in inspector. +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + let gWalker = null; + + addAsyncTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + + const { target } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + + runNextTest(); + }); + + addAsyncTest(async function testWalker() { + const nodeFront = await gWalker.querySelector(gWalker.rootNode, "template"); + + let children = await gWalker.children(nodeFront); + is(children.nodes.length, 1, "Found one child under the template element"); + + const docFragment = children.nodes[0]; + is(docFragment.nodeName, "#document-fragment", + "First child under <template> is a document-fragment"); + + children = await gWalker.children(docFragment); + is(children.nodes.length, 1, "Found one child under the template element"); + + const p = children.nodes[0]; + is(p.nodeName, "P", + "First child under the document-fragment is a p element"); + + runNextTest(); + }); + + runNextTest(); +}; + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<a id="inspectorContent" target="_blank" href="inspector-template.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector_getImageData-wait-for-load.html b/devtools/server/tests/chrome/test_inspector_getImageData-wait-for-load.html new file mode 100644 index 0000000000..cd3bffae65 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector_getImageData-wait-for-load.html @@ -0,0 +1,133 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tests for InspectorActor.getImageData() in following cases: + * Image takes too long to load (the method rejects after a timeout). + * Image is loading when the method is called and the load finishes before + timeout. + * Image fails to load. + +https://bugzilla.mozilla.org/show_bug.cgi?id=1192536 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1192536</title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +const PATH = "http://mochi.test:8888/chrome/devtools/server/tests/chrome/"; +const BASE_IMAGE = PATH + "inspector-delay-image-response.sjs"; +const DELAYED_IMAGE = BASE_IMAGE + "?delay=300"; +const TIMEOUT_IMAGE = BASE_IMAGE + "?delay=50000"; +const NONEXISTENT_IMAGE = PATH + "this-does-not-exist.png"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +function pushPref(preferenceName, value) { + return new Promise(resolve => { + const options = {"set": [[preferenceName, value]]}; + SpecialPowers.pushPrefEnv(options, resolve); + }); +} + +let gImg = null; +let gNodeFront = null; +let gWalker = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + gNodeFront = await gWalker.querySelector(gWalker.rootNode, "img.custom"); + gImg = doc.querySelector("img.custom"); + ok(gNodeFront, "Got the image NodeFront."); + ok(gImg, "Got the image Node."); + runNextTest(); +}); + +addTest(async function testTimeout() { + info("Testing that the method aborts if the image takes too long to load."); + + // imageToImageData() only times out when flags.testing is not set. + await pushPref("devtools.testing", false); + + gImg.src = TIMEOUT_IMAGE; + + info("Calling getImageData()."); + ensureRejects(gNodeFront.getImageData(), "Timeout image").then(runNextTest); +}); + +addTest(async function testNonExistentImage() { + info("Testing that non-existent image causes a rejection."); + + // This test shouldn't hit the timeout. + await pushPref("devtools.testing", true); + + gImg.src = NONEXISTENT_IMAGE; + + info("Calling getImageData()."); + ensureRejects(gNodeFront.getImageData(), "Non-existent image").then(runNextTest); +}); + +addTest(async function testDelayedImage() { + info("Testing that the method waits for an image to load."); + + // This test shouldn't hit the timeout. + await pushPref("devtools.testing", true); + + gImg.src = DELAYED_IMAGE; + + info("Calling getImageData()."); + checkImageData(gNodeFront.getImageData()).then(runNextTest); +}); + +addTest(function cleanup() { + gImg = null; + gNodeFront = null; + gWalker = null; + runNextTest(); +}); + +/** + * Asserts that the given promise rejects. + */ +function ensureRejects(promise, desc) { + return promise.then(() => { + ok(false, desc + ": promise resolved unexpectedly."); + }, () => { + ok(true, desc + ": promise rejected as expected."); + }); +} + +/** + * Waits for the call to getImageData() the resolve and checks that the image + * size is reported correctly. + */ +function checkImageData(promise, { width, height } = { width: 1, height: 1 }) { + return promise.then(({ size }) => { + is(size.naturalWidth, width, "The width is correct."); + is(size.naturalHeight, height, "The height is correct."); + }); +} + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1192536">Mozilla Bug 1192536</a> +<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector_getImageData.html b/devtools/server/tests/chrome/test_inspector_getImageData.html new file mode 100644 index 0000000000..53597b144a --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector_getImageData.html @@ -0,0 +1,166 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=932937 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 932937</title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gWalker = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + runNextTest(); +}); + +addTest(function testLargeImage() { + // Select the image node from the test page + gWalker.querySelector(gWalker.rootNode, ".big-horizontal").then(img => { + ok(img, "Image node found in the test page"); + ok(img.getImageData, "Image node has the getImageData function"); + + img.getImageData(100).then(imageData => { + ok(imageData.data, "Image data actor was sent back"); + ok(imageData.size, "Image size info was sent back too"); + is(imageData.size.naturalWidth, 5333, "Natural width of the image correct"); + is(imageData.size.naturalHeight, 3000, "Natural width of the image correct"); + ok(imageData.size.resized, "Image was resized"); + + // eslint-disable-next-line max-nested-callbacks + imageData.data.string().then(str => { + ok(str, "We have an image data string!"); + testResizing(imageData, str); + }); + }); + }); +}); + +addTest(function testLargeCanvas() { + // Select the canvas node from the test page + gWalker.querySelector(gWalker.rootNode, ".big-vertical").then(canvas => { + ok(canvas, "Image node found in the test page"); + ok(canvas.getImageData, "Image node has the getImageData function"); + + canvas.getImageData(350).then(imageData => { + ok(imageData.data, "Image data actor was sent back"); + ok(imageData.size, "Image size info was sent back too"); + is(imageData.size.naturalWidth, 1000, "Natural width of the image correct"); + is(imageData.size.naturalHeight, 2000, "Natural width of the image correct"); + ok(imageData.size.resized, "Image was resized"); + + // eslint-disable-next-line max-nested-callbacks + imageData.data.string().then(str => { + ok(str, "We have an image data string!"); + testResizing(imageData, str); + }); + }); + }); +}); + +addTest(function testSmallImage() { + // Select the small image node from the test page + gWalker.querySelector(gWalker.rootNode, ".small").then(img => { + ok(img, "Image node found in the test page"); + ok(img.getImageData, "Image node has the getImageData function"); + + img.getImageData().then(imageData => { + ok(imageData.data, "Image data actor was sent back"); + ok(imageData.size, "Image size info was sent back too"); + is(imageData.size.naturalWidth, 245, "Natural width of the image correct"); + is(imageData.size.naturalHeight, 240, "Natural width of the image correct"); + ok(!imageData.size.resized, "Image was NOT resized"); + + // eslint-disable-next-line max-nested-callbacks + imageData.data.string().then(str => { + ok(str, "We have an image data string!"); + testResizing(imageData, str); + }); + }); + }); +}); + +addTest(function testDataImage() { + // Select the data image node from the test page + gWalker.querySelector(gWalker.rootNode, ".data").then(img => { + ok(img, "Image node found in the test page"); + ok(img.getImageData, "Image node has the getImageData function"); + + img.getImageData(14).then(imageData => { + ok(imageData.data, "Image data actor was sent back"); + ok(imageData.size, "Image size info was sent back too"); + is(imageData.size.naturalWidth, 28, "Natural width of the image correct"); + is(imageData.size.naturalHeight, 28, "Natural width of the image correct"); + ok(imageData.size.resized, "Image was resized"); + + // eslint-disable-next-line max-nested-callbacks + imageData.data.string().then(str => { + ok(str, "We have an image data string!"); + testResizing(imageData, str); + }); + }); + }); +}); + +addTest(function testNonImgOrCanvasElements() { + gWalker.querySelector(gWalker.rootNode, "body").then(body => { + ensureRejects(body.getImageData(), "Invalid element").then(runNextTest); + }); +}); + +addTest(function cleanup() { + gWalker = null; + runNextTest(); +}); + +/** + * Checks if the server told the truth about resizing the image + */ +function testResizing(imageData, str) { + const img = document.createElement("img"); + img.addEventListener("load", () => { + const resized = !(img.naturalWidth == imageData.size.naturalWidth && + img.naturalHeight == imageData.size.naturalHeight); + is(imageData.size.resized, resized, "Server told the truth about resizing"); + runNextTest(); + }); + img.src = str; +} + +/** + * Asserts that the given promise rejects. + */ +function ensureRejects(promise, desc) { + return promise.then(() => { + ok(false, desc + ": promise resolved unexpectedly."); + }, () => { + ok(true, desc + ": promise rejected as expected."); + }); +} + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=932937">Mozilla Bug 932937</a> +<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector_getImageDataFromURL.html b/devtools/server/tests/chrome/test_inspector_getImageDataFromURL.html new file mode 100644 index 0000000000..f8496fce5e --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector_getImageDataFromURL.html @@ -0,0 +1,116 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tests for InspectorActor.getImageDataFromURL() in following cases: + * Normal case, image loads after a small delay. + * Image takes too long to load (the method rejects after a timeout). + * Image fails to load. + +https://bugzilla.mozilla.org/show_bug.cgi?id=1192536 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1192536</title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +const PATH = "http://mochi.test:8888/chrome/devtools/server/tests/chrome/"; +const BASE_IMAGE = PATH + "inspector-delay-image-response.sjs"; +const DELAYED_IMAGE = BASE_IMAGE + "?delay=300"; +const TIMEOUT_IMAGE = BASE_IMAGE + "?delay=50000"; +const NONEXISTENT_IMAGE = PATH + "this-does-not-exist.png"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +function pushPref(preferenceName, value) { + return new Promise(resolve => { + const options = {"set": [[preferenceName, value]]}; + SpecialPowers.pushPrefEnv(options, resolve); + }); +} + +let gInspector = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target } = await attachURL(url); + gInspector = await target.getFront("inspector"); + runNextTest(); +}); + +addTest(async function testTimeout() { + info("Testing that the method aborts if the image takes too long to load."); + + // imageToImageData() only times out when flags.testing is not set. + await pushPref("devtools.testing", false); + + ensureRejects(gInspector.getImageDataFromURL(TIMEOUT_IMAGE), + "Image that loads for too long").then(runNextTest); +}); + +addTest(async function testNonExistentImage() { + info("Testing that non-existent image causes a rejection."); + + // This test shouldn't hit the timeout. + await pushPref("devtools.testing", true); + + ensureRejects(gInspector.getImageDataFromURL(NONEXISTENT_IMAGE), + "Non-existent image").then(runNextTest); +}); + +addTest(async function testNormalImage() { + info("Testing that the method waits for an image to load."); + + // This test shouldn't hit the timeout. + await pushPref("devtools.testing", true); + + checkImageData(gInspector.getImageDataFromURL(DELAYED_IMAGE)).then(runNextTest); +}); + +addTest(function cleanup() { + gInspector = null; + runNextTest(); +}); + +/** + * Asserts that the given promise rejects. + */ +function ensureRejects(promise, desc) { + return promise.then(() => { + ok(false, desc + ": promise resolved unexpectedly."); + }, () => { + ok(true, desc + ": promise rejected as expected."); + }); +} + +/** + * Waits for the call to getImageData() the resolve and checks that the image + * size is reported correctly. + */ +function checkImageData(promise, { width, height } = { width: 1, height: 1 }) { + return promise.then(({ size }) => { + is(size.naturalWidth, width, "The width is correct."); + is(size.naturalHeight, height, "The height is correct."); + }); +} + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1192536">Mozilla Bug 1192536</a> +<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector_getNodeFromActor.html b/devtools/server/tests/chrome/test_inspector_getNodeFromActor.html new file mode 100644 index 0000000000..c3c5d32af9 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector_getNodeFromActor.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1155653 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1155653</title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gWalker; + +addTest(async function() { + const url = document.getElementById("inspectorContent").href; + const { target } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + runNextTest(); +}); + +addTest(function() { + info("Try to get a NodeFront from an invalid actorID"); + gWalker.getNodeFromActor("invalid", ["node"]).then(node => { + ok(!node, "The node returned is null"); + runNextTest(); + }); +}); + +addTest(function() { + info("Try to get a NodeFront from a valid actorID but invalid path"); + gWalker.getNodeFromActor(gWalker.actorID, ["invalid", "path"]).then(node => { + ok(!node, "The node returned is null"); + runNextTest(); + }); +}); + +addTest(function() { + info("Try to get a NodeFront from a valid actorID and valid path"); + gWalker.getNodeFromActor(gWalker.actorID, ["rootDoc"]).then(rootDocNode => { + ok(rootDocNode, "A node was returned"); + is(rootDocNode, gWalker.rootNode, "The right node was returned"); + runNextTest(); + }); +}); + +addTest(function() { + info("Try to get a NodeFront from a valid actorID and valid complex path"); + gWalker.getNodeFromActor(gWalker.actorID, + ["targetActor", "window", "document", "body"]).then(bodyNode => { + ok(bodyNode, "A node was returned"); + gWalker.querySelector(gWalker.rootNode, "body").then(node => { + is(bodyNode, node, "The body node was returned"); + runNextTest(); + }); + }); +}); + +addTest(function() { + gWalker = null; + runNextTest(); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1155653">Mozilla Bug 1155653</a> +<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_inspector_getOffsetParent.html b/devtools/server/tests/chrome/test_inspector_getOffsetParent.html new file mode 100644 index 0000000000..09da7d55d1 --- /dev/null +++ b/devtools/server/tests/chrome/test_inspector_getOffsetParent.html @@ -0,0 +1,129 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1345119 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1345119</title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +var gWalker; +var gHTMLNode; +var gBodyNode; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + gBodyNode = await gWalker.querySelector(gWalker.rootNode, "body"); + gHTMLNode = await gWalker.querySelector(gWalker.rootNode, "html"); + runNextTest(); +}); + +addTest(function() { + info("Try to get the offset parent for a dead node (null)"); + gWalker.getOffsetParent(null).then(offsetParent => { + ok(!offsetParent, "No offset parent found"); + runNextTest(); + }); +}); + +addTest(function() { + info("Try to get the offset parent for a node that is absolutely positioned inside a " + + "relative node"); + gWalker.querySelector(gWalker.rootNode, "#absolute_child").then(node => { + return gWalker.getOffsetParent(node); + }).then(offsetParent => { + ok(offsetParent, "The node has an offset parent"); + gWalker.querySelector(gWalker.rootNode, "#relative_parent").then(parent => { + ok(offsetParent === parent, "The offset parent is the correct node"); + runNextTest(); + }); + }); +}); + +addTest(function() { + info("Try to get the offset parent for a node that is absolutely positioned outside a" + + " relative node"); + gWalker.querySelector(gWalker.rootNode, "#no_parent").then(node => { + return gWalker.getOffsetParent(node); + }).then(offsetParent => { + ok(offsetParent === gBodyNode || offsetParent === gHTMLNode, + "The node's offset parent is the body or html node"); + runNextTest(); + }); +}); + +addTest(function() { + info("Try to get the offset parent for a relatively positioned node"); + gWalker.querySelector(gWalker.rootNode, "#relative_parent").then(node => { + return gWalker.getOffsetParent(node); + }).then(offsetParent => { + ok(offsetParent === gBodyNode || offsetParent === gHTMLNode, + "The node's offset parent is the body or html node"); + runNextTest(); + }); +}); + +addTest(function() { + info("Try to get the offset parent for a statically positioned node"); + gWalker.querySelector(gWalker.rootNode, "#static").then(node => { + return gWalker.getOffsetParent(node); + }).then(offsetParent => { + ok(offsetParent === gBodyNode || offsetParent === gHTMLNode, + "The node's offset parent is the body or html node"); + runNextTest(); + }); +}); + +addTest(function() { + info("Try to get the offset parent for a fixed positioned node"); + gWalker.querySelector(gWalker.rootNode, "#fixed").then(node => { + return gWalker.getOffsetParent(node); + }).then(offsetParent => { + ok(offsetParent === gBodyNode || offsetParent === gHTMLNode, + "The node's offset parent is the body or html node"); + runNextTest(); + }); +}); + +addTest(function() { + info("Try to get the offset parent for the body"); + gWalker.querySelector(gWalker.rootNode, "body").then(node => { + return gWalker.getOffsetParent(node); + }).then(offsetParent => { + ok(!offsetParent, "The body has no offset parent"); + runNextTest(); + }); +}); + +addTest(function() { + gWalker = null; + gBodyNode = null; + runNextTest(); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1345119">Mozilla Bug 1345119</a> +<a id="inspectorContent" target="_blank" href="inspector_getOffsetParent.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_makeGlobalObjectReference.html b/devtools/server/tests/chrome/test_makeGlobalObjectReference.html new file mode 100644 index 0000000000..c9b26fc86e --- /dev/null +++ b/devtools/server/tests/chrome/test_makeGlobalObjectReference.html @@ -0,0 +1,96 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=914405 + +Debugger.prototype.makeGlobalObjectReference should dereference WindowProxy +(outer window) objects. +--> +<head> + <meta charset="utf-8"> + <title>Mozilla Bug 914405</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +const {addSandboxedDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm"); +addSandboxedDebuggerToGlobal(this); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + // Load one of our iframes over http to force it in a different compartment + // from the current window and the other iframe. + const iframe = document.createElement("iframe"); + const baseURL = "http://mochi.test:8888/chrome/devtools/server/tests/chrome/"; + iframe.src = baseURL + "iframe1_makeGlobalObjectReference.html"; + iframe.onload = iframeOnLoad; + document.body.appendChild(iframe); + + function iframeOnLoad() { + const dbg = new Debugger(); + + // 'o' for 'outer window' + const g1o = iframe.contentWindow; + ok(!dbg.hasDebuggee(g1o), "iframe is not initially a debuggee"); + + // Like addDebuggee, makeGlobalObjectReference innerizes. + // 'i' stands for 'inner window'. + // 'DO' stands for 'Debugger.Object'. + const g1iDO = dbg.makeGlobalObjectReference(g1o); + ok(!dbg.hasDebuggee(g1o), + "makeGlobalObjectReference does not add g1 as debuggee, designated via outer"); + ok(!dbg.hasDebuggee(g1iDO), + "makeGlobalObjectReference does not add g1 as debuggee, designated via D.O "); + + // Wrapping an object automatically outerizes it, so dereferencing an + // inner object D.O gets you an outer object. + // ('===' does distinguish inner and outer objects.) + // (That's a capital '=', if you must know.) + ok(g1iDO.unsafeDereference() === g1o, "g1iDO has the right referent"); + + // However, Debugger.Objects do distinguish inner and outer windows. + const g1oDO = g1iDO.makeDebuggeeValue(g1o); + ok(g1iDO !== g1oDO, "makeDebuggeeValue doesn't innerize"); + ok(g1iDO.unsafeDereference() === g1oDO.unsafeDereference(), + "unsafeDereference() outerizes," + + " so inner and outer window D.Os both dereference to outer"); + + ok(dbg.addDebuggee(g1o) === g1iDO, "addDebuggee returns the inner window's D.O"); + ok(dbg.hasDebuggee(g1o), "addDebuggee adds the correct global"); + ok(dbg.hasDebuggee(g1iDO), + "hasDebuggee can take a D.O referring to the inner window"); + ok(dbg.hasDebuggee(g1oDO), + "hasDebuggee can take a D.O referring to the outer window"); + + const iframe2 = document.createElement("iframe"); + iframe2.src = "iframe2_makeGlobalObjectReference.html"; + iframe2.onload = iframe2OnLoad; + document.body.appendChild(iframe2); + + function iframe2OnLoad() { + // makeGlobalObjectReference dereferences CCWs. + const g2o = iframe2.contentWindow; + g2o.g1o = g1o; + + const g2iDO = dbg.addDebuggee(g2o); + const g2g1oDO = g2iDO.getOwnPropertyDescriptor("g1o").value; + ok(g2g1oDO !== g1oDO, "g2's cross-compartment wrapper for g1o gets its own D.O"); + ok(g2g1oDO.unwrap() === g1oDO, + "unwrapping g2's cross-compartment wrapper for g1o gets the right D.O"); + ok(dbg.makeGlobalObjectReference(g2g1oDO) === g1iDO, + "makeGlobalObjectReference unwraps cross-compartment wrappers, and innerizes"); + + SimpleTest.finish(); + } + } +}; + +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_memory.html b/devtools/server/tests/chrome/test_memory.html new file mode 100644 index 0000000000..79ba29c913 --- /dev/null +++ b/devtools/server/tests/chrome/test_memory.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 923275 - Add a memory monitor widget to the developer toolbar +--> +<head> + <meta charset="utf-8"> + <title>Memory monitoring actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="memory-helpers.js" type="application/javascript"></script> +<script> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + (async function() { + const { memory, target } = await startServerAndGetSelectedTabMemory(); + const measurement = await memory.measure(); + ok(measurement.total > 0, "total memory is valid"); + ok(measurement.domSize > 0, "domSize is valid"); + ok(measurement.styleSize > 0, "styleSize is valid"); + ok(measurement.jsObjectsSize > 0, "jsObjectsSize is valid"); + ok(measurement.jsStringsSize > 0, "jsStringsSize is valid"); + ok(measurement.jsOtherSize > 0, "jsOtherSize is valid"); + ok(measurement.otherSize > 0, "otherSize is valid"); + ok(measurement.jsMilliseconds, "jsMilliseconds is valid"); + ok(measurement.nonJSMilliseconds, "nonJSMilliseconds is valid"); + await destroyServerAndFinish(target); + })(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_memory_allocations_02.html b/devtools/server/tests/chrome/test_memory_allocations_02.html new file mode 100644 index 0000000000..aedddb49ab --- /dev/null +++ b/devtools/server/tests/chrome/test_memory_allocations_02.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1132764 - Test controlling the maximum allocations log length over the RDP. +--> +<head> + <meta charset="utf-8"> + <title>Memory monitoring actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="memory-helpers.js" type="application/javascript"></script> +<script> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + (async function() { + const { memory, target } = await startServerAndGetSelectedTabMemory(); + await memory.attach(); + + const allocs = []; + let eventsFired = 0; + let intervalId = null; + function onAlloc() { + eventsFired++; + } + function startAllocating() { + intervalId = setInterval(() => { + for (let i = 100000; --i;) { + allocs.push(new Object()); + } + }, 1); + } + function stopAllocating() { + clearInterval(intervalId); + } + + memory.on("allocations", onAlloc); + + await memory.startRecordingAllocations({ + drainAllocationsTimeout: 10, + }); + + await waitUntil(() => eventsFired > 5); + ok(eventsFired > 5, + "Some allocation events fired without allocating much via auto drain"); + await memory.stopRecordingAllocations(); + + // Set a really high auto drain timer so we can test if + // it fires on GC + eventsFired = 0; + const startTime = performance.now(); + const drainTimer = 1000000; + await memory.startRecordingAllocations({ + drainAllocationsTimeout: drainTimer, + }); + + startAllocating(); + await waitUntil(() => { + Cu.forceGC(); + return eventsFired > 1; + }); + stopAllocating(); + ok(performance.now() - drainTimer < startTime, + "Allocation events fired on GC before timer"); + await memory.stopRecordingAllocations(); + + memory.off("allocations", onAlloc); + await memory.detach(); + destroyServerAndFinish(target); + })(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_memory_allocations_03.html b/devtools/server/tests/chrome/test_memory_allocations_03.html new file mode 100644 index 0000000000..ca6a1ec1b4 --- /dev/null +++ b/devtools/server/tests/chrome/test_memory_allocations_03.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1067491 - Test that frames keep the same index while we are recording. +--> +<head> + <meta charset="utf-8"> + <title>Memory monitoring actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="memory-helpers.js" type="application/javascript"></script> +<script> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + (async function() { + const { memory, target } = await startServerAndGetSelectedTabMemory(); + await memory.attach(); + + await memory.startRecordingAllocations(); + + // Allocate twice with the exact same stack (hence setTimeout rather than + // allocating directly in the generator), but with getAllocations() calls in + // between. + + const allocs = []; + function allocator() { + allocs.push({}); + } + + setTimeout(allocator, 1); + await waitForTime(2); + const first = await memory.getAllocations(); + + setTimeout(allocator, 1); + await waitForTime(2); + const second = await memory.getAllocations(); + + await memory.stopRecordingAllocations(); + + // Assert that each frame in the first response has the same index in the + // second response. This isn't commutative, so we don't check that all + // of the second response's frames are the same in the first response, + // because there might be new allocations that happen after the first query + // but before the second. + + function assertSameFrame(a, b) { + info(" First frame = " + JSON.stringify(a, null, 4)); + info(" Second frame = " + JSON.stringify(b, null, 4)); + + is(!!a, !!b); + if (!a || !b) { + return; + } + + is(a.source, b.source); + is(a.line, b.line); + is(a.column, b.column); + is(a.functionDisplayName, b.functionDisplayName); + is(a.parent, b.parent); + } + + for (let i = 0; i < first.frames.length; i++) { + info("Checking frames at index " + i + ":"); + assertSameFrame(first.frames[i], second.frames[i]); + } + + await memory.detach(); + destroyServerAndFinish(target); + })(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_memory_allocations_04.html b/devtools/server/tests/chrome/test_memory_allocations_04.html new file mode 100644 index 0000000000..8bb64c591c --- /dev/null +++ b/devtools/server/tests/chrome/test_memory_allocations_04.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1068171 - Test controlling the memory actor's allocation sampling probability. +--> +<head> + <meta charset="utf-8"> + <title>Memory monitoring actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="memory-helpers.js" type="application/javascript"></script> +<script> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + (async function() { + const { memory, target } = await startServerAndGetSelectedTabMemory(); + await memory.attach(); + + const allocs = []; + function allocator() { + for (let i = 0; i < 100; i++) { + allocs.push({}); + } + } + + const testProbability = async function(p, expected) { + info("probability = " + p); + await memory.startRecordingAllocations({ + probability: p, + }); + allocator(); + const response = await memory.getAllocations(); + await memory.stopRecordingAllocations(); + return response.allocations.length; + }; + + is((await testProbability(0.0)), 0, + "With probability = 0.0, we shouldn't get any allocations."); + + ok((await testProbability(1.0)) >= 100, + "With probability = 1.0, we should get all 100 allocations (plus " + + "whatever allocations the actor and SpiderMonkey make)."); + + // We don't test any other probabilities because the test would be + // non-deterministic. We don't have a way to control the PRNG like we do in + // jit-tests + // (js/src/jit-test/tests/debug/Memory-allocationsSamplingProbability-*.js). + + await memory.detach(); + destroyServerAndFinish(target); + })(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_memory_allocations_05.html b/devtools/server/tests/chrome/test_memory_allocations_05.html new file mode 100644 index 0000000000..6220e71449 --- /dev/null +++ b/devtools/server/tests/chrome/test_memory_allocations_05.html @@ -0,0 +1,93 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1068144 - Test getting the timestamps for allocations. +--> +<head> + <meta charset="utf-8"> + <title>Memory monitoring actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="memory-helpers.js" type="application/javascript"></script> +<script> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + (async function() { + const { memory, target } = await startServerAndGetSelectedTabMemory(); + await memory.attach(); + + const allocs = []; + function allocator() { + allocs.push(new Object()); + } + + // Using setTimeout results in wildly varying delays that make it hard to + // test our timestamps and results in intermittent failures. Instead, we + // actually spin an empty loop for a whole millisecond. + function actuallyWaitOneWholeMillisecond() { + const start = window.performance.now(); + // eslint-disable-next-line curly + while (window.performance.now() - start < 1.000); + } + + await memory.startRecordingAllocations(); + + allocator(); + actuallyWaitOneWholeMillisecond(); + allocator(); + actuallyWaitOneWholeMillisecond(); + allocator(); + + const response = await memory.getAllocations(); + await memory.stopRecordingAllocations(); + + ok(response.allocationsTimestamps, "The response should have timestamps."); + is(response.allocationsTimestamps.length, response.allocations.length, + "There should be a timestamp for every allocation."); + + const allocatorIndices = response.allocations + .map(function(a, idx) { + const frame = response.frames[a]; + if (frame && frame.functionDisplayName === "allocator") { + return idx; + } + return null; + }) + .filter(function(idx) { + return idx !== null; + }); + + is(allocatorIndices.length, 3, + "Should have our 3 allocations from the `allocator` timeouts."); + + let lastTimestamp; + for (let i = 0; i < 3; i++) { + const timestamp = response.allocationsTimestamps[allocatorIndices[i]]; + info("timestamp", timestamp); + ok(timestamp, "We should have a timestamp for the `allocator` allocation."); + + if (lastTimestamp) { + const delta = timestamp - lastTimestamp; + info("delta since last timestamp", delta); + // ms + ok(delta >= 1, + "The timestamp should be about 1 ms after the last timestamp."); + } + + lastTimestamp = timestamp; + } + + await memory.detach(); + destroyServerAndFinish(target); + })(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_memory_allocations_06.html b/devtools/server/tests/chrome/test_memory_allocations_06.html new file mode 100644 index 0000000000..e3da7fac1a --- /dev/null +++ b/devtools/server/tests/chrome/test_memory_allocations_06.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1132764 - Test controlling the maximum allocations log length over the RDP. +--> +<head> + <meta charset="utf-8"> + <title>Memory monitoring actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="memory-helpers.js" type="application/javascript"></script> +<script> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + (async function() { + const { memory, target } = await startServerAndGetSelectedTabMemory(); + await memory.attach(); + + const allocs = []; + function allocator() { + allocs.push(new Object()); + } + + await memory.startRecordingAllocations({ + maxLogLength: 1, + }); + + allocator(); + allocator(); + allocator(); + + const response = await memory.getAllocations(); + await memory.stopRecordingAllocations(); + + is(response.allocations.length, 1, + "There should only be one entry in the allocations log."); + + await memory.detach(); + destroyServerAndFinish(target); + })(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_memory_allocations_07.html b/devtools/server/tests/chrome/test_memory_allocations_07.html new file mode 100644 index 0000000000..a9ae810cba --- /dev/null +++ b/devtools/server/tests/chrome/test_memory_allocations_07.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1192335 - Test getting the byte sizes for allocations. +--> +<head> + <meta charset="utf-8"> + <title>Memory monitoring actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="memory-helpers.js" type="application/javascript"></script> +<script> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + (async function() { + const { memory, target } = await startServerAndGetSelectedTabMemory(); + await memory.attach(); + + const allocs = []; + function allocator() { + allocs.push(new Object()); + } + + await memory.startRecordingAllocations(); + + allocator(); + allocator(); + allocator(); + + const response = await memory.getAllocations(); + await memory.stopRecordingAllocations(); + + ok(response.allocationSizes, "The response should have bytesizes."); + is(response.allocationSizes.length, response.allocations.length, + "There should be a bytesize for every allocation."); + ok(response.allocationSizes.length >= 3, + "There are atleast 3 allocations."); + ok(response.allocationSizes.every(isPositiveNumber), + "every bytesize is a positive number"); + + await memory.detach(); + destroyServerAndFinish(target); + })(); +}; + +function isPositiveNumber(n) { + return typeof n === "number" && n > 0; +} +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_memory_attach_01.html b/devtools/server/tests/chrome/test_memory_attach_01.html new file mode 100644 index 0000000000..89f1818292 --- /dev/null +++ b/devtools/server/tests/chrome/test_memory_attach_01.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 960671 - Test attaching and detaching from a memory actor. +--> +<head> + <meta charset="utf-8"> + <title>Memory monitoring actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="memory-helpers.js" type="application/javascript"></script> +<script> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + (async function() { + const { memory, target } = await startServerAndGetSelectedTabMemory(); + await memory.attach(); + ok(true, "Shouldn't have gotten an error attaching."); + await memory.detach(); + ok(true, "Shouldn't have gotten an error detaching."); + destroyServerAndFinish(target); + })(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_memory_attach_02.html b/devtools/server/tests/chrome/test_memory_attach_02.html new file mode 100644 index 0000000000..18a797fe5e --- /dev/null +++ b/devtools/server/tests/chrome/test_memory_attach_02.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 960671 - Test attaching and detaching while in the wrong state. +--> +<head> + <meta charset="utf-8"> + <title>Memory monitoring actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="memory-helpers.js" type="application/javascript"></script> +<script> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + (async function() { + const { memory, target } = await startServerAndGetSelectedTabMemory(); + + let e = null; + try { + await memory.detach(); + } catch (ee) { + e = ee; + } + ok(e, "Should have hit the wrongState error"); + + await memory.attach(); + + e = null; + try { + await memory.attach(); + } catch (ee) { + e = ee; + } + ok(e, "Should have hit the wrongState error"); + + await memory.detach(); + destroyServerAndFinish(target); + })(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_memory_census.html b/devtools/server/tests/chrome/test_memory_census.html new file mode 100644 index 0000000000..3c351740d3 --- /dev/null +++ b/devtools/server/tests/chrome/test_memory_census.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1067491 - Test taking a census over the RDP. +--> +<head> + <meta charset="utf-8"> + <title>Memory monitoring actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="memory-helpers.js" type="application/javascript"></script> +<script> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + (async function() { + const { memory, target } = await startServerAndGetSelectedTabMemory(); + await memory.attach(); + + const census = await memory.takeCensus(); + is(typeof census, "object"); + + await memory.detach(); + destroyServerAndFinish(target); + })(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_memory_gc_01.html b/devtools/server/tests/chrome/test_memory_gc_01.html new file mode 100644 index 0000000000..8b2f049602 --- /dev/null +++ b/devtools/server/tests/chrome/test_memory_gc_01.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1067491 - Test forcing a gc. +--> +<head> + <meta charset="utf-8"> + <title>Memory monitoring actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="memory-helpers.js" type="application/javascript"></script> +<script> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + (async function() { + const { memory, target } = await startServerAndGetSelectedTabMemory(); + + let beforeGC, afterGC; + + do { + let objects = []; + for (let i = 0; i < 1000; i++) { + const o = {}; + o[Math.random()] = 1; + objects.push(o); + } + objects = null; + + beforeGC = (await memory.measure()).total; + + await memory.forceGarbageCollection(); + + afterGC = (await memory.measure()).total; + } while (beforeGC < afterGC); + + ok(true, "The amount of memory after GC should eventually decrease"); + + destroyServerAndFinish(target); + })(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_memory_gc_events.html b/devtools/server/tests/chrome/test_memory_gc_events.html new file mode 100644 index 0000000000..5db1607c91 --- /dev/null +++ b/devtools/server/tests/chrome/test_memory_gc_events.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1137527 - Test receiving GC events from the memory actor. +--> +<head> + <meta charset="utf-8"> + <title>Memory monitoring actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src="memory-helpers.js" type="application/javascript"></script> +<script> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + const EventEmitter = require("devtools/shared/event-emitter"); + + (async function() { + const { memory, target } = await startServerAndGetSelectedTabMemory(); + await memory.attach(); + + const gotGcEvent = new Promise(resolve => { + EventEmitter.on(memory, "garbage-collection", gcData => { + ok(gcData, "Got GC data"); + resolve(); + }); + }); + + memory.forceGarbageCollection(); + await gotGcEvent; + + await memory.detach(); + destroyServerAndFinish(target); + })(); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_overflowing-body.html b/devtools/server/tests/chrome/test_overflowing-body.html new file mode 100644 index 0000000000..1fe52e0011 --- /dev/null +++ b/devtools/server/tests/chrome/test_overflowing-body.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test InspectorUtils.GetOverflowingChildrenOfElement applied to the body element +--> +<head> +<meta charset="utf-8"> +<title>Test InspectorUtils.GetOverflowingChildrenOfElement on the body element</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<style> +body { + overflow: auto; + margin: 0; +} +.tallBox { + overflow: auto; + background: lavender; + width: 200px; + height: 110vh; +} +</style> +<script> +'use strict'; + +SimpleTest.waitForExplicitFinish(); +const InspectorUtils = SpecialPowers.InspectorUtils; + +function runTests() { + const body = document.documentElement; + const overflowing_children = InspectorUtils.getOverflowingChildrenOfElement(body); + + is(overflowing_children.length, 1, `body has the expected number of children.`); + + SimpleTest.finish(); +}; +window.onload = runTests; +</script> +</head> +<body> +<div class="tallBox"><div> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_overflowing-children.html b/devtools/server/tests/chrome/test_overflowing-children.html new file mode 100644 index 0000000000..8ba81bec3d --- /dev/null +++ b/devtools/server/tests/chrome/test_overflowing-children.html @@ -0,0 +1,131 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test InspectorUtils.GetOverflowingChildrenOfElement in various cases +--> +<head> +<meta charset="utf-8"> +<title>Test InspectorUtils.GetOverflowingChildrenOfElement</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<style> +/* "e" is our custom tag name for "element" */ +e { + background: lightgray; + display: inline-block; + margin: 10px; + padding: 0; + border: 0; + width: 100px; + height: 100px; + overflow: auto; +} + +/* "c" is our custom tag name for "child" */ +c { + display: block; + background: green; +} + +.fixedSize { + width: 10px; + height: 10px; +} + +.target { + background: red; +} +</style> + +<script> +'use strict'; + +SimpleTest.waitForExplicitFinish(); +const InspectorUtils = SpecialPowers.InspectorUtils; + +const CASES = [ + {id: "no_children", expected: 0}, + {id: "one_child_no_overflow", expected: 0}, + {id: "margin_left_overflow", expected: 1}, + {id: "transform_overflow", expected: 1}, + {id: "nested_overflow", expected: 1}, + {id: "intermediate_overflow", expected: 1}, + {id: "multiple_overflow_at_different_depths", expected: 2}, +]; + +function runTests() { + // Assign each child element to an inner id so each of them can be identified for testing. + Array.from(document.getElementsByTagName('c')).forEach((e, i) => {e.id = `inner${i}`}); + + for (const {id, expected} of CASES) { + info(`Checking element id ${id}.`); + + const element = document.getElementById(id); + if (!element) { + ok(false, `Expected to find element with id ${id}.`); + continue; + } + const overflowing_children = InspectorUtils.getOverflowingChildrenOfElement(element); + + is(overflowing_children.length, expected, `${id} has the expected number of children.`); + + // Check that each child has the "target" class. Otherwise, we're getting the + // wrong children. We don't check each child with a test function, because we + // don't want to needlessly inflate the number of test functions in the log. + // But if we find a child that *doesn't* have the class "target", we report + // that as a test failure. + for (const child of overflowing_children) { + // child is a Node, but not necessarily an Element. We want to get the containing + // Element so that we can use its classList, tagName, and id properties. + let e = child; + if (child.nodeType !== Node.ELEMENT_NODE) { + e = child.parentElement; + } + if (!e.classList.contains("target")) { + ok(false, `${id} is reporting this unexpected child as a target: ${e.tagName} id=${e.id}`); + } + } + } + + SimpleTest.finish(); +}; +window.onload = runTests; +</script> +</head> +<body onload="runTests()"> + +<e id="no_children"></e> + +<e id="one_child_no_overflow"> + <c></c> +</e> + +<e id="margin_left_overflow"> + <c class="target" style="margin-left:100px">abcd</c> +</e> + +<e id="transform_overflow"> + <c class="target" style="transform: translate(50px)">abcd</c> +</e> + +<e id="nested_overflow"> + <c> + <c class="target" style="margin-left:100px">abcd</c> + </c> +</e> + +<e id="intermediate_overflow"> + <c class="fixedSize target" style="margin-left:100px"> + <c></c> + </c> +</e> + +<e id="multiple_overflow_at_different_depths"> + <c class="fixedSize target" style="margin-left:100px"> + <c></c> + </c> + <c style="margin-left:100px"> + <c class="target">abcd</c> + </c> +</e> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_preference.html b/devtools/server/tests/chrome/test_preference.html new file mode 100644 index 0000000000..f8856eee24 --- /dev/null +++ b/devtools/server/tests/chrome/test_preference.html @@ -0,0 +1,129 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 943251 - Test preferences actor +--> +<head> + <meta charset="utf-8"> + <title>Test Preference Actor</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +function runTests() { + const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); + const {DevToolsClient} = require("devtools/client/devtools-client"); + const {DevToolsServer} = require("devtools/server/devtools-server"); + const Services = require("Services"); + + SimpleTest.waitForExplicitFinish(); + + DevToolsServer.init(); + DevToolsServer.registerAllActors(); + + const client = new DevToolsClient(DevToolsServer.connectPipe()); + client.connect().then(function onConnect() { + return client.mainRoot.getFront("preference"); + }).then(function(p) { + const prefs = {}; + + const localPref = { + boolPref: true, + intPref: 0x1234, + charPref: "Hello World", + }; + + function checkValues() { + is(prefs.boolPref, localPref.boolPref, "read/write bool pref"); + is(prefs.intPref, localPref.intPref, "read/write int pref"); + is(prefs.charPref, localPref.charPref, "read/write string pref"); + + ["test.all.bool", "test.all.int", "test.all.string"].forEach(function(key) { + let expectedValue; + switch (Services.prefs.getPrefType(key)) { + case Ci.nsIPrefBranch.PREF_STRING: + expectedValue = Services.prefs.getCharPref(key); + break; + case Ci.nsIPrefBranch.PREF_INT: + expectedValue = Services.prefs.getIntPref(key); + break; + case Ci.nsIPrefBranch.PREF_BOOL: + expectedValue = Services.prefs.getBoolPref(key); + break; + default: + ok(false, "unexpected pref type (" + key + ")"); + break; + } + + is(prefs.allPrefs[key].value, expectedValue, + "valid preference value (" + key + ")"); + is(prefs.allPrefs[key].hasUserValue, Services.prefs.prefHasUserValue(key), + "valid hasUserValue (" + key + ")"); + }); + + ["test.bool", "test.int", "test.string"].forEach(function(key) { + ok(!prefs.allPrefs.hasOwnProperty(key), "expect no pref (" + key + ")"); + is(Services.prefs.getPrefType(key), Ci.nsIPrefBranch.PREF_INVALID, + "pref (" + key + ") is clear"); + }); + + client.close().then(() => { + DevToolsServer.destroy(); + SimpleTest.finish(); + }); + } + + function checkUndefined() { + let next = p.getCharPref("test.undefined"); + next = next.then( + () => ok(false, "getCharPref should've thrown for an undefined preference"), + (ex) => { + const messageRe = new RegExp( + "Protocol error \\(Error\\): preference is not of the right type: " + + `test.undefined from: ${p.actorID} ` + + "\\(resource://devtools/server/actors/preference.js:\\d+:\\d+\\)" + ); + ok(messageRe.test(ex.message), "Error message matches the expected format"); + } + ); + return next; + } + + function updatePrefsProperty(key) { + return function(value) { + prefs[key] = value; + }; + } + + p.getAllPrefs().then(updatePrefsProperty("allPrefs")) + .then(() => p.setBoolPref("test.bool", localPref.boolPref)) + .then(() => p.setIntPref("test.int", localPref.intPref)) + .then(() => p.setCharPref("test.string", localPref.charPref)) + .then(() => p.getBoolPref("test.bool")).then(updatePrefsProperty("boolPref")) + .then(() => p.getIntPref("test.int")).then(updatePrefsProperty("intPref")) + .then(() => p.getCharPref("test.string")).then(updatePrefsProperty("charPref")) + .then(() => p.clearUserPref("test.bool")) + .then(() => p.clearUserPref("test.int")) + .then(() => p.clearUserPref("test.string")) + .then(() => checkUndefined()) + .then(checkValues); + }); +} + +window.onload = function() { + SpecialPowers.pushPrefEnv({ + "set": [ + ["test.all.bool", true], + ["test.all.int", 0x4321], + ["test.all.string", "allizom"], + ], + }, runTests); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_styles-applied.html b/devtools/server/tests/chrome/test_styles-applied.html new file mode 100644 index 0000000000..9e27d9ab69 --- /dev/null +++ b/devtools/server/tests/chrome/test_styles-applied.html @@ -0,0 +1,143 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gWalker = null; +let gStyles = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + gStyles = await inspector.getPageStyle(); + runNextTest(); +}); + +addTest(function inheritedUserStyles() { + promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => { + return gStyles.getApplied(node, { inherited: true, filter: "user" }); + }).then(applied => { + ok(!applied[0].inherited, "Entry 0 should be uninherited"); + is(applied[0].rule.type, 100, "Entry 0 should be an element style"); + ok(!!applied[0].rule.href, "Element styles should have a URL"); + is(applied[0].rule.cssText, "", "Entry 0 should be an empty style"); + + is(applied[1].inherited.id, "uninheritable-rule-inheritable-style", + "Entry 1 should be inherited from the parent"); + is(applied[1].rule.type, 100, "Entry 1 should be an element style"); + is(applied[1].rule.cssText, "color: red;", + "Entry 1 should have the expected cssText"); + + is(applied[2].inherited.id, "inheritable-rule-inheritable-style", + "Entry 2 should be inherited from the parent's parent"); + is(applied[2].rule.type, 100, "Entry 2 should be an element style"); + is(applied[2].rule.cssText, "color: blue;", + "Entry 2 should have the expected cssText"); + + is(applied[3].inherited.id, "inheritable-rule-inheritable-style", + "Entry 3 should be inherited from the parent's parent"); + is(applied[3].rule.type, 1, "Entry 3 should be a rule style"); + is(applied[3].rule.cssText, "font-size: 15px;", + "Entry 3 should have the expected cssText"); + ok(!applied[3].matchedSelectors, + "Shouldn't get matchedSelectors with this request."); + + is(applied[4].inherited.id, "inheritable-rule-uninheritable-style", + "Entry 4 should be inherited from the parent's parent"); + is(applied[4].rule.type, 1, "Entry 4 should be an rule style"); + is(applied[4].rule.cssText, "font-size: 15px;", + "Entry 4 should have the expected cssText"); + ok(!applied[4].matchedSelectors, "Shouldn't get matchedSelectors with this request."); + + is(applied.length, 5, "Should have 5 rules."); + }).then(runNextTest)); +}); + +addTest(function inheritedSystemStyles() { + promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => { + return gStyles.getApplied(node, { inherited: true, filter: "ua" }); + }).then(applied => { + // If our system stylesheets are prone to churn, this might be a fragile + // test. If you're here because of that I apologize, file a bug + // and we can find a different way to test. + + ok(!applied[1].inherited, "Entry 1 should not be inherited"); + ok(applied[1].rule.parentStyleSheet.system, "Entry 1 should be a system style"); + is(applied[1].rule.type, 1, "Entry 1 should be a rule style"); + + is(applied.length, 8, "Should have the expected number of rules."); + }).then(runNextTest)); +}); + +addTest(function noInheritedStyles() { + promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => { + return gStyles.getApplied(node, { inherited: false, filter: "user" }); + }).then(applied => { + ok(!applied[0].inherited, "Entry 0 should be uninherited"); + is(applied[0].rule.type, 100, "Entry 0 should be an element style"); + is(applied[0].rule.cssText, "", "Entry 0 should be an empty style"); + is(applied.length, 1, "Should have 1 rule."); + }).then(runNextTest)); +}); + +addTest(function matchedSelectors() { + promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => { + return gStyles.getApplied(node, { + inherited: true, filter: "user", matchedSelectors: true, + }); + }).then(applied => { + is(applied[3].matchedSelectors[0], ".inheritable-rule", + "Entry 3 should have a matched selector"); + is(applied[4].matchedSelectors[0], ".inheritable-rule", + "Entry 4 should have a matched selector"); + }).then(runNextTest)); +}); + +addTest(function testMediaQuery() { + promiseDone(gWalker.querySelector(gWalker.rootNode, "#mediaqueried").then(node => { + return gStyles.getApplied(node, { + inherited: false, filter: "user", matchedSelectors: true, + }); + }).then(applied => { + is(applied[1].rule.type, 1, "Entry 1 is a rule style"); + is(applied[1].rule.parentRule.type, 4, "Entry 1's parent rule is a media rule"); + is(applied[1].rule.media[0], "screen", "Entry 1's rule has the expected medium"); + }).then(runNextTest)); +}); + +addTest(function cleanup() { + gStyles = null; + gWalker = null; + runNextTest(); +}); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_styles-computed.html b/devtools/server/tests/chrome/test_styles-computed.html new file mode 100644 index 0000000000..af96869ed8 --- /dev/null +++ b/devtools/server/tests/chrome/test_styles-computed.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gWalker = null; +let gStyles = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + gStyles = await inspector.getPageStyle(); + runNextTest(); +}); + +addTest(function testComputed() { + promiseDone( + gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => { + return gStyles.getComputed(node, {}); + }).then(computed => { + // Test a smattering of properties that include some system-defined + // props, some props that were defined in this node's stylesheet, + // and some default props. + is(computed["white-space"].value, "normal", "Default value should appear"); + is(computed.display.value, "block", "System stylesheet item should appear"); + is(computed.cursor.value, "crosshair", "Included stylesheet rule should appear"); + is(computed.color.value, "rgb(255, 0, 0)", + "Inherited style attribute should appear"); + is(computed["font-size"].value, "15px", "Inherited inline rule should appear"); + + // We didn't request markMatched, so these shouldn't be set + ok(!computed.cursor.matched, "Didn't ask for matched, shouldn't get it"); + ok(!computed.color.matched, "Didn't ask for matched, shouldn't get it"); + ok(!computed["font-size"].matched, "Didn't ask for matched, shouldn't get it"); + }).then(runNextTest) + ); +}); + +addTest(function testComputedUserMatched() { + promiseDone( + gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => { + return gStyles.getComputed(node, { filter: "user", markMatched: true }); + }).then(computed => { + ok(!computed["white-space"].matched, "Default style shouldn't match"); + ok(!computed.display.matched, "Only user styles should match"); + ok(computed.cursor.matched, "Asked for matched, should get it"); + ok(computed.color.matched, "Asked for matched, should get it"); + ok(computed["font-size"].matched, "Asked for matched, should get it"); + }).then(runNextTest) + ); +}); + +addTest(function testComputedSystemMatched() { + promiseDone( + gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => { + return gStyles.getComputed(node, { filter: "ua", markMatched: true }); + }).then(computed => { + ok(!computed["white-space"].matched, "Default style shouldn't match"); + ok(computed.display.matched, "System stylesheets should match"); + ok(computed.cursor.matched, "Asked for matched, should get it"); + ok(computed.color.matched, "Asked for matched, should get it"); + ok(computed["font-size"].matched, "Asked for matched, should get it"); + }).then(runNextTest) + ); +}); + +addTest(function testComputedUserOnlyMatched() { + promiseDone( + gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => { + return gStyles.getComputed(node, { filter: "user", onlyMatched: true }); + }).then(computed => { + ok(!("white-space" in computed), "Default style shouldn't exist"); + ok(!("display" in computed), "System stylesheets shouldn't exist"); + ok(("cursor" in computed), "User items should exist."); + ok(("color" in computed), "User items should exist."); + ok(("font-size" in computed), "User items should exist."); + }).then(runNextTest) + ); +}); + +addTest(function testComputedSystemOnlyMatched() { + promiseDone( + gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => { + return gStyles.getComputed(node, { filter: "ua", onlyMatched: true }); + }).then(computed => { + ok(!("white-space" in computed), "Default style shouldn't exist"); + ok(("display" in computed), "System stylesheets should exist"); + ok(("cursor" in computed), "User items should exist."); + ok(("color" in computed), "User items should exist."); + ok(("font-size" in computed), "User items should exist."); + }).then(runNextTest) + ); +}); + +addTest(function cleanup() { + gStyles = null; + gWalker = null; + runNextTest(); +}); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_styles-layout.html b/devtools/server/tests/chrome/test_styles-layout.html new file mode 100644 index 0000000000..f0441edd13 --- /dev/null +++ b/devtools/server/tests/chrome/test_styles-layout.html @@ -0,0 +1,110 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Test for Bug 1175040 - PageStyleActor.getLayout</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +<script type="application/javascript" src="inspector-helpers.js"></script> +<script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gWalker = null; +let gStyles = null; + +addTest(async function() { + const url = document.getElementById("inspectorContent").href; + const { target } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + gStyles = await inspector.getPageStyle(); + runNextTest(); +}); + +addTest(function() { + ok(gStyles.getLayout, "The PageStyleActor has a getLayout method"); + runNextTest(); +}); + +addAsyncTest(async function() { + const node = await gWalker.querySelector(gWalker.rootNode, "#layout-element"); + const layout = await gStyles.getLayout(node, {}); + + const properties = ["width", "height", + "margin-top", "margin-right", "margin-bottom", + "margin-left", "padding-top", "padding-right", + "padding-bottom", "padding-left", "border-top-width", + "border-right-width", "border-bottom-width", + "border-left-width", "z-index", "box-sizing", "display", + "position"]; + for (const prop of properties) { + ok((prop in layout), "The layout object returned has " + prop); + } + + runNextTest(); +}); + +addAsyncTest(async function() { + const node = await gWalker.querySelector(gWalker.rootNode, "#layout-element"); + const layout = await gStyles.getLayout(node, {}); + + const expected = { + "box-sizing": "border-box", + "position": "absolute", + "z-index": "2", + "display": "block", + "width": 50, + "height": 50, + "margin-top": "10px", + "margin-right": "20px", + "margin-bottom": "30px", + "margin-left": "0px", + }; + + for (const name in expected) { + is(layout[name], expected[name], "The " + name + " property is correct"); + } + + runNextTest(); +}); + +addAsyncTest(async function() { + const node = await gWalker.querySelector(gWalker.rootNode, + "#layout-auto-margin-element"); + + let layout = await gStyles.getLayout(node, {}); + ok(!("autoMargins" in layout), + "By default, getLayout doesn't return auto margins"); + + layout = await gStyles.getLayout(node, {autoMargins: true}); + ok(("autoMargins" in layout), + "getLayout does return auto margins when asked to"); + is(layout.autoMargins.left, "auto", "The left margin is auto"); + is(layout.autoMargins.right, "auto", "The right margin is auto"); + ok(!layout.autoMargins.bottom, "The bottom margin is not auto"); + ok(!layout.autoMargins.top, "The top margin is not auto"); + + runNextTest(); +}); + +addTest(function() { + gStyles = gWalker = null; + runNextTest(); +}); + +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175040">Mozilla Bug 1175040</a> +<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_styles-matched.html b/devtools/server/tests/chrome/test_styles-matched.html new file mode 100644 index 0000000000..e99f538663 --- /dev/null +++ b/devtools/server/tests/chrome/test_styles-matched.html @@ -0,0 +1,103 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +const CssLogic = require("devtools/shared/inspector/css-logic"); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gWalker = null; +let gStyles = null; +let gInspectee = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target, doc } = await attachURL(url); + gInspectee = doc; + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + gStyles = await inspector.getPageStyle(); + runNextTest(); +}); + +addTest(function testMatchedStyles() { + promiseDone(gWalker.querySelector(gWalker.rootNode, "#matched-test-node").then(node => { + return gStyles.getMatchedSelectors(node, "font-size", {}); + }).then(matched => { + is(matched[0].sourceText, "this.style", "First match comes from the element style"); + is(matched[0].selector, "@element.style", "Element style has a special selector"); + is(matched[0].value, "10px", "First match has the expected value"); + is(matched[0].status, CssLogic.STATUS.BEST, "First match is the best match"); + is(matched[0].rule.type, 100, "First match is an element style"); + is(matched[0].rule.href, gInspectee.defaultView.location.href, + "Node style comes from this document"); + + is(matched[1].sourceText, ".column-rule", + "Second match comes from a rule"); + is(matched[1].selector, ".column-rule", + "Second match comes from highest line number"); + is(matched[1].value, "25px", "Second match comes from highest column"); + is(matched[1].status, CssLogic.STATUS.PARENT_MATCH, + "Second match is from the parent"); + is(matched[1].rule.parentStyleSheet.href, null, + "Inline stylesheet shouldn't have an href"); + is(matched[1].rule.parentStyleSheet.nodeHref, gInspectee.defaultView.location.href, + "Inline stylesheet's nodeHref should match the current document"); + ok(!matched[1].rule.parentStyleSheet.system, + "Inline stylesheet shouldn't be a system stylesheet."); + + // matched[2] is only there to test matched[1]; do not need to test + + is(matched[3].value, "15px", "Third match has the expected value"); + }).then(runNextTest)); +}); + +addTest(function testSystemStyles() { + let testNode = null; + + promiseDone(gWalker.querySelector(gWalker.rootNode, "#matched-test-node").then(node => { + testNode = node; + return gStyles.getMatchedSelectors(testNode, "display", { filter: "user" }); + }).then(matched => { + is(matched.length, 0, "No user selectors apply to this rule."); + return gStyles.getMatchedSelectors(testNode, "display", { filter: "ua" }); + }).then(matched => { + is(matched[0].selector, "div", "Should match system div selector"); + is(matched[0].value, "block"); + }).then(runNextTest)); +}); + +addTest(function cleanup() { + gStyles = null; + gWalker = null; + gInspectee = null; + runNextTest(); +}); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_styles-modify.html b/devtools/server/tests/chrome/test_styles-modify.html new file mode 100644 index 0000000000..e615ec4425 --- /dev/null +++ b/devtools/server/tests/chrome/test_styles-modify.html @@ -0,0 +1,110 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +const {isCssPropertyKnown} = require("devtools/server/actors/css-properties"); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +var gWalker = null; +var gStyles = null; +var gInspectee = null; + +addAsyncTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + + const { target, doc } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gInspectee = doc; + + gWalker = inspector.walker; + gStyles = await inspector.getPageStyle(); + + runNextTest(); +}); + +addAsyncTest(async function modifyProperties() { + const localNode = gInspectee.querySelector("#inheritable-rule-inheritable-style"); + + const node = await gWalker.querySelector(gWalker.rootNode, + "#inheritable-rule-inheritable-style"); + + const applied = await gStyles.getApplied(node, + { inherited: false, filter: "user" }); + + const elementStyle = applied[0].rule; + is(elementStyle.cssText, localNode.style.cssText, "Got expected css text"); + + // Change an existing property... + await setProperty(elementStyle, 0, "color", "black"); + // Create a new property + await setProperty(elementStyle, 1, "background-color", "green"); + + // Create a new property and then change it immediately. + await setProperty(elementStyle, 2, "border", "1px solid black"); + await setProperty(elementStyle, 2, "border", "2px solid black"); + + is(elementStyle.cssText, + "color: black; background-color: green; border: 2px solid black;", + "Should have expected cssText"); + is(elementStyle.cssText, localNode.style.cssText, + "Local node and style front match."); + + // Remove all the properties + await removeProperty(elementStyle, 0, "color"); + await removeProperty(elementStyle, 0, "background-color"); + await removeProperty(elementStyle, 0, "border"); + + is(elementStyle.cssText, "", "Should have expected cssText"); + is(elementStyle.cssText, localNode.style.cssText, + "Local node and style front match."); + + runNextTest(); +}); + +async function setProperty(rule, index, name, value) { + const changes = rule.startModifyingProperties(isCssPropertyKnown); + changes.setProperty(index, name, value); + await changes.apply(); +} + +async function removeProperty(rule, index, name) { + const changes = rule.startModifyingProperties(isCssPropertyKnown); + changes.removeProperty(index, name); + await changes.apply(); +} + +addTest(function cleanup() { + gStyles = null; + gWalker = null; + gInspectee = null; + runNextTest(); +}); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_styles-svg.html b/devtools/server/tests/chrome/test_styles-svg.html new file mode 100644 index 0000000000..b03bc868b7 --- /dev/null +++ b/devtools/server/tests/chrome/test_styles-svg.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=921191 +Bug 921191 - allow inspection/editing of SVG elements' CSS properties +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="inspector-helpers.js"></script> + <script type="application/javascript"> +"use strict"; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + runNextTest(); +}; + +let gWalker = null; +let gStyles = null; + +addTest(async function setup() { + const url = document.getElementById("inspectorContent").href; + const { target } = await attachURL(url); + const inspector = await target.getFront("inspector"); + gWalker = inspector.walker; + gStyles = await inspector.getPageStyle(); + runNextTest(); +}); + +addTest(function inheritedUserStyles() { + promiseDone(gWalker.querySelector(gWalker.rootNode, "#svgcontent rect").then(node => { + return gStyles.getApplied(node, { inherited: true, filter: "user" }); + }).then(applied => { + is(applied.length, 2, "Should have 2 rules"); + is(applied[1].rule.cssText, "fill: rgb(1, 2, 3);", "cssText is right"); + }).then(runNextTest)); +}); + +addTest(function cleanup() { + gStyles = null; + gWalker = null; + runNextTest(); +}); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=921191">Mozilla Bug 921191</a> +<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_suspendTimeouts.html b/devtools/server/tests/chrome/test_suspendTimeouts.html new file mode 100644 index 0000000000..65a168986f --- /dev/null +++ b/devtools/server/tests/chrome/test_suspendTimeouts.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1426467 + +When we use windowUtils.resumeTimeouts to resume timeouts in a window, that call +should not immediately dispatch `onmessage` handlers for messages from workers. +--> +<head> + <meta charset="utf-8"> + <title>Mozilla Bug 1426467</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script src='test_suspendTimeouts.js'></script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_suspendTimeouts.js b/devtools/server/tests/chrome/test_suspendTimeouts.js new file mode 100644 index 0000000000..caea6936af --- /dev/null +++ b/devtools/server/tests/chrome/test_suspendTimeouts.js @@ -0,0 +1,139 @@ +"use strict"; + +// The debugger uses nsIDOMWindowUtils::suspendTimeouts and ...::resumeTimeouts +// to ensure that content event handlers do not run while a JavaScript +// invocation is stepping or paused at a breakpoint. If a worker thread sends +// messages to the content while the content is paused, those messages must not +// run until the JavaScript invocation interrupted by the debugger has completed. +// +// Bug 1426467 is that calling nsIDOMWindowUtils::resumeTimeouts actually +// delivers deferred messages itself, calling the content's 'onmessage' handler. +// But the debugger calls suspend/resume around each individual interruption of +// the debuggee -- each step, say -- meaning that hitting the "step into" button +// causes you to step from the debuggee directly into an onmessage handler, +// since the onmessage handler is the next function call the debugger sees. +// +// In other words, delivering deferred messages from resumeTimeouts, as it is +// used by the debugger, breaks the run-to-completion rule. They must not be +// delivered until after the JavaScript invocation at hand is complete. That's +// what this test checks. +// +// For this test to detect the bug, the following steps must take place in +// order: +// +// 1) The content page must call suspendTimeouts. +// 2) A runnable conveying a message from the worker thread must attempt to +// deliver the message, see that the content page has suspended such things, +// and hold the message for later delivery. +// 3) The content page must call resumeTimeouts. +// +// In a correct implementation, the message from the worker thread is delivered +// only after the main thread returns to the event loop after calling +// resumeTimeouts in step 3). In the buggy implementation, the onmessage handler +// is called directly from the call to resumeTimeouts, so that the onmessage +// handlers run in the midst of whatever JavaScript invocation resumed timeouts +// (say, stepping in the debugger), in violation of the run-to-completion rule. +// +// In this specific bug, the handlers are called from resumeTimeouts, but +// really, running them any time before that invocation returns to the main +// event loop would be a bug. +// +// Posting the message and calling resumeTimeouts take place in different +// threads, but if 2) and 3) don't occur in that order, the worker's message +// will never be delayed and the test will pass spuriously. But the worker +// can't communicate with the content page directly, to let it know that it +// should proceed with step 3): the purpose of suspendTimeouts is to pause +// all such communication. +// +// So instead, the content page creates a MessageChannel, and passes one +// MessagePort to the worker and the other to this mochitest (which has its +// own window, separate from the one calling suspendTimeouts). The worker +// notifies the mochitest when it has posted the message, and then the +// mochitest calls into the content to carry out step 3). + +// To help you follow all the callbacks and event handlers, this code pulls out +// event handler functions so that control flows from top to bottom. + +window.onload = function() { + // This mochitest is not complete until we call SimpleTest.finish. Don't just + // exit as soon as we return to the main event loop. + SimpleTest.waitForExplicitFinish(); + + const iframe = document.createElement("iframe"); + iframe.src = + "http://mochi.test:8888/chrome/devtools/server/tests/chrome/suspendTimeouts_content.html"; + iframe.onload = iframe_onload_handler; + document.body.appendChild(iframe); + + function iframe_onload_handler() { + const content = iframe.contentWindow.wrappedJSObject; + + const windowUtils = iframe.contentWindow.windowUtils; + + // Hand over the suspend and resume functions to the content page, along + // with some testing utilities. + content.suspendTimeouts = function() { + SimpleTest.info("test_suspendTimeouts", "calling suspendTimeouts"); + windowUtils.suspendTimeouts(); + }; + content.resumeTimeouts = function() { + windowUtils.resumeTimeouts(); + SimpleTest.info("test_suspendTimeouts", "resumeTimeouts called"); + }; + content.info = function(message) { + SimpleTest.info("suspendTimeouts_content.js", message); + }; + content.ok = SimpleTest.ok; + content.finish = finish; + + SimpleTest.info( + "Disappointed with National Tautology Day? Well, it is what it is." + ); + + // Once the worker has sent a message to its parent (which should get delayed), + // it sends us a message directly on this channel. + const workerPort = content.create_channel(); + workerPort.onmessage = handle_worker_echo; + + // Have content send the worker a message that it should echo back to both + // content and us. The echo to content should get delayed; the echo to us + // should cause our handle_worker_echo to be called. + content.start_worker(); + + function handle_worker_echo({ data }) { + info(`mochitest received message from worker: ${data}`); + + // As it turns out, it's not correct to assume that, if the worker posts a + // message to its parent via the global `postMessage` function, and then + // posts a message to the mochitest via the MessagePort, those two + // messages will be delivered in the order they were sent. + // + // - Messages sent via the worker's global's postMessage go through two + // ThrottledEventQueues (one in the worker, and one on the parent), and + // eventually find their way into the thread's primary event queue, + // which is a PrioritizedEventQueue. + // + // - Messages sent via a MessageChannel whose ports are owned by different + // threads are passed as IPDL messages. + // + // There's basically no reliable way to ensure that delivery to content + // has been attempted and the runnable deferred; there are too many + // variables affecting the order in which things are processed. Delaying + // for a second is the best I could think of. + // + // Fortunately, this tactic failing can only cause spurious test passes + // (the runnable never gets deferred, so things work by accident), not + // spurious failures. Without some kind of trustworthy notification that + // the runnable has been deferred, perhaps via some special white-box + // testing API, we can't do better. + setTimeout(() => { + content.resume_timeouts(); + }, 1000); + } + + function finish(message) { + SimpleTest.info("suspendTimeouts_content.js", "called finish"); + SimpleTest.finish(); + } + } +}; diff --git a/devtools/server/tests/chrome/test_unsafeDereference.html b/devtools/server/tests/chrome/test_unsafeDereference.html new file mode 100644 index 0000000000..72069572bd --- /dev/null +++ b/devtools/server/tests/chrome/test_unsafeDereference.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=837723 + +When we use Debugger.Object.prototype.unsafeDereference to get a non-D.O +reference to a content object in chrome, that reference should be via an +xray wrapper. +--> +<head> + <meta charset="utf-8"> + <title>Mozilla Bug 837723</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script> +"use strict"; + +const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm"); +addDebuggerToGlobal(this); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + const iframe = document.createElement("iframe"); + iframe.src = "http://mochi.test:8888/chrome/devtools/server/tests/chrome/nonchrome_unsafeDereference.html"; + + iframe.onload = function() { + const dbg = new Debugger(); + const contentDO = dbg.addDebuggee(iframe.contentWindow); + const xhrDesc = contentDO.getOwnPropertyDescriptor("xhr"); + + isnot(xhrDesc, undefined, "xhr should be visible as property of content global"); + isnot(xhrDesc.value, undefined, "xhr should have a value"); + + const xhr = xhrDesc.value.unsafeDereference(); + + is(typeof xhr, "object", "we should be able to deference xhr's value's D.O"); + is(xhr.timeout, 1742, "chrome should see the xhr's 'timeout' property"); + is(xhr.expando, undefined, "chrome should not see the xhr's 'expando' property"); + + SimpleTest.finish(); + }; + + document.body.appendChild(iframe); +}; + +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_webconsole-node-grip.html b/devtools/server/tests/chrome/test_webconsole-node-grip.html new file mode 100644 index 0000000000..7b0b2d1624 --- /dev/null +++ b/devtools/server/tests/chrome/test_webconsole-node-grip.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>DOMNode Object actor test</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="webconsole-helpers.js"></script> + <script> +"use strict"; + +const TEST_URL = "data:text/html,<html><body>Hello</body></html>"; + +window.onload = async function() { + SimpleTest.waitForExplicitFinish(); + + try { + const { + webConsoleFront, + } = await attachURL(TEST_URL); + await testNotInTreeElementNode(webConsoleFront); + await testInTreeElementNode(webConsoleFront); + await testNotInTreeTextNode(webConsoleFront); + await testInTreeTextNode(webConsoleFront); + } catch (e) { + ok(false, `Error thrown: ${e.message}`); + } + SimpleTest.finish(); +}; + +async function testNotInTreeElementNode(webConsoleFront) { + info("Testing isConnected property on a ElementNode not in the DOM tree"); + const {result} = await webConsoleFront.evaluateJSAsync("document.createElement(\"div\")"); + is(result.getGrip().preview.isConnected, false, + "isConnected is false since we only created the element"); +} + +async function testInTreeElementNode(webConsoleFront) { + info("Testing isConnected property on a ElementNode in the DOM tree"); + const {result} = await webConsoleFront.evaluateJSAsync("document.body"); + is(result.getGrip().preview.isConnected, true, + "isConnected is true as expected, since the element was retrieved from the DOM tree"); +} + +async function testNotInTreeTextNode(webConsoleFront) { + info("Testing isConnected property on a TextNode not in the DOM tree"); + const {result} = await webConsoleFront.evaluateJSAsync("document.createTextNode(\"Hello\")"); + is(result.getGrip().preview.isConnected, false, + "isConnected is false since we only created the element"); +} + +async function testInTreeTextNode(webConsoleFront) { + info("Testing isConnected property on a TextNode in the DOM tree"); + const {result} = await webConsoleFront.evaluateJSAsync("document.body.firstChild"); + is(result.getGrip().preview.isConnected, true, + "isConnected is true as expected, since the element was retrieved from the DOM tree"); +} + + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_webextension-addon-debugging-connect.html b/devtools/server/tests/chrome/test_webextension-addon-debugging-connect.html new file mode 100644 index 0000000000..d983d1bbb8 --- /dev/null +++ b/devtools/server/tests/chrome/test_webextension-addon-debugging-connect.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1302702 - Test connect to a webextension addon +--> +<head> + <meta charset="utf-8"> + <title>Mozilla Bug</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script src="webextension-helpers.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +add_task(async function test_webextension_addon_debugging_connect() { + // Install and start a test webextension. + const extension = ExtensionTestUtils.loadExtension({ + useAddonManager: "temporary", + background: function() { + /* eslint-disable no-undef */ + browser.test.log("background script executed"); + browser.test.sendMessage("background page ready"); + /* eslint-enable no-undef */ + }, + }); + await extension.startup(); + await extension.awaitMessage("background page ready"); + + // Connect a DevToolsClient. + const transport = DevToolsServer.connectPipe(); + const client = new DevToolsClient(transport); + await client.connect(); + + // List addons and assertions on the expected addon actor. + const addonDescriptor = await client.mainRoot.getAddon({ id: extension.id }); + ok(addonDescriptor, "The expected webextension addon actor has been found"); + + const addonTarget = await addonDescriptor.getTarget(); + ok(addonTarget, "The expected webextension target addon actor has been found"); + + // Connect to the target addon actor and wait for the updated list of frames. + is(addonDescriptor.targetForm.isOOP, true, + "Got the expected oop mode in the webextension actor form"); + const frames = await waitForFramesUpdated(addonTarget); + const backgroundPageFrame = frames.filter((frame) => { + return frame.url && frame.url.endsWith("/_generated_background_page.html"); + }).pop(); + is(backgroundPageFrame.addonID, extension.id, "Got an extension frame"); + + // When running in oop mode we can explicitly attach the thread without locking + // the main process. + const threadFront = await addonTarget.attachThread(); + + ok(threadFront, "Got a threadFront for the target addon"); + is(threadFront.paused, false, "The addon threadActor isn't paused"); + + const waitTransportClosed = new Promise(resolve => { + client._transport.once("close", resolve); + }); + + await addonTarget.destroy(); + await client.close(); + + // Check that if we close the debugging client without uninstalling the addon, + // the webextension debugging actor should release the debug browser. + await waitTransportClosed; + is(ExtensionParent.DebugUtils.debugBrowserPromises.size, 0, + "The debug browser has been released when the RDP connection has been closed"); + + await extension.unload(); +}); + +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/test_webextension-addon-debugging-reload.html b/devtools/server/tests/chrome/test_webextension-addon-debugging-reload.html new file mode 100644 index 0000000000..420cb0df5e --- /dev/null +++ b/devtools/server/tests/chrome/test_webextension-addon-debugging-reload.html @@ -0,0 +1,122 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 1302702 - Test connect to a webextension addon +--> +<head> + <meta charset="utf-8"> + <title>Mozilla Bug</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="webextension-helpers.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +// NOTE: This test installs the webextension addon using the addon manager, so that +// it can be reloaded using the same actor RDP method used by the developer tools. +add_task(async function test_webextension_addon_debugging_reload() { + const addonID = "test-webext-debugging-reload@test.mozilla.com"; + const addonFile = generateWebExtensionXPI({ + manifest: { + applications: { + gecko: {id: addonID}, + }, + background: { scripts: ["background.js"] }, + }, + files: { + "background.js": function() { + console.log("background page loaded"); + }, + }, + }); + + const {addon} = await promiseInstallFile(addonFile); + await promiseWebExtensionStartup(); + + const addonTarget = await attachAddon(addonID); + ok(addonTarget, "Got an addon target"); + + const matchBackgroundPageFrame = (data) => { + if (data.frames) { + const frameFound = data.frames.filter((frame) => { + return frame.url && frame.url.endsWith("_generated_background_page.html"); + }).pop(); + + return !!frameFound; + } + + return false; + }; + + const matchFrameSelected = (data) => { + return "selected" in data; + }; + + // Test the target addon actor reload behavior. + + let waitFramesUpdated = waitForFramesUpdated(addonTarget, matchBackgroundPageFrame); + let collectFrameSelected = collectFrameUpdates(addonTarget, matchFrameSelected); + + is(ExtensionParent.DebugUtils.debugBrowserPromises.size, 1, + "The expected number of debug browser has been created by the addon actor"); + + info("Reloading the target addon"); + reloadAddon(addonTarget, addonID); + await promiseWebExtensionStartup(); + + is(ExtensionParent.DebugUtils.debugBrowserPromises.size, 1, + "The number of debug browser has not been changed after an addon reload"); + + let frames = await waitFramesUpdated; + const selectedFrame = collectFrameSelected().pop(); + + is(selectedFrame.selected, frames[0].id, "The new background page has been selected"); + is(addonTarget.url, frames[0].url, + "The addon target has the url of the selected frame"); + + // Test the target addon actor once reloaded twice. + + waitFramesUpdated = waitForFramesUpdated(addonTarget, matchBackgroundPageFrame); + collectFrameSelected = collectFrameUpdates(addonTarget, matchFrameSelected); + + info("Reloading the target addon again"); + reloadAddon(addonTarget, addonID); + await promiseWebExtensionStartup(); + + frames = await waitFramesUpdated; + const newSelectedFrame = collectFrameSelected().pop(); + + ok(newSelectedFrame !== selectedFrame, + "The new selected frame is different from the previous on"); + is(newSelectedFrame.selected, frames[0].id, + "The new background page has been selected on the second reload"); + is(addonTarget.url, frames[0].url, + "The addon target has the url of the selected frame"); + + // Expect the target to be automatically closed when the addon + // is finally uninstalled. + + const {client} = addonTarget; + const waitDebuggingClientClosed = new Promise(resolve => { + addonTarget.once("close", resolve); + }); + + const waitShutdown = promiseWebExtensionShutdown(); + addon.uninstall(); + await waitShutdown; + + info("Waiting the addon target to be closed on addon uninstall"); + await waitDebuggingClientClosed; + + // Debugging client has to be closed explicitly when + // the target has been created as remote. + await client.close(); +}); + +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/chrome/webconsole-helpers.js b/devtools/server/tests/chrome/webconsole-helpers.js new file mode 100644 index 0000000000..4236cba16b --- /dev/null +++ b/devtools/server/tests/chrome/webconsole-helpers.js @@ -0,0 +1,58 @@ +/* exported attachURL, evaluateJS */ +"use strict"; + +const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm"); +const { DevToolsServer } = require("devtools/server/devtools-server"); +const { TargetFactory } = require("devtools/client/framework/target"); + +const Services = require("Services"); + +// Always log packets when running tests. +Services.prefs.setBoolPref("devtools.debugger.log", true); +SimpleTest.registerCleanupFunction(function() { + Services.prefs.clearUserPref("devtools.debugger.log"); +}); + +if (!DevToolsServer.initialized) { + DevToolsServer.init(); + DevToolsServer.registerAllActors(); + SimpleTest.registerCleanupFunction(function() { + DevToolsServer.destroy(); + }); +} + +/** + * Open a tab, load the url, find the tab with the devtools server, + * and attach the console to it. + * + * @param {string} url : url to navigate to + * @return {Promise} Promise resolving when the console is attached. + * The Promise resolves with an object containing : + * - tab: the attached tab + * - targetFront: the target front + * - webConsoleFront: the console front + * - cleanup: a generator function which can be called to close + * the opened tab and disconnect its devtools client. + */ +async function attachURL(url) { + const tab = await addTab(url); + const target = await TargetFactory.forTab(tab); + await target.attach(); + const webConsoleFront = await target.getFront("console"); + return { + webConsoleFront, + }; +} + +/** + * Naive implementaion of addTab working from a mochitest-chrome test. + */ +async function addTab(url) { + const { gBrowser } = Services.wm.getMostRecentWindow("navigator:browser"); + const { + BrowserTestUtils, + } = require("resource://testing-common/BrowserTestUtils.jsm"); + const tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url)); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + return tab; +} diff --git a/devtools/server/tests/chrome/webextension-helpers.js b/devtools/server/tests/chrome/webextension-helpers.js new file mode 100644 index 0000000000..98f8efd900 --- /dev/null +++ b/devtools/server/tests/chrome/webextension-helpers.js @@ -0,0 +1,138 @@ +/* exported attachAddon, waitForFramesUpdated, reloadAddon, + collectFrameUpdates, generateWebExtensionXPI, promiseInstallFile, + promiseWebExtensionStartup, promiseWebExtensionShutdown + */ + +"use strict"; + +const { require, loader } = ChromeUtils.import( + "resource://devtools/shared/Loader.jsm" +); +const { DevToolsClient } = require("devtools/client/devtools-client"); +const { DevToolsServer } = require("devtools/server/devtools-server"); + +const { + AddonTestUtils, +} = require("resource://testing-common/AddonTestUtils.jsm"); +const { + ExtensionTestCommon, +} = require("resource://testing-common/ExtensionTestCommon.jsm"); + +loader.lazyImporter( + this, + "ExtensionParent", + "resource://gre/modules/ExtensionParent.jsm" +); + +// Initialize a minimal DevToolsServer and connect to the webextension addon actor. +if (!DevToolsServer.initialized) { + DevToolsServer.init(); + DevToolsServer.registerAllActors(); + SimpleTest.registerCleanupFunction(function() { + DevToolsServer.destroy(); + }); +} + +SimpleTest.registerCleanupFunction(function() { + const { hiddenXULWindow } = ExtensionParent.DebugUtils; + const debugBrowserMapSize = + ExtensionParent.DebugUtils.debugBrowserPromises.size; + + if (debugBrowserMapSize > 0) { + is( + debugBrowserMapSize, + 0, + "ExtensionParent DebugUtils debug browsers have not been released" + ); + } + + if (hiddenXULWindow) { + ok( + false, + "ExtensionParent DebugUtils hiddenXULWindow has not been destroyed" + ); + } +}); + +// Test helpers related to the webextensions debugging RDP actors. + +function waitForFramesUpdated(target, matchFn) { + return new Promise(resolve => { + const listener = data => { + if (typeof matchFn === "function" && !matchFn(data)) { + return; + } else if (!data.frames) { + return; + } + + target.off("frameUpdate", listener); + resolve(data.frames); + }; + target.on("frameUpdate", listener); + }); +} + +function collectFrameUpdates({ client }, matchFn) { + const collected = []; + + const listener = data => { + if (matchFn(data)) { + collected.push(data); + } + }; + + client.on("frameUpdate", listener); + let unsubscribe = () => { + unsubscribe = null; + client.off("frameUpdate", listener); + return collected; + }; + + SimpleTest.registerCleanupFunction(function() { + if (unsubscribe) { + unsubscribe(); + } + }); + + return unsubscribe; +} + +async function attachAddon(addonId) { + const transport = DevToolsServer.connectPipe(); + const client = new DevToolsClient(transport); + + await client.connect(); + + const addonDescriptor = await client.mainRoot.getAddon({ id: addonId }); + const addonTarget = await addonDescriptor.getTarget(); + + if (!addonTarget) { + client.close(); + throw new Error(`No WebExtension Actor found for ${addonId}`); + } + + return addonTarget; +} + +async function reloadAddon({ client }, addonId) { + const addonTargetFront = await client.mainRoot.getAddon({ id: addonId }); + + if (!addonTargetFront) { + client.close(); + throw new Error(`No WebExtension Actor found for ${addonId}`); + } + + await addonTargetFront.reload(); +} + +// Test helpers related to the AddonManager. + +function generateWebExtensionXPI(extDetails) { + return ExtensionTestCommon.generateXPI(extDetails); +} + +let { + promiseInstallFile, + promiseWebExtensionStartup, + promiseWebExtensionShutdown, +} = AddonTestUtils; |