зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound
This commit is contained in:
Коммит
199d6e3b27
|
@ -113,6 +113,7 @@ devtools/server/**
|
|||
!devtools/server/actors/webbrowser.js
|
||||
!devtools/server/actors/styles.js
|
||||
!devtools/server/actors/string.js
|
||||
!devtools/server/actors/csscoverage.js
|
||||
devtools/shared/*.js
|
||||
!devtools/shared/css-lexer.js
|
||||
!devtools/shared/defer.js
|
||||
|
|
|
@ -99,7 +99,6 @@ function* addTabWithToolbarRunTests(win) {
|
|||
filename: { value: "" + file.path },
|
||||
fullpage: { value: false },
|
||||
clipboard: { value: false },
|
||||
chrome: { value: false },
|
||||
},
|
||||
},
|
||||
exec: {
|
||||
|
@ -123,7 +122,6 @@ function* addTabWithToolbarRunTests(win) {
|
|||
check: {
|
||||
args: {
|
||||
clipboard: { value: true },
|
||||
chrome: { value: false },
|
||||
},
|
||||
},
|
||||
exec: {
|
||||
|
@ -143,7 +141,6 @@ function* addTabWithToolbarRunTests(win) {
|
|||
args: {
|
||||
fullpage: { value: true },
|
||||
clipboard: { value: true },
|
||||
chrome: { value: false },
|
||||
},
|
||||
},
|
||||
exec: {
|
||||
|
@ -166,7 +163,6 @@ function* addTabWithToolbarRunTests(win) {
|
|||
check: {
|
||||
args: {
|
||||
clipboard: { value: true },
|
||||
chrome: { value: false },
|
||||
},
|
||||
},
|
||||
exec: {
|
||||
|
@ -215,7 +211,6 @@ function* addTabWithToolbarRunTests(win) {
|
|||
check: {
|
||||
args: {
|
||||
clipboard: { value: true },
|
||||
chrome: { value: false },
|
||||
},
|
||||
},
|
||||
exec: {
|
||||
|
@ -239,7 +234,6 @@ function* addTabWithToolbarRunTests(win) {
|
|||
args: {
|
||||
fullpage: { value: true },
|
||||
clipboard: { value: true },
|
||||
chrome: { value: false },
|
||||
},
|
||||
},
|
||||
exec: {
|
||||
|
@ -264,7 +258,6 @@ function* addTabWithToolbarRunTests(win) {
|
|||
check: {
|
||||
args: {
|
||||
clipboard: { value: true },
|
||||
chrome: { value: false },
|
||||
},
|
||||
},
|
||||
exec: {
|
||||
|
|
|
@ -19,19 +19,16 @@ add_task(function* () {
|
|||
|
||||
function* addNewRuleFromContextMenu(inspector, view) {
|
||||
info("Waiting for context menu to be shown");
|
||||
let onPopup = once(view._contextmenu._menupopup, "popupshown");
|
||||
let win = view.styleWindow;
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(view.element,
|
||||
{button: 2, type: "contextmenu"}, win);
|
||||
yield onPopup;
|
||||
let allMenuItems = openStyleContextMenuAndGetAllItems(view, view.element);
|
||||
let menuitemAddRule = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.addNewRule"));
|
||||
|
||||
ok(!view._contextmenu.menuitemAddRule.hidden, "Add rule is visible");
|
||||
ok(menuitemAddRule.visible, "Add rule is visible");
|
||||
|
||||
info("Adding the new rule and expecting a ruleview-changed event");
|
||||
let onRuleViewChanged = view.once("ruleview-changed");
|
||||
view._contextmenu.menuitemAddRule.click();
|
||||
view._contextmenu._menupopup.hidePopup();
|
||||
menuitemAddRule.click();
|
||||
yield onRuleViewChanged;
|
||||
}
|
||||
|
||||
|
|
|
@ -58,11 +58,12 @@ function* testMdnContextMenuItemVisibility(view) {
|
|||
let root = rootElement(view);
|
||||
for (let node of iterateNodes(root)) {
|
||||
info("Setting " + node + " as popupNode");
|
||||
view.styleDocument.popupNode = node;
|
||||
info("Creating context menu with " + node + " as popupNode");
|
||||
let allMenuItems = openStyleContextMenuAndGetAllItems(view, node);
|
||||
let menuitemShowMdnDocs = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.showMdnDocs"));
|
||||
|
||||
info("Updating context menu state");
|
||||
view._contextmenu._updateMenuItems();
|
||||
let isVisible = !view._contextmenu.menuitemShowMdnDocs.hidden;
|
||||
let isVisible = menuitemShowMdnDocs.visible;
|
||||
let shouldBeVisible = isPropertyNameNode(node);
|
||||
let message = shouldBeVisible ? "shown" : "hidden";
|
||||
is(isVisible, shouldBeVisible,
|
||||
|
|
|
@ -46,14 +46,15 @@ function* testShowMdnTooltip(view) {
|
|||
|
||||
let {nameSpan} = getRuleViewProperty(view, "element", PROPERTYNAME);
|
||||
|
||||
view.styleDocument.popupNode = nameSpan.firstChild;
|
||||
view._contextmenu._updateMenuItems();
|
||||
let allMenuItems = openStyleContextMenuAndGetAllItems(view, nameSpan.firstChild);
|
||||
let menuitemShowMdnDocs = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.showMdnDocs"));
|
||||
|
||||
let cssDocs = view.tooltips.cssDocs;
|
||||
|
||||
info("Showing the MDN docs tooltip");
|
||||
let onShown = cssDocs.tooltip.once("shown");
|
||||
view._contextmenu.menuitemShowMdnDocs.click();
|
||||
menuitemShowMdnDocs.click();
|
||||
yield onShown;
|
||||
ok(true, "The MDN docs tooltip was shown");
|
||||
}
|
||||
|
|
|
@ -103,11 +103,11 @@ function* testMdnContextMenuItemVisibility(view, shouldBeVisible) {
|
|||
info("Set a CSS property name as popupNode");
|
||||
let root = rootElement(view);
|
||||
let node = root.querySelector("." + PROPERTY_NAME_CLASS).firstChild;
|
||||
view.styleDocument.popupNode = node;
|
||||
let allMenuItems = openStyleContextMenuAndGetAllItems(view, node);
|
||||
let menuitemShowMdnDocs = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.showMdnDocs"));
|
||||
|
||||
info("Update context menu state");
|
||||
view._contextmenu._updateMenuItems();
|
||||
let isVisible = !view._contextmenu.menuitemShowMdnDocs.hidden;
|
||||
let isVisible = menuitemShowMdnDocs.visible;
|
||||
is(isVisible, shouldBeVisible,
|
||||
"The MDN context menu item is " + message);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ const TEST_URI = URL_ROOT + "doc_copystyles.html";
|
|||
add_task(function* () {
|
||||
yield addTab(TEST_URI);
|
||||
let { inspector, view } = yield openRuleView();
|
||||
let contextmenu = view._contextmenu;
|
||||
yield selectNode("#testid", inspector);
|
||||
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
@ -27,77 +26,77 @@ add_task(function* () {
|
|||
{
|
||||
desc: "Test Copy Property Name",
|
||||
node: ruleEditor.rule.textProps[0].editor.nameSpan,
|
||||
menuItem: contextmenu.menuitemCopyPropertyName,
|
||||
menuItemLabel: "styleinspector.contextmenu.copyPropertyName",
|
||||
expectedPattern: "color",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
visible: {
|
||||
copyLocation: false,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: false,
|
||||
copySelector: false,
|
||||
copyRule: true
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Property Value",
|
||||
node: ruleEditor.rule.textProps[2].editor.valueSpan,
|
||||
menuItem: contextmenu.menuitemCopyPropertyValue,
|
||||
menuItemLabel: "styleinspector.contextmenu.copyPropertyValue",
|
||||
expectedPattern: "12px",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: false,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
visible: {
|
||||
copyLocation: false,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: false,
|
||||
copyRule: true
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Property Value with Priority",
|
||||
node: ruleEditor.rule.textProps[3].editor.valueSpan,
|
||||
menuItem: contextmenu.menuitemCopyPropertyValue,
|
||||
menuItemLabel: "styleinspector.contextmenu.copyPropertyValue",
|
||||
expectedPattern: "#00F !important",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: false,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
visible: {
|
||||
copyLocation: false,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: false,
|
||||
copyRule: true
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Property Declaration",
|
||||
node: ruleEditor.rule.textProps[2].editor.nameSpan,
|
||||
menuItem: contextmenu.menuitemCopyPropertyDeclaration,
|
||||
menuItemLabel: "styleinspector.contextmenu.copyPropertyDeclaration",
|
||||
expectedPattern: "font-size: 12px;",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
visible: {
|
||||
copyLocation: false,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: false,
|
||||
copySelector: false,
|
||||
copyRule: true
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Property Declaration with Priority",
|
||||
node: ruleEditor.rule.textProps[3].editor.nameSpan,
|
||||
menuItem: contextmenu.menuitemCopyPropertyDeclaration,
|
||||
menuItemLabel: "styleinspector.contextmenu.copyPropertyDeclaration",
|
||||
expectedPattern: "border-color: #00F !important;",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
visible: {
|
||||
copyLocation: false,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: false,
|
||||
copySelector: false,
|
||||
copyRule: true
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Rule",
|
||||
node: ruleEditor.rule.textProps[2].editor.nameSpan,
|
||||
menuItem: contextmenu.menuitemCopyRule,
|
||||
menuItemLabel: "styleinspector.contextmenu.copyRule",
|
||||
expectedPattern: "#testid {[\\r\\n]+" +
|
||||
"\tcolor: #F00;[\\r\\n]+" +
|
||||
"\tbackground-color: #00F;[\\r\\n]+" +
|
||||
|
@ -105,42 +104,42 @@ add_task(function* () {
|
|||
"\tborder-color: #00F !important;[\\r\\n]+" +
|
||||
"\t--var: \"\\*/\";[\\r\\n]+" +
|
||||
"}",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
visible: {
|
||||
copyLocation: false,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: false,
|
||||
copySelector: false,
|
||||
copyRule: true
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Selector",
|
||||
node: ruleEditor.selectorText,
|
||||
menuItem: contextmenu.menuitemCopySelector,
|
||||
menuItemLabel: "styleinspector.contextmenu.copySelector",
|
||||
expectedPattern: "html, body, #testid",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: true,
|
||||
copySelector: false,
|
||||
copyRule: false
|
||||
visible: {
|
||||
copyLocation: false,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: false,
|
||||
copySelector: true,
|
||||
copyRule: true
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Location",
|
||||
node: ruleEditor.source,
|
||||
menuItem: contextmenu.menuitemCopyLocation,
|
||||
menuItemLabel: "styleinspector.contextmenu.copyLocation",
|
||||
expectedPattern: "http://example.com/browser/devtools/client/" +
|
||||
"inspector/rules/test/doc_copystyles.css",
|
||||
hidden: {
|
||||
copyLocation: false,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
visible: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: false,
|
||||
copySelector: false,
|
||||
copyRule: true
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -149,7 +148,7 @@ add_task(function* () {
|
|||
},
|
||||
desc: "Test Copy Rule with Disabled Property",
|
||||
node: ruleEditor.rule.textProps[2].editor.nameSpan,
|
||||
menuItem: contextmenu.menuitemCopyRule,
|
||||
menuItemLabel: "styleinspector.contextmenu.copyRule",
|
||||
expectedPattern: "#testid {[\\r\\n]+" +
|
||||
"\t\/\\* color: #F00; \\*\/[\\r\\n]+" +
|
||||
"\tbackground-color: #00F;[\\r\\n]+" +
|
||||
|
@ -157,13 +156,13 @@ add_task(function* () {
|
|||
"\tborder-color: #00F !important;[\\r\\n]+" +
|
||||
"\t--var: \"\\*/\";[\\r\\n]+" +
|
||||
"}",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
visible: {
|
||||
copyLocation: false,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: false,
|
||||
copySelector: false,
|
||||
copyRule: true
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -172,7 +171,7 @@ add_task(function* () {
|
|||
},
|
||||
desc: "Test Copy Rule with Disabled Property with Comment",
|
||||
node: ruleEditor.rule.textProps[2].editor.nameSpan,
|
||||
menuItem: contextmenu.menuitemCopyRule,
|
||||
menuItemLabel: "styleinspector.contextmenu.copyRule",
|
||||
expectedPattern: "#testid {[\\r\\n]+" +
|
||||
"\t\/\\* color: #F00; \\*\/[\\r\\n]+" +
|
||||
"\tbackground-color: #00F;[\\r\\n]+" +
|
||||
|
@ -180,81 +179,94 @@ add_task(function* () {
|
|||
"\tborder-color: #00F !important;[\\r\\n]+" +
|
||||
"\t/\\* --var: \"\\*\\\\\/\"; \\*\/[\\r\\n]+" +
|
||||
"}",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
visible: {
|
||||
copyLocation: false,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: false,
|
||||
copySelector: false,
|
||||
copyRule: true
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Property Declaration with Disabled Property",
|
||||
node: ruleEditor.rule.textProps[0].editor.nameSpan,
|
||||
menuItem: contextmenu.menuitemCopyPropertyDeclaration,
|
||||
menuItemLabel: "styleinspector.contextmenu.copyPropertyDeclaration",
|
||||
expectedPattern: "\/\\* color: #F00; \\*\/",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
visible: {
|
||||
copyLocation: false,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: false,
|
||||
copySelector: false,
|
||||
copyRule: true
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
for (let { setup, desc, node, menuItem, expectedPattern, hidden } of data) {
|
||||
for (let { setup, desc, node, menuItemLabel, expectedPattern, visible } of data) {
|
||||
if (setup) {
|
||||
yield setup();
|
||||
}
|
||||
|
||||
info(desc);
|
||||
yield checkCopyStyle(view, node, menuItem, expectedPattern, hidden);
|
||||
yield checkCopyStyle(view, node, menuItemLabel, expectedPattern, visible);
|
||||
}
|
||||
});
|
||||
|
||||
function* checkCopyStyle(view, node, menuItem, expectedPattern, hidden) {
|
||||
let onPopup = once(view._contextmenu._menupopup, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(node,
|
||||
{button: 2, type: "contextmenu"}, view.styleWindow);
|
||||
yield onPopup;
|
||||
function* checkCopyStyle(view, node, menuItemLabel, expectedPattern, visible) {
|
||||
let allMenuItems = openStyleContextMenuAndGetAllItems(view, node);
|
||||
let menuItem = allMenuItems.find(item =>
|
||||
item.label === _STRINGS.GetStringFromName(menuItemLabel));
|
||||
let menuitemCopy = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copy"));
|
||||
let menuitemCopyLocation = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copyLocation"));
|
||||
let menuitemCopyPropertyDeclaration = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copyPropertyDeclaration"));
|
||||
let menuitemCopyPropertyName = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copyPropertyName"));
|
||||
let menuitemCopyPropertyValue = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copyPropertyValue"));
|
||||
let menuitemCopySelector = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copySelector"));
|
||||
let menuitemCopyRule = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copyRule"));
|
||||
|
||||
ok(view._contextmenu.menuitemCopy.disabled,
|
||||
ok(menuitemCopy.disabled,
|
||||
"Copy disabled is as expected: true");
|
||||
ok(!view._contextmenu.menuitemCopy.hidden,
|
||||
"Copy hidden is as expected: false");
|
||||
ok(menuitemCopy.visible,
|
||||
"Copy visible is as expected: true");
|
||||
|
||||
is(view._contextmenu.menuitemCopyLocation.hidden,
|
||||
hidden.copyLocation,
|
||||
"Copy Location hidden attribute is as expected: " +
|
||||
hidden.copyLocation);
|
||||
is(menuitemCopyLocation.visible,
|
||||
visible.copyLocation,
|
||||
"Copy Location visible attribute is as expected: " +
|
||||
visible.copyLocation);
|
||||
|
||||
is(view._contextmenu.menuitemCopyPropertyDeclaration.hidden,
|
||||
hidden.copyPropertyDeclaration,
|
||||
"Copy Property Declaration hidden attribute is as expected: " +
|
||||
hidden.copyPropertyDeclaration);
|
||||
is(menuitemCopyPropertyDeclaration.visible,
|
||||
visible.copyPropertyDeclaration,
|
||||
"Copy Property Declaration visible attribute is as expected: " +
|
||||
visible.copyPropertyDeclaration);
|
||||
|
||||
is(view._contextmenu.menuitemCopyPropertyName.hidden,
|
||||
hidden.copyPropertyName,
|
||||
"Copy Property Name hidden attribute is as expected: " +
|
||||
hidden.copyPropertyName);
|
||||
is(menuitemCopyPropertyName.visible,
|
||||
visible.copyPropertyName,
|
||||
"Copy Property Name visible attribute is as expected: " +
|
||||
visible.copyPropertyName);
|
||||
|
||||
is(view._contextmenu.menuitemCopyPropertyValue.hidden,
|
||||
hidden.copyPropertyValue,
|
||||
"Copy Property Value hidden attribute is as expected: " +
|
||||
hidden.copyPropertyValue);
|
||||
is(menuitemCopyPropertyValue.visible,
|
||||
visible.copyPropertyValue,
|
||||
"Copy Property Value visible attribute is as expected: " +
|
||||
visible.copyPropertyValue);
|
||||
|
||||
is(view._contextmenu.menuitemCopySelector.hidden,
|
||||
hidden.copySelector,
|
||||
"Copy Selector hidden attribute is as expected: " +
|
||||
hidden.copySelector);
|
||||
is(menuitemCopySelector.visible,
|
||||
visible.copySelector,
|
||||
"Copy Selector visible attribute is as expected: " +
|
||||
visible.copySelector);
|
||||
|
||||
is(view._contextmenu.menuitemCopyRule.hidden,
|
||||
hidden.copyRule,
|
||||
"Copy Rule hidden attribute is as expected: " +
|
||||
hidden.copyRule);
|
||||
is(menuitemCopyRule.visible,
|
||||
visible.copyRule,
|
||||
"Copy Rule visible attribute is as expected: " +
|
||||
visible.copyRule);
|
||||
|
||||
try {
|
||||
yield waitForClipboard(() => menuItem.click(),
|
||||
|
@ -262,8 +274,6 @@ function* checkCopyStyle(view, node, menuItem, expectedPattern, hidden) {
|
|||
} catch (e) {
|
||||
failedClipboard(expectedPattern);
|
||||
}
|
||||
|
||||
view._contextmenu._menupopup.hidePopup();
|
||||
}
|
||||
|
||||
function* disableProperty(view, index) {
|
||||
|
|
|
@ -69,29 +69,25 @@ function* checkCopySelection(view) {
|
|||
"html {[\\r\\n]+" +
|
||||
" color: #000000;[\\r\\n]*";
|
||||
|
||||
let onPopup = once(view._contextmenu._menupopup, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(prop,
|
||||
{button: 2, type: "contextmenu"}, win);
|
||||
yield onPopup;
|
||||
let allMenuItems = openStyleContextMenuAndGetAllItems(view, prop);
|
||||
let menuitemCopy = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copy"));
|
||||
|
||||
ok(!view._contextmenu.menuitemCopy.hidden,
|
||||
ok(menuitemCopy.visible,
|
||||
"Copy menu item is displayed as expected");
|
||||
|
||||
try {
|
||||
yield waitForClipboard(() => view._contextmenu.menuitemCopy.click(),
|
||||
yield waitForClipboard(() => menuitemCopy.click(),
|
||||
() => checkClipboardData(expectedPattern));
|
||||
} catch (e) {
|
||||
failedClipboard(expectedPattern);
|
||||
}
|
||||
|
||||
view._contextmenu._menupopup.hidePopup();
|
||||
}
|
||||
|
||||
function* checkSelectAll(view) {
|
||||
info("Testing select-all copy");
|
||||
|
||||
let contentDoc = view.styleDocument;
|
||||
let win = view.styleWindow;
|
||||
let prop = contentDoc.querySelector(".ruleview-property");
|
||||
|
||||
info("Checking that _SelectAll() then copy returns the correct " +
|
||||
|
@ -107,28 +103,24 @@ function* checkSelectAll(view) {
|
|||
" color: #000000;[\\r\\n]+" +
|
||||
"}[\\r\\n]*";
|
||||
|
||||
let onPopup = once(view._contextmenu._menupopup, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(prop,
|
||||
{button: 2, type: "contextmenu"}, win);
|
||||
yield onPopup;
|
||||
let allMenuItems = openStyleContextMenuAndGetAllItems(view, prop);
|
||||
let menuitemCopy = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copy"));
|
||||
|
||||
ok(!view._contextmenu.menuitemCopy.hidden,
|
||||
ok(menuitemCopy.visible,
|
||||
"Copy menu item is displayed as expected");
|
||||
|
||||
try {
|
||||
yield waitForClipboard(() => view._contextmenu.menuitemCopy.click(),
|
||||
yield waitForClipboard(() => menuitemCopy.click(),
|
||||
() => checkClipboardData(expectedPattern));
|
||||
} catch (e) {
|
||||
failedClipboard(expectedPattern);
|
||||
}
|
||||
|
||||
view._contextmenu._menupopup.hidePopup();
|
||||
}
|
||||
|
||||
function* checkCopyEditorValue(view) {
|
||||
info("Testing CSS property editor value copy");
|
||||
|
||||
let win = view.styleWindow;
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 0);
|
||||
let propEditor = ruleEditor.rule.textProps[0].editor;
|
||||
|
||||
|
@ -139,28 +131,19 @@ function* checkCopyEditorValue(view) {
|
|||
|
||||
let expectedPattern = "10em";
|
||||
|
||||
let onPopup = once(view._contextmenu._menupopup, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(editor.input,
|
||||
{button: 2, type: "contextmenu"}, win);
|
||||
yield onPopup;
|
||||
let allMenuItems = openStyleContextMenuAndGetAllItems(view, editor.input);
|
||||
let menuitemCopy = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copy"));
|
||||
|
||||
ok(!view._contextmenu.menuitemCopy.hidden,
|
||||
ok(menuitemCopy.visible,
|
||||
"Copy menu item is displayed as expected");
|
||||
|
||||
try {
|
||||
yield waitForClipboard(() => view._contextmenu.menuitemCopy.click(),
|
||||
yield waitForClipboard(() => menuitemCopy.click(),
|
||||
() => checkClipboardData(expectedPattern));
|
||||
} catch (e) {
|
||||
failedClipboard(expectedPattern);
|
||||
}
|
||||
|
||||
view._contextmenu._menupopup.hidePopup();
|
||||
|
||||
// The value field is still focused. Blur it now and wait for the
|
||||
// ruleview-changed event to avoid pending requests.
|
||||
let onRuleViewChanged = view.once("ruleview-changed");
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
yield onRuleViewChanged;
|
||||
}
|
||||
|
||||
function checkClipboardData(expectedPattern) {
|
||||
|
|
|
@ -19,6 +19,8 @@ var {getInplaceEditorForSpan: inplaceEditor} =
|
|||
|
||||
const ROOT_TEST_DIR = getRootDirectory(gTestPath);
|
||||
const FRAME_SCRIPT_URL = ROOT_TEST_DIR + "doc_frame_script.js";
|
||||
const _STRINGS = Services.strings.createBundle(
|
||||
"chrome://devtools-shared/locale/styleinspector.properties");
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.defaultColorUnit");
|
||||
|
@ -782,3 +784,23 @@ function* sendKeysAndWaitForFocus(view, element, keys) {
|
|||
}
|
||||
yield onFocus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the style editor context menu and return all of it's items in a flat array
|
||||
* @param {CssRuleView} view
|
||||
* The instance of the rule-view panel
|
||||
* @return An array of MenuItems
|
||||
*/
|
||||
function openStyleContextMenuAndGetAllItems(view, target) {
|
||||
let menu = view._contextmenu._openMenu({target: target});
|
||||
|
||||
// Flatten all menu items into a single array to make searching through it easier
|
||||
let allItems = [].concat.apply([], menu.items.map(function addItem(item) {
|
||||
if (item.submenu) {
|
||||
return addItem(item.submenu.items);
|
||||
}
|
||||
return item;
|
||||
}));
|
||||
|
||||
return allItems;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,9 @@ const {PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils");
|
|||
const Services = require("Services");
|
||||
const {Task} = require("devtools/shared/task");
|
||||
|
||||
const Menu = require("devtools/client/framework/menu");
|
||||
const MenuItem = require("devtools/client/framework/menu-item");
|
||||
|
||||
loader.lazyRequireGetter(this, "overlays",
|
||||
"devtools/client/inspector/shared/style-inspector-overlays");
|
||||
loader.lazyServiceGetter(this, "clipboardHelper",
|
||||
|
@ -20,7 +23,6 @@ loader.lazyGetter(this, "_strings", () => {
|
|||
.createBundle("chrome://devtools-shared/locale/styleinspector.properties");
|
||||
});
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
const PREF_ENABLE_MDN_DOCS_TOOLTIP =
|
||||
"devtools.inspector.mdnDocsTooltip.enabled";
|
||||
|
||||
|
@ -54,9 +56,6 @@ function StyleInspectorMenu(view, options) {
|
|||
this._onSelectAll = this._onSelectAll.bind(this);
|
||||
this._onShowMdnDocs = this._onShowMdnDocs.bind(this);
|
||||
this._onToggleOrigSources = this._onToggleOrigSources.bind(this);
|
||||
this._updateMenuItems = this._updateMenuItems.bind(this);
|
||||
|
||||
this._createContextMenu();
|
||||
}
|
||||
|
||||
module.exports = StyleInspectorMenu;
|
||||
|
@ -67,209 +66,195 @@ StyleInspectorMenu.prototype = {
|
|||
*/
|
||||
show: function (event) {
|
||||
try {
|
||||
// In the sidebar we do not have this.styleDocument.popupNode
|
||||
// so we need to save the node ourselves.
|
||||
this.styleDocument.popupNode = event.explicitOriginalTarget;
|
||||
this.styleWindow.focus();
|
||||
this._menupopup.openPopupAtScreen(event.screenX, event.screenY, true);
|
||||
this._openMenu({
|
||||
target: event.explicitOriginalTarget,
|
||||
screenX: event.screenX,
|
||||
screenY: event.screenY,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
|
||||
_createContextMenu: function () {
|
||||
this._menupopup = this.styleDocument.createElementNS(XUL_NS, "menupopup");
|
||||
this._menupopup.addEventListener("popupshowing", this._updateMenuItems);
|
||||
this._menupopup.id = "computed-view-context-menu";
|
||||
_openMenu: function ({ target, screenX = 0, screenY = 0 } = { }) {
|
||||
// In the sidebar we do not have this.styleDocument.popupNode
|
||||
// so we need to save the node ourselves.
|
||||
this.styleDocument.popupNode = target;
|
||||
this.styleWindow.focus();
|
||||
|
||||
let parentDocument = this.styleWindow.parent.document;
|
||||
let popupset = parentDocument.documentElement.querySelector("popupset");
|
||||
if (!popupset) {
|
||||
popupset = parentDocument.createElementNS(XUL_NS, "popupset");
|
||||
parentDocument.documentElement.appendChild(popupset);
|
||||
}
|
||||
popupset.appendChild(this._menupopup);
|
||||
let menu = new Menu();
|
||||
|
||||
this._createContextMenuItems();
|
||||
},
|
||||
/**
|
||||
* Create all context menu items
|
||||
*/
|
||||
_createContextMenuItems: function () {
|
||||
this.menuitemCopy = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.copy",
|
||||
accesskey: "styleinspector.contextmenu.copy.accessKey",
|
||||
command: this._onCopy
|
||||
let menuitemCopy = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.copy"),
|
||||
accesskey: _strings.GetStringFromName("styleinspector.contextmenu.copy.accessKey"),
|
||||
click: () => {
|
||||
this._onCopy();
|
||||
},
|
||||
disabled: !this._hasTextSelected(),
|
||||
});
|
||||
|
||||
this.menuitemCopyLocation = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.copyLocation",
|
||||
command: this._onCopyLocation
|
||||
let menuitemCopyLocation = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.copyLocation"),
|
||||
click: () => {
|
||||
this._onCopyLocation();
|
||||
},
|
||||
visible: false,
|
||||
});
|
||||
|
||||
this.menuitemCopyRule = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.copyRule",
|
||||
command: this._onCopyRule
|
||||
let menuitemCopyRule = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.copyRule"),
|
||||
click: () => {
|
||||
this._onCopyRule();
|
||||
},
|
||||
visible: this.isRuleView,
|
||||
});
|
||||
|
||||
this.menuitemCopyColor = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.copyColor",
|
||||
accesskey: "styleinspector.contextmenu.copyColor.accessKey",
|
||||
command: this._onCopyColor
|
||||
let copyColorAccessKey = "styleinspector.contextmenu.copyColor.accessKey";
|
||||
let menuitemCopyColor = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.copyColor"),
|
||||
accesskey: _strings.GetStringFromName(copyColorAccessKey),
|
||||
click: () => {
|
||||
this._onCopyColor();
|
||||
},
|
||||
visible: this._isColorPopup(),
|
||||
});
|
||||
|
||||
this.menuitemCopyUrl = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.copyUrl",
|
||||
accesskey: "styleinspector.contextmenu.copyUrl.accessKey",
|
||||
command: this._onCopyUrl
|
||||
let copyUrlAccessKey = "styleinspector.contextmenu.copyUrl.accessKey";
|
||||
let menuitemCopyUrl = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.copyUrl"),
|
||||
accesskey: _strings.GetStringFromName(copyUrlAccessKey),
|
||||
click: () => {
|
||||
this._onCopyUrl();
|
||||
},
|
||||
visible: this._isImageUrl(),
|
||||
});
|
||||
|
||||
this.menuitemCopyImageDataUrl = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.copyImageDataUrl",
|
||||
accesskey: "styleinspector.contextmenu.copyImageDataUrl.accessKey",
|
||||
command: this._onCopyImageDataUrl
|
||||
let copyImageAccessKey = "styleinspector.contextmenu.copyImageDataUrl.accessKey";
|
||||
let menuitemCopyImageDataUrl = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.copyImageDataUrl"),
|
||||
accesskey: _strings.GetStringFromName(copyImageAccessKey),
|
||||
click: () => {
|
||||
this._onCopyImageDataUrl();
|
||||
},
|
||||
visible: this._isImageUrl(),
|
||||
});
|
||||
|
||||
this.menuitemCopyPropertyDeclaration = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.copyPropertyDeclaration",
|
||||
command: this._onCopyPropertyDeclaration
|
||||
let copyPropDeclarationLabel = "styleinspector.contextmenu.copyPropertyDeclaration";
|
||||
let menuitemCopyPropertyDeclaration = new MenuItem({
|
||||
label: _strings.GetStringFromName(copyPropDeclarationLabel),
|
||||
click: () => {
|
||||
this._onCopyPropertyDeclaration();
|
||||
},
|
||||
visible: false,
|
||||
});
|
||||
|
||||
this.menuitemCopyPropertyName = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.copyPropertyName",
|
||||
command: this._onCopyPropertyName
|
||||
let menuitemCopyPropertyName = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.copyPropertyName"),
|
||||
click: () => {
|
||||
this._onCopyPropertyName();
|
||||
},
|
||||
visible: false,
|
||||
});
|
||||
|
||||
this.menuitemCopyPropertyValue = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.copyPropertyValue",
|
||||
command: this._onCopyPropertyValue
|
||||
let menuitemCopyPropertyValue = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.copyPropertyValue"),
|
||||
click: () => {
|
||||
this._onCopyPropertyValue();
|
||||
},
|
||||
visible: false,
|
||||
});
|
||||
|
||||
this.menuitemCopySelector = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.copySelector",
|
||||
command: this._onCopySelector
|
||||
let menuitemCopySelector = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.copySelector"),
|
||||
click: () => {
|
||||
this._onCopySelector();
|
||||
},
|
||||
visible: false,
|
||||
});
|
||||
|
||||
this._createMenuSeparator();
|
||||
|
||||
// Select All
|
||||
this.menuitemSelectAll = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.selectAll",
|
||||
accesskey: "styleinspector.contextmenu.selectAll.accessKey",
|
||||
command: this._onSelectAll
|
||||
});
|
||||
|
||||
this._createMenuSeparator();
|
||||
|
||||
// Add new rule
|
||||
this.menuitemAddRule = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.addNewRule",
|
||||
accesskey: "styleinspector.contextmenu.addNewRule.accessKey",
|
||||
command: this._onAddNewRule
|
||||
});
|
||||
|
||||
// Show MDN Docs
|
||||
this.menuitemShowMdnDocs = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.showMdnDocs",
|
||||
accesskey: "styleinspector.contextmenu.showMdnDocs.accessKey",
|
||||
command: this._onShowMdnDocs
|
||||
});
|
||||
|
||||
// Show Original Sources
|
||||
this.menuitemSources = this._createContextMenuItem({
|
||||
label: "styleinspector.contextmenu.toggleOrigSources",
|
||||
accesskey: "styleinspector.contextmenu.toggleOrigSources.accessKey",
|
||||
command: this._onToggleOrigSources,
|
||||
type: "checkbox"
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a single context menu item based on the provided configuration
|
||||
* Returns the created menu item element
|
||||
*/
|
||||
_createContextMenuItem: function (attributes) {
|
||||
let ownerDocument = this._menupopup.ownerDocument;
|
||||
let item = ownerDocument.createElementNS(XUL_NS, "menuitem");
|
||||
|
||||
item.setAttribute("label", _strings.GetStringFromName(attributes.label));
|
||||
if (attributes.accesskey) {
|
||||
item.setAttribute("accesskey",
|
||||
_strings.GetStringFromName(attributes.accesskey));
|
||||
}
|
||||
item.addEventListener("command", attributes.command);
|
||||
|
||||
if (attributes.type) {
|
||||
item.setAttribute("type", attributes.type);
|
||||
}
|
||||
|
||||
this._menupopup.appendChild(item);
|
||||
return item;
|
||||
},
|
||||
|
||||
_createMenuSeparator: function () {
|
||||
let ownerDocument = this._menupopup.ownerDocument;
|
||||
let separator = ownerDocument.createElementNS(XUL_NS, "menuseparator");
|
||||
this._menupopup.appendChild(separator);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the context menu. This means enabling or disabling menuitems as
|
||||
* appropriate.
|
||||
*/
|
||||
_updateMenuItems: function () {
|
||||
this._updateCopyMenuItems();
|
||||
|
||||
let showOrig = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
|
||||
this.menuitemSources.setAttribute("checked", showOrig);
|
||||
|
||||
let enableMdnDocsTooltip =
|
||||
Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP);
|
||||
this.menuitemShowMdnDocs.hidden = !(enableMdnDocsTooltip &&
|
||||
this._isPropertyName());
|
||||
|
||||
this.menuitemAddRule.hidden = !this.isRuleView;
|
||||
this.menuitemAddRule.disabled = !this.isRuleView ||
|
||||
this.inspector.selection.isAnonymousNode();
|
||||
},
|
||||
|
||||
/**
|
||||
* Display the necessary copy context menu items depending on the clicked
|
||||
* node and selection in the rule view.
|
||||
*/
|
||||
_updateCopyMenuItems: function () {
|
||||
this.menuitemCopy.disabled = !this._hasTextSelected();
|
||||
|
||||
this.menuitemCopyColor.hidden = !this._isColorPopup();
|
||||
this.menuitemCopyImageDataUrl.hidden = !this._isImageUrl();
|
||||
this.menuitemCopyUrl.hidden = !this._isImageUrl();
|
||||
this.menuitemCopyRule.hidden = !this.isRuleView;
|
||||
|
||||
this.menuitemCopyLocation.hidden = true;
|
||||
this.menuitemCopyPropertyDeclaration.hidden = true;
|
||||
this.menuitemCopyPropertyName.hidden = true;
|
||||
this.menuitemCopyPropertyValue.hidden = true;
|
||||
this.menuitemCopySelector.hidden = true;
|
||||
|
||||
this._clickedNodeInfo = this._getClickedNodeInfo();
|
||||
if (this.isRuleView && this._clickedNodeInfo) {
|
||||
switch (this._clickedNodeInfo.type) {
|
||||
case overlays.VIEW_NODE_PROPERTY_TYPE :
|
||||
this.menuitemCopyPropertyDeclaration.hidden = false;
|
||||
this.menuitemCopyPropertyName.hidden = false;
|
||||
menuitemCopyPropertyDeclaration.visible = true;
|
||||
menuitemCopyPropertyName.visible = true;
|
||||
break;
|
||||
case overlays.VIEW_NODE_VALUE_TYPE :
|
||||
this.menuitemCopyPropertyDeclaration.hidden = false;
|
||||
this.menuitemCopyPropertyValue.hidden = false;
|
||||
menuitemCopyPropertyDeclaration.visible = true;
|
||||
menuitemCopyPropertyValue.visible = true;
|
||||
break;
|
||||
case overlays.VIEW_NODE_SELECTOR_TYPE :
|
||||
this.menuitemCopySelector.hidden = false;
|
||||
menuitemCopySelector.visible = true;
|
||||
break;
|
||||
case overlays.VIEW_NODE_LOCATION_TYPE :
|
||||
this.menuitemCopyLocation.hidden = false;
|
||||
menuitemCopyLocation.visible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
menu.append(menuitemCopy);
|
||||
menu.append(menuitemCopyLocation);
|
||||
menu.append(menuitemCopyRule);
|
||||
menu.append(menuitemCopyColor);
|
||||
menu.append(menuitemCopyUrl);
|
||||
menu.append(menuitemCopyImageDataUrl);
|
||||
menu.append(menuitemCopyPropertyDeclaration);
|
||||
menu.append(menuitemCopyPropertyName);
|
||||
menu.append(menuitemCopyPropertyValue);
|
||||
menu.append(menuitemCopySelector);
|
||||
|
||||
menu.append(new MenuItem({
|
||||
type: "separator",
|
||||
}));
|
||||
|
||||
// Select All
|
||||
let selectAllAccessKey = "styleinspector.contextmenu.selectAll.accessKey";
|
||||
let menuitemSelectAll = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.selectAll"),
|
||||
accesskey: _strings.GetStringFromName(selectAllAccessKey),
|
||||
click: () => {
|
||||
this._onSelectAll();
|
||||
},
|
||||
});
|
||||
menu.append(menuitemSelectAll);
|
||||
|
||||
menu.append(new MenuItem({
|
||||
type: "separator",
|
||||
}));
|
||||
|
||||
// Add new rule
|
||||
let addRuleAccessKey = "styleinspector.contextmenu.addNewRule.accessKey";
|
||||
let menuitemAddRule = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.addNewRule"),
|
||||
accesskey: _strings.GetStringFromName(addRuleAccessKey),
|
||||
click: () => {
|
||||
this._onAddNewRule();
|
||||
},
|
||||
visible: this.isRuleView,
|
||||
disabled: !this.isRuleView ||
|
||||
this.inspector.selection.isAnonymousNode(),
|
||||
});
|
||||
menu.append(menuitemAddRule);
|
||||
|
||||
// Show MDN Docs
|
||||
let mdnDocsAccessKey = "styleinspector.contextmenu.showMdnDocs.accessKey";
|
||||
let menuitemShowMdnDocs = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.showMdnDocs"),
|
||||
accesskey: _strings.GetStringFromName(mdnDocsAccessKey),
|
||||
click: () => {
|
||||
this._onShowMdnDocs();
|
||||
},
|
||||
visible: (Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP) &&
|
||||
this._isPropertyName()),
|
||||
});
|
||||
menu.append(menuitemShowMdnDocs);
|
||||
|
||||
// Show Original Sources
|
||||
let sourcesAccessKey = "styleinspector.contextmenu.toggleOrigSources.accessKey";
|
||||
let menuitemSources = new MenuItem({
|
||||
label: _strings.GetStringFromName("styleinspector.contextmenu.toggleOrigSources"),
|
||||
accesskey: _strings.GetStringFromName(sourcesAccessKey),
|
||||
click: () => {
|
||||
this._onToggleOrigSources();
|
||||
},
|
||||
type: "checkbox",
|
||||
checked: Services.prefs.getBoolPref(PREF_ORIG_SOURCES),
|
||||
});
|
||||
menu.append(menuitemSources);
|
||||
|
||||
menu.popup(screenX, screenY, this.inspector._toolbox);
|
||||
return menu;
|
||||
},
|
||||
|
||||
_hasTextSelected: function () {
|
||||
|
@ -512,46 +497,11 @@ StyleInspectorMenu.prototype = {
|
|||
},
|
||||
|
||||
destroy: function () {
|
||||
this._removeContextMenuItems();
|
||||
|
||||
// Destroy the context menu.
|
||||
this._menupopup.removeEventListener("popupshowing", this._updateMenuItems);
|
||||
this._menupopup.parentNode.removeChild(this._menupopup);
|
||||
this._menupopup = null;
|
||||
|
||||
this.popupNode = null;
|
||||
this.styleDocument.popupNode = null;
|
||||
this.view = null;
|
||||
this.inspector = null;
|
||||
this.styleDocument = null;
|
||||
this.styleWindow = null;
|
||||
},
|
||||
|
||||
_removeContextMenuItems: function () {
|
||||
this._removeContextMenuItem("menuitemAddRule", this._onAddNewRule);
|
||||
this._removeContextMenuItem("menuitemCopy", this._onCopy);
|
||||
this._removeContextMenuItem("menuitemCopyColor", this._onCopyColor);
|
||||
this._removeContextMenuItem("menuitemCopyImageDataUrl",
|
||||
this._onCopyImageDataUrl);
|
||||
this._removeContextMenuItem("menuitemCopyLocation", this._onCopyLocation);
|
||||
this._removeContextMenuItem("menuitemCopyPropertyDeclaration",
|
||||
this._onCopyPropertyDeclaration);
|
||||
this._removeContextMenuItem("menuitemCopyPropertyName",
|
||||
this._onCopyPropertyName);
|
||||
this._removeContextMenuItem("menuitemCopyPropertyValue",
|
||||
this._onCopyPropertyValue);
|
||||
this._removeContextMenuItem("menuitemCopyRule", this._onCopyRule);
|
||||
this._removeContextMenuItem("menuitemCopySelector", this._onCopySelector);
|
||||
this._removeContextMenuItem("menuitemCopyUrl", this._onCopyUrl);
|
||||
this._removeContextMenuItem("menuitemSelectAll", this._onSelectAll);
|
||||
this._removeContextMenuItem("menuitemShowMdnDocs", this._onShowMdnDocs);
|
||||
this._removeContextMenuItem("menuitemSources", this._onToggleOrigSources);
|
||||
},
|
||||
|
||||
_removeContextMenuItem: function (itemName, callback) {
|
||||
if (this[itemName]) {
|
||||
this[itemName].removeEventListener("command", callback);
|
||||
this[itemName] = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -30,20 +30,19 @@ function* testCopyToClipboard(inspector, view) {
|
|||
|
||||
yield selectNode("div", inspector);
|
||||
|
||||
let win = view.styleWindow;
|
||||
let element = getRuleViewProperty(view, "div", "color").valueSpan
|
||||
.querySelector(".ruleview-colorswatch");
|
||||
|
||||
let popup = once(view._contextmenu._menupopup, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(element, {button: 2, type: "contextmenu"},
|
||||
win);
|
||||
yield popup;
|
||||
let allMenuItems = openStyleContextMenuAndGetAllItems(view, element);
|
||||
let menuitemCopyColor = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copyColor"));
|
||||
|
||||
ok(!view._contextmenu.menuitemCopyColor.hidden, "Copy color is visible");
|
||||
ok(menuitemCopyColor.visible, "Copy color is visible");
|
||||
|
||||
yield waitForClipboard(() => view._contextmenu.menuitemCopyColor.click(),
|
||||
yield waitForClipboard(() => menuitemCopyColor.click(),
|
||||
"#123ABC");
|
||||
view._contextmenu._menupopup.hidePopup();
|
||||
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", { });
|
||||
}
|
||||
|
||||
function* testManualEdit(inspector, view) {
|
||||
|
|
|
@ -77,42 +77,31 @@ function* testCopyUrlToClipboard({view, inspector}, type, selector, expected) {
|
|||
ok(imageLink, "Background-image link element found");
|
||||
|
||||
info("Simulate right click on the background-image URL");
|
||||
let popup = once(view._contextmenu._menupopup, "popupshown");
|
||||
|
||||
// Cannot rely on synthesizeMouseAtCenter here. The image URL can be displayed
|
||||
// on several lines. A click simulated at the exact center may click between
|
||||
// the lines and miss the target. Instead, using the top-left corner of first
|
||||
// client rect, with an offset of 2 pixels.
|
||||
let rect = imageLink.getClientRects()[0];
|
||||
let x = rect.left + 2;
|
||||
let y = rect.top + 2;
|
||||
|
||||
EventUtils.synthesizeMouseAtPoint(x, y, {
|
||||
button: 2,
|
||||
type: "contextmenu"
|
||||
}, getViewWindow(view));
|
||||
yield popup;
|
||||
let allMenuItems = openStyleContextMenuAndGetAllItems(view, imageLink);
|
||||
let menuitemCopyUrl = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copyUrl"));
|
||||
let menuitemCopyImageDataUrl = allMenuItems.find(item => item.label ===
|
||||
_STRINGS.GetStringFromName("styleinspector.contextmenu.copyImageDataUrl"));
|
||||
|
||||
info("Context menu is displayed");
|
||||
ok(!view._contextmenu.menuitemCopyUrl.hidden,
|
||||
ok(menuitemCopyUrl.visible,
|
||||
"\"Copy URL\" menu entry is displayed");
|
||||
ok(!view._contextmenu.menuitemCopyImageDataUrl.hidden,
|
||||
ok(menuitemCopyImageDataUrl.visible,
|
||||
"\"Copy Image Data-URL\" menu entry is displayed");
|
||||
|
||||
if (type == "data-uri") {
|
||||
info("Click Copy Data URI and wait for clipboard");
|
||||
yield waitForClipboard(() => {
|
||||
return view._contextmenu.menuitemCopyImageDataUrl.click();
|
||||
return menuitemCopyImageDataUrl.click();
|
||||
}, expected);
|
||||
} else {
|
||||
info("Click Copy URL and wait for clipboard");
|
||||
yield waitForClipboard(() => {
|
||||
return view._contextmenu.menuitemCopyUrl.click();
|
||||
return menuitemCopyUrl.click();
|
||||
}, expected);
|
||||
}
|
||||
|
||||
info("Hide context menu");
|
||||
view._contextmenu._menupopup.hidePopup();
|
||||
}
|
||||
|
||||
function getBackgroundImageProperty(view, selector) {
|
||||
|
@ -122,11 +111,3 @@ function getBackgroundImageProperty(view, selector) {
|
|||
}
|
||||
return getComputedViewProperty(view, "background-image");
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that returns the window for a given view.
|
||||
*/
|
||||
function getViewWindow(view) {
|
||||
let viewDocument = view.styleDocument ? view.styleDocument : view.doc;
|
||||
return viewDocument.defaultView;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ const TEST_URL_ROOT_SSL =
|
|||
"https://example.com/browser/devtools/client/inspector/shared/test/";
|
||||
const ROOT_TEST_DIR = getRootDirectory(gTestPath);
|
||||
const FRAME_SCRIPT_URL = ROOT_TEST_DIR + "doc_frame_script.js";
|
||||
const _STRINGS = Services.strings.createBundle(
|
||||
"chrome://devtools-shared/locale/styleinspector.properties");
|
||||
|
||||
// Clean-up all prefs that might have been changed during a test run
|
||||
// (safer here because if the test fails, then the pref is never reverted)
|
||||
|
@ -534,3 +536,23 @@ function getComputedViewPropertyValue(view, name, propertyName) {
|
|||
return getComputedViewProperty(view, name, propertyName)
|
||||
.valueSpan.textContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the style editor context menu and return all of it's items in a flat array
|
||||
* @param {CssRuleView} view
|
||||
* The instance of the rule-view panel
|
||||
* @return An array of MenuItems
|
||||
*/
|
||||
function openStyleContextMenuAndGetAllItems(view, target) {
|
||||
let menu = view._contextmenu._openMenu({target: target});
|
||||
|
||||
// Flatten all menu items into a single array to make searching through it easier
|
||||
let allItems = [].concat.apply([], menu.items.map(function addItem(item) {
|
||||
if (item.submenu) {
|
||||
return addItem(item.submenu.items);
|
||||
}
|
||||
return item;
|
||||
}));
|
||||
|
||||
return allItems;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ support-files =
|
|||
[test_reps_date-time.html]
|
||||
[test_reps_function.html]
|
||||
[test_reps_grip.html]
|
||||
[test_reps_null.html]
|
||||
[test_reps_object-with-text.html]
|
||||
[test_reps_object-with-url.html]
|
||||
[test_reps_stylesheet.html]
|
||||
[test_reps_undefined.html]
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test Null rep
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Rep test - Null</title>
|
||||
<script type="application/javascript" 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="head.js" type="application/javascript;version=1.8"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = Task.async(function* () {
|
||||
try {
|
||||
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
|
||||
let { Null } = browserRequire("devtools/client/shared/components/reps/null");
|
||||
|
||||
let gripStub = {
|
||||
"type": "null"
|
||||
};
|
||||
|
||||
// Test that correct rep is chosen
|
||||
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
|
||||
is(renderedRep.type, Null.rep, `Rep correctly selects ${Null.rep.displayName}`);
|
||||
|
||||
// Test rendering
|
||||
const renderedComponent = renderComponent(Null.rep, { object: gripStub });
|
||||
is(renderedComponent.textContent, "null", "Null rep has expected text content");
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,52 @@
|
|||
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test ObjectWithText rep
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Rep test - ObjectWithText</title>
|
||||
<script type="application/javascript" 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="head.js" type="application/javascript;version=1.8"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
window.onload = Task.async(function* () {
|
||||
try {
|
||||
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
|
||||
let { ObjectWithText } = browserRequire("devtools/client/shared/components/reps/object-with-text");
|
||||
|
||||
let gripStub = {
|
||||
"type": "object",
|
||||
"class": "CSSStyleRule",
|
||||
"actor": "server1.conn3.obj273",
|
||||
"extensible": true,
|
||||
"frozen": false,
|
||||
"sealed": false,
|
||||
"ownPropertyLength": 0,
|
||||
"preview": {
|
||||
"kind": "ObjectWithText",
|
||||
"text": ".Shadow"
|
||||
}
|
||||
};
|
||||
|
||||
// Test that correct rep is chosen
|
||||
const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
|
||||
is(renderedRep.type, ObjectWithText.rep, `Rep correctly selects ${ObjectWithText.rep.displayName}`);
|
||||
|
||||
// Test rendering
|
||||
const renderedComponent = renderComponent(ObjectWithText.rep, { object: gripStub });
|
||||
is(renderedComponent.textContent, ".Shadow", "ObjectWithText rep has expected text content");
|
||||
} catch(e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -122,7 +122,8 @@ KeyShortcuts.parseElectronKey = function (window, str) {
|
|||
} else if (mod === "Shift") {
|
||||
shortcut.shift = true;
|
||||
} else {
|
||||
throw new Error("Unsupported modifier: " + mod);
|
||||
console.error("Unsupported modifier:", mod, "from key:", str);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,7 +143,8 @@ KeyShortcuts.parseElectronKey = function (window, str) {
|
|||
// Used only to stringify the shortcut
|
||||
shortcut.keyCodeString = key;
|
||||
} else {
|
||||
throw new Error("Unsupported key: " + key);
|
||||
console.error("Unsupported key:", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return shortcut;
|
||||
|
@ -220,6 +222,10 @@ KeyShortcuts.prototype = {
|
|||
}
|
||||
if (!this.keys.has(key)) {
|
||||
let shortcut = KeyShortcuts.parseElectronKey(this.window, key);
|
||||
// The key string is wrong and we were unable to compute the key shortcut
|
||||
if (!shortcut) {
|
||||
return;
|
||||
}
|
||||
this.keys.set(key, shortcut);
|
||||
}
|
||||
this.eventEmitter.on(key, listener);
|
||||
|
|
|
@ -18,6 +18,7 @@ add_task(function* () {
|
|||
yield testAltModifier(shortcuts);
|
||||
yield testCommandOrControlModifier(shortcuts);
|
||||
yield testCtrlModifier(shortcuts);
|
||||
yield testInvalidShortcutString(shortcuts);
|
||||
shortcuts.destroy();
|
||||
|
||||
yield testTarget();
|
||||
|
@ -362,3 +363,13 @@ function testTarget() {
|
|||
|
||||
shortcuts.destroy();
|
||||
}
|
||||
|
||||
function testInvalidShortcutString(shortcuts) {
|
||||
info("Test wrong shortcut string");
|
||||
|
||||
let shortcut = KeyShortcuts.parseElectronKey(window, "Cmmd+F");
|
||||
ok(!shortcut, "Passing a invalid shortcut string should return a null object");
|
||||
|
||||
shortcuts.on("Cmmd+F", function () {});
|
||||
ok(true, "on() shouldn't throw when passing invalid shortcut string");
|
||||
}
|
||||
|
|
|
@ -200,7 +200,7 @@ input {
|
|||
.remove-button {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url(chrome://devtools/skin/images/close@2x.png);
|
||||
background: url(chrome://devtools/skin/images/close.svg);
|
||||
background-size: cover;
|
||||
font-size: 0;
|
||||
border: none;
|
||||
|
|
|
@ -616,19 +616,19 @@ StyleEditorUI.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
let href = csscoverage.sheetToUrl(showEditor.styleSheet);
|
||||
let reportData = yield usage.createEditorReport(href);
|
||||
let sheet = showEditor.styleSheet;
|
||||
let {reports} = yield usage.createEditorReportForSheet(sheet);
|
||||
|
||||
showEditor.removeAllUnusedRegions();
|
||||
|
||||
if (reportData.reports.length > 0) {
|
||||
if (reports.length > 0) {
|
||||
// Only apply if this file isn't compressed. We detect a
|
||||
// compressed file if there are more rules than lines.
|
||||
let editorText = showEditor.sourceEditor.getText();
|
||||
let lineCount = editorText.split("\n").length;
|
||||
let ruleCount = showEditor.styleSheet.ruleCount;
|
||||
if (lineCount >= ruleCount) {
|
||||
showEditor.addUnusedRegions(reportData.reports);
|
||||
showEditor.addUnusedRegions(reports);
|
||||
} else {
|
||||
this.emit("error", { key: "error-compressed", level: "info" });
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@
|
|||
|
||||
/* Responsive sidebar */
|
||||
@media (max-width: 700px) {
|
||||
#storage-tree {
|
||||
#storage-tree,
|
||||
#storage-sidebar {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
|
|
@ -316,7 +316,7 @@ JSTerm.prototype = {
|
|||
errorDocLink = this.hud.document.createElementNS(XHTML_NS, "a");
|
||||
errorDocLink.className = "learn-more-link webconsole-learn-more-link";
|
||||
errorDocLink.textContent = `[${l10n.getStr("webConsoleMoreInfoLabel")}]`;
|
||||
errorDocLink.title = errorDocURL;
|
||||
errorDocLink.title = errorDocURL.split("?")[0];
|
||||
errorDocLink.href = "#";
|
||||
errorDocLink.draggable = false;
|
||||
errorDocLink.addEventListener("click", () => {
|
||||
|
|
|
@ -25,7 +25,7 @@ function ConsoleApiCall(props) {
|
|||
const { message } = props;
|
||||
const messageBody =
|
||||
dom.span({className: "message-body devtools-monospace"},
|
||||
formatTextContent(message.data.arguments));
|
||||
formatTextContent(message.data));
|
||||
const icon = MessageIcon({severity: message.severity});
|
||||
const repeat = MessageRepeat({repeat: message.repeat});
|
||||
const children = [
|
||||
|
@ -53,8 +53,13 @@ function ConsoleApiCall(props) {
|
|||
);
|
||||
}
|
||||
|
||||
function formatTextContent(args) {
|
||||
return args.map(function (arg, i, arr) {
|
||||
function formatTextContent(data) {
|
||||
return data.arguments.map(function (arg, i, arr) {
|
||||
if (data.counter) {
|
||||
let {label, count} = data.counter;
|
||||
arg = `${label}: ${count}`;
|
||||
}
|
||||
|
||||
const str = dom.span({className: "console-string"}, arg);
|
||||
if (i < arr.length - 1) {
|
||||
return [str, " "];
|
||||
|
|
|
@ -20,14 +20,42 @@ window.onload = Task.async(function* () {
|
|||
const message = prepareMessage(packet);
|
||||
const rendered = renderComponent(ConsoleApiCall, {message});
|
||||
|
||||
const queryPath = "div.message.cm-s-mozilla span span.message-flex-body span.message-body.devtools-monospace";
|
||||
const messageBody = rendered.querySelectorAll(queryPath);
|
||||
const consoleStringNodes = messageBody[0].querySelectorAll("span.console-string");
|
||||
const messageBody = getMessageBody(rendered);
|
||||
const consoleStringNodes = getConsoleStringNodes(messageBody);
|
||||
is(consoleStringNodes.length, 2, "ConsoleApiCall outputs expected HTML structure");
|
||||
is(messageBody[0].textContent, "foobar test", "ConsoleApiCall outputs expected text");
|
||||
is(messageBody.textContent, "foobar test", "ConsoleApiCall outputs expected text");
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const countPacket = yield getPacket("console.count('bar')", "consoleAPICall");
|
||||
const countMessage = prepareMessage(countPacket);
|
||||
const countRendered = renderComponent(ConsoleApiCall, {message: countMessage});
|
||||
testConsoleCountRenderedElement(countRendered, `bar: ${i + 1}`);
|
||||
}
|
||||
|
||||
SimpleTest.finish()
|
||||
});
|
||||
|
||||
function getMessageBody(renderedComponent) {
|
||||
const queryPath = "div.message.cm-s-mozilla span span.message-flex-body " +
|
||||
"span.message-body.devtools-monospace";
|
||||
return renderedComponent.querySelector(queryPath);
|
||||
}
|
||||
|
||||
function getConsoleStringNodes(messageBody) {
|
||||
return messageBody.querySelectorAll("span.console-string");
|
||||
}
|
||||
|
||||
function testConsoleCountRenderedElement(renderedComponent, expectedTextContent) {
|
||||
info("Testing console.count rendered element");
|
||||
|
||||
const messageBody = getMessageBody(renderedComponent);
|
||||
const consoleStringNodes = getConsoleStringNodes(messageBody);
|
||||
|
||||
is(consoleStringNodes.length, 1,
|
||||
"console.count rendered element has the expected HTML structure");
|
||||
is(messageBody.textContent, expectedTextContent,
|
||||
"console.count rendered element has the expected text content");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
|
||||
const TEST_URI = "https://example.com/browser/devtools/client/webconsole/" +
|
||||
"test/test-mixedcontent-securityerrors.html";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/" +
|
||||
"MixedContent";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Web/Security/" +
|
||||
"Mixed_content" + DOCS_GA_PARAMS;
|
||||
|
||||
add_task(function* () {
|
||||
yield pushPrefEnv();
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
const TEST_URI = "https://example.com/browser/devtools/client/webconsole/" +
|
||||
"test/test-mixedcontent-securityerrors.html";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/" +
|
||||
"MixedContent";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Web/Security/" +
|
||||
"Mixed_content" + DOCS_GA_PARAMS;
|
||||
|
||||
add_task(function* () {
|
||||
yield pushPrefEnv();
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
const TEST_URI = "data:text/html;charset=utf8,Web Console mixed content test";
|
||||
const TEST_HTTPS_URI = "https://example.com/browser/devtools/client/" +
|
||||
"webconsole/test/test-bug-737873-mixedcontent.html";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/" +
|
||||
"MixedContent";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Web/Security/" +
|
||||
"Mixed_content";
|
||||
|
||||
add_task(function* () {
|
||||
Services.prefs.setBoolPref("security.mixed_content.block_display_content",
|
||||
|
|
|
@ -19,8 +19,8 @@ const INSECURE_FORM_ACTION_MSG = "Password fields present in a form with an " +
|
|||
const INSECURE_IFRAME_MSG = "Password fields present on an insecure " +
|
||||
"(http://) iframe. This is a security risk that allows " +
|
||||
"user login credentials to be stolen.";
|
||||
const INSECURE_PASSWORDS_URI = "https://developer.mozilla.org/docs/Security/" +
|
||||
"InsecurePasswords";
|
||||
const INSECURE_PASSWORDS_URI = "https://developer.mozilla.org/docs/Web/" +
|
||||
"Security/Insecure_passwords" + DOCS_GA_PARAMS;
|
||||
|
||||
add_task(function* () {
|
||||
yield loadTab(TEST_URI);
|
||||
|
|
|
@ -13,7 +13,7 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console HPKP invalid " +
|
|||
const SJS_URL = "https://example.com/browser/devtools/client/webconsole/" +
|
||||
"test/test_hpkp-invalid-headers.sjs";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Web/Security/" +
|
||||
"Public_Key_Pinning";
|
||||
"Public_Key_Pinning" + DOCS_GA_PARAMS;
|
||||
const NON_BUILTIN_ROOT_PREF = "security.cert_pinning.process_headers_from_" +
|
||||
"non_builtin_roots";
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console HSTS invalid " +
|
|||
"header test";
|
||||
const SJS_URL = "https://example.com/browser/devtools/client/webconsole/" +
|
||||
"test/test_hsts-invalid-headers.sjs";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/" +
|
||||
"HTTP_Strict_Transport_Security";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Web/Security/" +
|
||||
"HTTP_strict_transport_security" + DOCS_GA_PARAMS;
|
||||
|
||||
add_task(function* () {
|
||||
yield loadTab(TEST_URI);
|
||||
|
|
|
@ -180,13 +180,13 @@ function* testJSTerm(hud) {
|
|||
};
|
||||
|
||||
for (let errorMessageName of Object.keys(ErrorDocStatements)) {
|
||||
let url = ErrorDocs.GetURL(errorMessageName);
|
||||
let title = ErrorDocs.GetURL({ errorMessageName }).split("?")[0];
|
||||
|
||||
jsterm.clearOutput();
|
||||
yield jsterm.execute(ErrorDocStatements[errorMessageName]);
|
||||
yield checkResult((node) => {
|
||||
return node.parentNode.getElementsByTagName("a")[0].title == url;
|
||||
}, `error links to ${url}`);
|
||||
return node.parentNode.getElementsByTagName("a")[0].title == title;
|
||||
}, `error links to ${title}`);
|
||||
}
|
||||
|
||||
// Ensure that dom errors, with error numbers outside of the range
|
||||
|
|
|
@ -54,7 +54,7 @@ function* testScriptError(hud, testData) {
|
|||
});
|
||||
|
||||
// grab the most current error doc URL
|
||||
let url = ErrorDocs.GetURL(testData.jsmsg);
|
||||
let url = ErrorDocs.GetURL({ errorMessageName: testData.jsmsg });
|
||||
|
||||
let hrefs = {};
|
||||
for (let link of hud.jsterm.outputNode.querySelectorAll("a")) {
|
||||
|
|
|
@ -8,9 +8,12 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "http://tracking.example.org/browser/devtools/client/webconsole/test/test-trackingprotection-securityerrors.html";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/Firefox/Privacy/Tracking_Protection";
|
||||
const TEST_URI = "http://tracking.example.org/browser/devtools/client/" +
|
||||
"webconsole/test/test-trackingprotection-securityerrors.html";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/Firefox/Privacy/" +
|
||||
"Tracking_Protection" + DOCS_GA_PARAMS;
|
||||
const PREF = "privacy.trackingprotection.enabled";
|
||||
|
||||
const {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
|
@ -31,7 +34,8 @@ add_task(function* testMessagesAppear() {
|
|||
messages: [
|
||||
{
|
||||
name: "Was blocked because tracking protection is enabled",
|
||||
text: "The resource at \u201chttp://tracking.example.com/\u201d was blocked because tracking protection is enabled",
|
||||
text: "The resource at \u201chttp://tracking.example.com/\u201d was " +
|
||||
"blocked because tracking protection is enabled",
|
||||
category: CATEGORY_SECURITY,
|
||||
severity: SEVERITY_WARNING,
|
||||
objects: true,
|
||||
|
|
|
@ -41,6 +41,10 @@ const WEBCONSOLE_STRINGS_URI = "chrome://devtools/locale/" +
|
|||
"webconsole.properties";
|
||||
var WCUL10n = new WebConsoleUtils.L10n(WEBCONSOLE_STRINGS_URI);
|
||||
|
||||
const DOCS_GA_PARAMS = "?utm_source=mozilla" +
|
||||
"&utm_medium=firefox-console-errors" +
|
||||
"&utm_campaign=default";
|
||||
|
||||
DevToolsUtils.testing = true;
|
||||
|
||||
function loadTab(url) {
|
||||
|
|
|
@ -44,17 +44,7 @@ var l10n = new WebConsoleUtils.L10n(STRINGS_URI);
|
|||
|
||||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/docs/Security/MixedContent";
|
||||
|
||||
const TRACKING_PROTECTION_LEARN_MORE = "https://developer.mozilla.org/Firefox/Privacy/Tracking_Protection";
|
||||
|
||||
const INSECURE_PASSWORDS_LEARN_MORE = "https://developer.mozilla.org/docs/Security/InsecurePasswords";
|
||||
|
||||
const PUBLIC_KEY_PINS_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Public_Key_Pinning";
|
||||
|
||||
const STRICT_TRANSPORT_SECURITY_LEARN_MORE = "https://developer.mozilla.org/docs/Security/HTTP_Strict_Transport_Security";
|
||||
|
||||
const WEAK_SIGNATURE_ALGORITHM_LEARN_MORE = "https://developer.mozilla.org/docs/Security/Weak_Signature_Algorithm";
|
||||
const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Mixed_content";
|
||||
|
||||
const IGNORED_SOURCE_URLS = ["debugger eval code"];
|
||||
|
||||
|
@ -1487,7 +1477,9 @@ WebConsoleFrame.prototype = {
|
|||
let msgBody = node.getElementsByClassName("message-body")[0];
|
||||
|
||||
// Add the more info link node to messages that belong to certain categories
|
||||
this.addMoreInfoLink(msgBody, scriptError);
|
||||
if (scriptError.exceptionDocURL) {
|
||||
this.addLearnMoreWarningNode(msgBody, scriptError.exceptionDocURL);
|
||||
}
|
||||
|
||||
// Collect telemetry data regarding JavaScript errors
|
||||
this._telemetry.logKeyed("DEVTOOLS_JAVASCRIPT_ERROR_DISPLAYED",
|
||||
|
@ -1667,48 +1659,6 @@ WebConsoleFrame.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a more info link node to messages based on the nsIScriptError object
|
||||
* that we need to report to the console
|
||||
*
|
||||
* @param node
|
||||
* The node to which we will be adding the more info link node
|
||||
* @param scriptError
|
||||
* The script error object that we are reporting to the console
|
||||
*/
|
||||
addMoreInfoLink: function (node, scriptError) {
|
||||
let url;
|
||||
switch (scriptError.category) {
|
||||
case "Insecure Password Field":
|
||||
url = INSECURE_PASSWORDS_LEARN_MORE;
|
||||
break;
|
||||
case "Mixed Content Message":
|
||||
case "Mixed Content Blocker":
|
||||
url = MIXED_CONTENT_LEARN_MORE;
|
||||
break;
|
||||
case "Invalid HPKP Headers":
|
||||
url = PUBLIC_KEY_PINS_LEARN_MORE;
|
||||
break;
|
||||
case "Invalid HSTS Headers":
|
||||
url = STRICT_TRANSPORT_SECURITY_LEARN_MORE;
|
||||
break;
|
||||
case "SHA-1 Signature":
|
||||
url = WEAK_SIGNATURE_ALGORITHM_LEARN_MORE;
|
||||
break;
|
||||
case "Tracking Protection":
|
||||
url = TRACKING_PROTECTION_LEARN_MORE;
|
||||
break;
|
||||
default:
|
||||
// If all else fails check for an error doc URL.
|
||||
url = ErrorDocs.GetURL(scriptError.errorMessageName);
|
||||
break;
|
||||
}
|
||||
|
||||
if (url) {
|
||||
this.addLearnMoreWarningNode(node, url);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Appends a clickable warning node to the node passed
|
||||
* as a parameter to the function. When a user clicks on the appended
|
||||
|
@ -1725,7 +1675,7 @@ WebConsoleFrame.prototype = {
|
|||
let moreInfoLabel = "[" + l10n.getStr("webConsoleMoreInfoLabel") + "]";
|
||||
|
||||
let warningNode = this.document.createElementNS(XHTML_NS, "a");
|
||||
warningNode.title = url;
|
||||
warningNode.title = url.split("?")[0];
|
||||
warningNode.href = url;
|
||||
warningNode.draggable = false;
|
||||
warningNode.textContent = moreInfoLabel;
|
||||
|
|
|
@ -4,14 +4,13 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const { Cc, Ci } = require("chrome");
|
||||
|
||||
const Services = require("Services");
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const events = require("sdk/event/core");
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
const { custom } = protocol;
|
||||
const { cssUsageSpec } = require("devtools/shared/specs/csscoverage");
|
||||
|
||||
loader.lazyGetter(this, "DOMUtils", () => {
|
||||
|
@ -137,8 +136,7 @@ var CSSUsageActor = protocol.ActorClassWithSpec(cssUsageSpec, {
|
|||
// If we're not starting by reloading the page, then pretend that onload
|
||||
// has just happened.
|
||||
this._onTabLoad(this._tabActor.window.document);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this._tabActor.window.location.reload();
|
||||
}
|
||||
|
||||
|
@ -295,8 +293,7 @@ var CSSUsageActor = protocol.ActorClassWithSpec(cssUsageSpec, {
|
|||
ruleData.preLoadOn.add(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
} catch (ex) {
|
||||
ruleData.isError = true;
|
||||
}
|
||||
}
|
||||
|
@ -342,6 +339,18 @@ var CSSUsageActor = protocol.ActorClassWithSpec(cssUsageSpec, {
|
|||
return { reports: reports };
|
||||
},
|
||||
|
||||
/**
|
||||
* Compute the stylesheet URL and delegate the report creation to createEditorReport.
|
||||
* See createEditorReport documentation.
|
||||
*
|
||||
* @param {StyleSheetActor} stylesheetActor
|
||||
* the stylesheet actor for which the coverage report should be generated.
|
||||
*/
|
||||
createEditorReportForSheet: function (stylesheetActor) {
|
||||
let url = sheetToUrl(stylesheetActor.rawSheet);
|
||||
return this.createEditorReport(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a JSONable structure designed for the page report which shows
|
||||
* the recommended changes to a page.
|
||||
|
@ -416,8 +425,7 @@ var CSSUsageActor = protocol.ActorClassWithSpec(cssUsageSpec, {
|
|||
if (!ruleData.isUsed) {
|
||||
let ruleReport = ruleToRuleReport(rule, ruleData);
|
||||
rules.push(ruleReport);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
summary.unused++;
|
||||
}
|
||||
}
|
||||
|
@ -445,8 +453,7 @@ var CSSUsageActor = protocol.ActorClassWithSpec(cssUsageSpec, {
|
|||
let ruleReport = ruleToRuleReport(rule, ruleData);
|
||||
page.rules.push(ruleReport);
|
||||
summary.preload++;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
summary.used++;
|
||||
}
|
||||
}
|
||||
|
@ -693,13 +700,15 @@ function getTestSelector(selector) {
|
|||
exports.SEL_ALL = [
|
||||
SEL_EXTERNAL, SEL_FORM, SEL_ELEMENT, SEL_STRUCTURAL, SEL_SEMI,
|
||||
SEL_COMBINING, SEL_MEDIA
|
||||
].reduce(function (prev, curr) { return prev.concat(curr); }, []);
|
||||
].reduce(function (prev, curr) {
|
||||
return prev.concat(curr);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Find a URL for a given stylesheet
|
||||
* @param stylesheet {StyleSheet|StyleSheetActor}
|
||||
* @param {StyleSheet} stylesheet raw stylesheet
|
||||
*/
|
||||
const sheetToUrl = exports.sheetToUrl = function (stylesheet) {
|
||||
const sheetToUrl = function (stylesheet) {
|
||||
// For <link> elements
|
||||
if (stylesheet.href) {
|
||||
return stylesheet.href;
|
||||
|
@ -713,10 +722,5 @@ const sheetToUrl = exports.sheetToUrl = function (stylesheet) {
|
|||
return getURL(document) + " → <style> index " + index;
|
||||
}
|
||||
|
||||
// When `stylesheet` is a StyleSheetActor, we don't have access to ownerNode
|
||||
if (stylesheet.nodeHref) {
|
||||
return stylesheet.nodeHref + " → <style> index " + stylesheet.styleSheetIndex;
|
||||
}
|
||||
|
||||
throw new Error("Unknown sheet source");
|
||||
};
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const baseURL = "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/";
|
||||
|
||||
const baseURL = "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/";
|
||||
const params = "?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default";
|
||||
const ErrorDocs = {
|
||||
JSMSG_READ_ONLY: "Read-only",
|
||||
JSMSG_BAD_ARRAY_LENGTH: "Invalid_array_length",
|
||||
|
@ -50,10 +50,34 @@ const ErrorDocs = {
|
|||
JSMSG_CURLY_AFTER_LIST: "Missing_curly_after_property_list",
|
||||
};
|
||||
|
||||
exports.GetURL = (errorName) => {
|
||||
let doc = ErrorDocs[errorName];
|
||||
if (doc) {
|
||||
return baseURL + doc;
|
||||
const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Mixed_content";
|
||||
const TRACKING_PROTECTION_LEARN_MORE = "https://developer.mozilla.org/Firefox/Privacy/Tracking_Protection";
|
||||
const INSECURE_PASSWORDS_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Insecure_passwords";
|
||||
const PUBLIC_KEY_PINS_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Public_Key_Pinning";
|
||||
const STRICT_TRANSPORT_SECURITY_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/HTTP_strict_transport_security";
|
||||
const WEAK_SIGNATURE_ALGORITHM_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Weak_Signature_Algorithm";
|
||||
const ErrorCategories = {
|
||||
"Insecure Password Field": INSECURE_PASSWORDS_LEARN_MORE,
|
||||
"Mixed Content Message": MIXED_CONTENT_LEARN_MORE,
|
||||
"Mixed Content Blocker": MIXED_CONTENT_LEARN_MORE,
|
||||
"Invalid HPKP Headers": PUBLIC_KEY_PINS_LEARN_MORE,
|
||||
"Invalid HSTS Headers": STRICT_TRANSPORT_SECURITY_LEARN_MORE,
|
||||
"SHA-1 Signature": WEAK_SIGNATURE_ALGORITHM_LEARN_MORE,
|
||||
"Tracking Protection": TRACKING_PROTECTION_LEARN_MORE,
|
||||
};
|
||||
|
||||
exports.GetURL = (error) => {
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let doc = ErrorDocs[error.errorMessageName];
|
||||
if (doc) {
|
||||
return baseURL + doc + params;
|
||||
}
|
||||
|
||||
let categoryURL = ErrorCategories[error.category];
|
||||
if (categoryURL) {
|
||||
return categoryURL + params;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -973,6 +973,9 @@ function getObjectForLocalOrSessionStorage(type) {
|
|||
|
||||
getValuesForHost(host, name) {
|
||||
let storage = this.hostVsStores.get(host);
|
||||
if (!storage) {
|
||||
return [];
|
||||
}
|
||||
if (name) {
|
||||
return [{name: name, value: storage.getItem(name)}];
|
||||
}
|
||||
|
|
|
@ -903,7 +903,7 @@ WebConsoleActor.prototype =
|
|||
// It is possible that we won't have permission to unwrap an
|
||||
// object and retrieve its errorMessageName.
|
||||
try {
|
||||
errorDocURL = ErrorDocs.GetURL(error && error.errorMessageName);
|
||||
errorDocURL = ErrorDocs.GetURL(error);
|
||||
} catch (ex) {}
|
||||
}
|
||||
}
|
||||
|
@ -1449,6 +1449,7 @@ WebConsoleActor.prototype =
|
|||
return {
|
||||
errorMessage: this._createStringGrip(aPageError.errorMessage),
|
||||
errorMessageName: aPageError.errorMessageName,
|
||||
exceptionDocURL: ErrorDocs.GetURL(aPageError),
|
||||
sourceName: aPageError.sourceName,
|
||||
lineText: lineText,
|
||||
lineNumber: aPageError.lineNumber,
|
||||
|
|
|
@ -33,7 +33,7 @@ const FILENAME_DEFAULT_VALUE = " ";
|
|||
* identical except that one runs on the client and one in the server.
|
||||
*
|
||||
* The server command is hidden, and is designed to be called from the client
|
||||
* command when the --chrome flag is *not* used.
|
||||
* command.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -179,36 +179,14 @@ exports.items = [
|
|||
params: [
|
||||
filenameParam,
|
||||
standardParams,
|
||||
{
|
||||
group: l10n.lookup("screenshotAdvancedOptions"),
|
||||
params: [
|
||||
{
|
||||
name: "chrome",
|
||||
type: "boolean",
|
||||
description: l10n.lookupFormat("screenshotChromeDesc2", [BRAND_SHORT_NAME]),
|
||||
manual: l10n.lookupFormat("screenshotChromeManual2", [BRAND_SHORT_NAME])
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
exec: function (args, context) {
|
||||
if (args.chrome && args.selector) {
|
||||
// Node screenshot with chrome option does not work as intended
|
||||
// Refer https://bugzilla.mozilla.org/show_bug.cgi?id=659268#c7
|
||||
// throwing for now.
|
||||
throw new Error(l10n.lookup("screenshotSelectorChromeConflict"));
|
||||
}
|
||||
// Re-execute the command on the server
|
||||
const command = context.typed.replace(/^screenshot/, "screenshot_server");
|
||||
let capture = context.updateExec(command).then(output => {
|
||||
return output.error ? Promise.reject(output.data) : output.data;
|
||||
});
|
||||
|
||||
let capture;
|
||||
if (!args.chrome) {
|
||||
// Re-execute the command on the server
|
||||
const command = context.typed.replace(/^screenshot/, "screenshot_server");
|
||||
capture = context.updateExec(command).then(output => {
|
||||
return output.error ? Promise.reject(output.data) : output.data;
|
||||
});
|
||||
} else {
|
||||
capture = captureScreenshot(args, context.environment.chromeDocument);
|
||||
}
|
||||
simulateCameraEffect(context.environment.chromeDocument, "shutter");
|
||||
return capture.then(saveScreenshot.bind(null, args, context));
|
||||
},
|
||||
|
|
|
@ -66,26 +66,10 @@ screenshotClipboardDesc=Copy screenshot to clipboard? (true/false)
|
|||
# asks for help on what it does.
|
||||
screenshotClipboardManual=True if you want to copy the screenshot instead of saving it to a file.
|
||||
|
||||
# LOCALIZATION NOTE (screenshotChromeDesc) A very short string to describe
|
||||
# the 'chrome' parameter to the 'screenshot' command, which is displayed in
|
||||
# a dialog when the user is using this command.
|
||||
# The argument (%1$S) is the browser name.
|
||||
screenshotChromeDesc2=Capture %1$S chrome window? (true/false)
|
||||
|
||||
# LOCALIZATION NOTE (screenshotChromeManual) A fuller description of the
|
||||
# 'chrome' parameter to the 'screenshot' command, displayed when the user
|
||||
# asks for help on what it does.
|
||||
# The argument (%1$S) is the browser name.
|
||||
screenshotChromeManual2=True if you want to take the screenshot of the %1$S window rather than the web page’s content window.
|
||||
|
||||
# LOCALIZATION NOTE (screenshotGroupOptions) A label for the optional options of
|
||||
# the screenshot command.
|
||||
screenshotGroupOptions=Options
|
||||
|
||||
# LOCALIZATION NOTE (screenshotGroupOptions) A label for the advanced options of
|
||||
# the screenshot command.
|
||||
screenshotAdvancedOptions=Advanced Options
|
||||
|
||||
# LOCALIZATION NOTE (screenshotDelayDesc) A very short string to describe
|
||||
# the 'delay' parameter to the 'screenshot' command, which is displayed in
|
||||
# a dialog when the user is using this command.
|
||||
|
@ -116,11 +100,6 @@ screenshotFullPageDesc=Entire webpage? (true/false)
|
|||
# asks for help on what it does.
|
||||
screenshotFullPageManual=True if the screenshot should also include parts of the webpage which are outside the current scrolled bounds.
|
||||
|
||||
# LOCALIZATION NOTE (screenshotSelectorChromeConflict) Exception thrown when user
|
||||
# tries to use 'selector' option along with 'chrome' option of the screenshot
|
||||
# command. Refer: https://bugzilla.mozilla.org/show_bug.cgi?id=659268#c7
|
||||
screenshotSelectorChromeConflict=selector option is not supported when chrome option is true
|
||||
|
||||
# LOCALIZATION NOTE (screenshotGeneratedFilename) The auto generated filename
|
||||
# when no file name is provided. The first argument (%1$S) is the date string
|
||||
# in yyyy-mm-dd format and the second argument (%2$S) is the time string
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
const {Arg, RetVal, generateActorSpec} = require("devtools/shared/protocol");
|
||||
|
||||
require("devtools/shared/specs/stylesheets");
|
||||
|
||||
const cssUsageSpec = generateActorSpec({
|
||||
typeName: "cssUsage",
|
||||
|
||||
|
@ -26,6 +28,10 @@ const cssUsageSpec = generateActorSpec({
|
|||
request: { url: Arg(0, "string") },
|
||||
response: { reports: RetVal("array:json") }
|
||||
},
|
||||
createEditorReportForSheet: {
|
||||
request: { url: Arg(0, "stylesheet") },
|
||||
response: { reports: RetVal("array:json") }
|
||||
},
|
||||
createPageReport: {
|
||||
response: RetVal("json")
|
||||
},
|
||||
|
|
|
@ -52,7 +52,7 @@ public:
|
|||
, mGrallocFormat(aGrallocFormat)
|
||||
{}
|
||||
|
||||
already_AddRefed<TextureClient> Allocate(TextureForwarder* aAllocator) override
|
||||
already_AddRefed<TextureClient> Allocate(CompositableForwarder* aAllocator) override
|
||||
{
|
||||
uint32_t usage = android::GraphicBuffer::USAGE_SW_READ_OFTEN |
|
||||
android::GraphicBuffer::USAGE_SW_WRITE_OFTEN |
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "mozilla/layers/TextureClient.h"
|
||||
#include "mozilla/layers/GrallocTextureClient.h"
|
||||
#include "mozilla/layers/ImageBridgeChild.h"
|
||||
#include "mozilla/layers/TextureClientRecycleAllocator.h"
|
||||
|
||||
#include "ImageContainer.h"
|
||||
#include "MediaInfo.h"
|
||||
|
|
|
@ -32,6 +32,8 @@ def run_external_media_test(tests, testtype=None, topsrcdir=None, **kwargs):
|
|||
|
||||
from mozlog.structured import commandline
|
||||
|
||||
from argparse import Namespace
|
||||
|
||||
parser = MediaTestArguments()
|
||||
commandline.add_logging_group(parser)
|
||||
|
||||
|
@ -39,7 +41,7 @@ def run_external_media_test(tests, testtype=None, topsrcdir=None, **kwargs):
|
|||
tests = [os.path.join(topsrcdir,
|
||||
'dom/media/test/external/external_media_tests/manifest.ini')]
|
||||
|
||||
args = parser.parse_args(args=tests)
|
||||
args = Namespace(tests=tests)
|
||||
|
||||
for k, v in kwargs.iteritems():
|
||||
setattr(args, k, v)
|
||||
|
|
|
@ -66,6 +66,8 @@ public class ClientsAdapter extends RecyclerView.Adapter<CombinedHistoryItem> im
|
|||
if (sState == null) {
|
||||
sState = new RemoteTabsExpandableListState(GeckoSharedPrefs.forProfile(context));
|
||||
}
|
||||
|
||||
this.setHasStableIds(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -145,6 +147,50 @@ public class ClientsAdapter extends RecyclerView.Adapter<CombinedHistoryItem> im
|
|||
return CombinedHistoryItem.ItemType.itemTypeToViewType(getItemTypeForPosition(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
// RecyclerView.NO_ID is -1, so start our hard-coded IDs at -2.
|
||||
final int NAVIGATION_BACK_ID = -2;
|
||||
final int HIDDEN_DEVICES_ID = -3;
|
||||
|
||||
final String clientGuid;
|
||||
// adapterList is a list of tuples (clientGuid, tabId).
|
||||
final Pair<String, Integer> pair = adapterList.get(position);
|
||||
|
||||
switch (getItemTypeForPosition(position)) {
|
||||
case NAVIGATION_BACK:
|
||||
return NAVIGATION_BACK_ID;
|
||||
|
||||
case HIDDEN_DEVICES:
|
||||
return HIDDEN_DEVICES_ID;
|
||||
|
||||
// For Clients, return hashCode of their GUIDs.
|
||||
case CLIENT:
|
||||
clientGuid = pair.first;
|
||||
return clientGuid.hashCode();
|
||||
|
||||
// For Tabs, return hashCode of their URLs.
|
||||
case CHILD:
|
||||
clientGuid = pair.first;
|
||||
final Integer tabId = pair.second;
|
||||
|
||||
final RemoteClient remoteClient = visibleClients.get(clientGuid);
|
||||
if (remoteClient == null) {
|
||||
return RecyclerView.NO_ID;
|
||||
}
|
||||
|
||||
final RemoteTab remoteTab = remoteClient.tabs.get(tabId);
|
||||
if (remoteTab == null) {
|
||||
return RecyclerView.NO_ID;
|
||||
}
|
||||
|
||||
return remoteTab.url.hashCode();
|
||||
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected Home Panel item type");
|
||||
}
|
||||
}
|
||||
|
||||
public int getClientsCount() {
|
||||
return hiddenClients.size() + visibleClients.size();
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ public class CombinedHistoryAdapter extends RecyclerView.Adapter<CombinedHistory
|
|||
super();
|
||||
sectionHeaders = new SparseArray<>();
|
||||
HistorySectionsHelper.updateRecentSectionOffset(resources, sectionDateRangeArray);
|
||||
this.setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void setHistory(Cursor history) {
|
||||
|
@ -189,6 +190,52 @@ public class CombinedHistoryAdapter extends RecyclerView.Adapter<CombinedHistory
|
|||
return historySize + sectionHeaders.size() + CombinedHistoryPanel.NUM_SMART_FOLDERS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stable ID for each position. Data behind historyCursor is a sorted Combined view.
|
||||
*
|
||||
* @param position view item position for which to generate a stable ID
|
||||
* @return stable ID for given position
|
||||
*/
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
// Two randomly selected large primes used to generate non-clashing IDs.
|
||||
final long PRIME_BOOKMARKS = 32416189867L;
|
||||
final long PRIME_SECTION_HEADERS = 32416187737L;
|
||||
|
||||
// RecyclerView.NO_ID is -1, so let's start from -2 for our hard-coded IDs.
|
||||
final int RECENT_TABS_ID = -2;
|
||||
final int SYNCED_DEVICES_ID = -3;
|
||||
|
||||
switch (getItemTypeForPosition(position)) {
|
||||
case RECENT_TABS:
|
||||
return RECENT_TABS_ID;
|
||||
case SYNCED_DEVICES:
|
||||
return SYNCED_DEVICES_ID;
|
||||
case SECTION_HEADER:
|
||||
// We might have multiple section headers, so we try get unique IDs for them.
|
||||
return position * PRIME_SECTION_HEADERS;
|
||||
case HISTORY:
|
||||
if (!historyCursor.moveToPosition(position)) {
|
||||
return RecyclerView.NO_ID;
|
||||
}
|
||||
|
||||
final int historyIdCol = historyCursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID);
|
||||
final long historyId = historyCursor.getLong(historyIdCol);
|
||||
|
||||
if (historyId != -1) {
|
||||
return historyId;
|
||||
}
|
||||
|
||||
final int bookmarkIdCol = historyCursor.getColumnIndexOrThrow(BrowserContract.Combined.BOOKMARK_ID);
|
||||
final long bookmarkId = historyCursor.getLong(bookmarkIdCol);
|
||||
|
||||
// Avoid clashing with historyId.
|
||||
return bookmarkId * PRIME_BOOKMARKS;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected Home Panel item type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add only the SectionHeaders that have history items within their range to a SparseArray, where the
|
||||
* array index is the position of the header in the history-only (no clients) ordering.
|
||||
|
|
|
@ -526,6 +526,19 @@ public class TopSitesPanel extends HomeFragment {
|
|||
return super.getItem(position + mMaxGridEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* We have to override default getItemId implementation, since for a given position, it returns
|
||||
* value of the _id column. In our case _id is always 0 (see Combined view).
|
||||
*/
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
final int adjustedPosition = position + mMaxGridEntries;
|
||||
final Cursor cursor = getCursor();
|
||||
|
||||
cursor.moveToPosition(adjustedPosition);
|
||||
return getItemIdForTopSitesCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
final int position = cursor.getPosition();
|
||||
|
@ -585,23 +598,16 @@ public class TopSitesPanel extends HomeFragment {
|
|||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* We have to override default getItemId implementation, since for a given position, it returns
|
||||
* value of the _id column. In our case _id is always 0 (see Combined view).
|
||||
*/
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
// We are trying to return stable ids so that Android can recycle views appropriately:
|
||||
// * If we have a history id then we return it
|
||||
// * If we only have a bookmark id then we negate it and return it. We negate it in order
|
||||
// to avoid clashing/conflicting with history ids.
|
||||
|
||||
final Cursor cursor = getCursor();
|
||||
cursor.moveToPosition(position);
|
||||
|
||||
final long historyId = cursor.getLong(cursor.getColumnIndexOrThrow(TopSites.HISTORY_ID));
|
||||
if (historyId != 0) {
|
||||
return historyId;
|
||||
}
|
||||
|
||||
final long bookmarkId = cursor.getLong(cursor.getColumnIndexOrThrow(TopSites.BOOKMARK_ID));
|
||||
return -1 * bookmarkId;
|
||||
return getItemIdForTopSitesCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -965,4 +971,25 @@ public class TopSitesPanel extends HomeFragment {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We are trying to return stable IDs so that Android can recycle views appropriately:
|
||||
* - If we have a history ID then we return it
|
||||
* - If we only have a bookmark ID then we negate it and return it. We negate it in order
|
||||
* to avoid clashing/conflicting with history IDs.
|
||||
*
|
||||
* @param cursorInPosition Cursor already moved to position for which we're getting a stable ID
|
||||
* @return Stable ID for a given cursor
|
||||
*/
|
||||
private static long getItemIdForTopSitesCursor(final Cursor cursorInPosition) {
|
||||
final int historyIdCol = cursorInPosition.getColumnIndexOrThrow(TopSites.HISTORY_ID);
|
||||
final long historyId = cursorInPosition.getLong(historyIdCol);
|
||||
if (historyId != 0) {
|
||||
return historyId;
|
||||
}
|
||||
|
||||
final int bookmarkIdCol = cursorInPosition.getColumnIndexOrThrow(TopSites.BOOKMARK_ID);
|
||||
final long bookmarkId = cursorInPosition.getLong(bookmarkIdCol);
|
||||
return -1 * bookmarkId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -605,6 +605,10 @@ RESTRequest.prototype = {
|
|||
asyncOnChannelRedirect:
|
||||
function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
|
||||
|
||||
let oldSpec = (oldChannel && oldChannel.URI) ? oldChannel.URI.spec : "<undefined>";
|
||||
let newSpec = (newChannel && newChannel.URI) ? newChannel.URI.spec : "<undefined>";
|
||||
this._log.debug("Channel redirect: " + oldSpec + ", " + newSpec + ", " + flags);
|
||||
|
||||
try {
|
||||
newChannel.QueryInterface(Ci.nsIHttpChannel);
|
||||
} catch (ex) {
|
||||
|
|
|
@ -92,5 +92,26 @@ var Authentication = {
|
|||
} catch (error) {
|
||||
throw new Error("signIn() failed with: " + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sign out of Firefox Accounts. It also clears out the device ID, if we find one.
|
||||
*/
|
||||
signOut() {
|
||||
if (Authentication.isLoggedIn) {
|
||||
let user = Authentication.getSignedInUser();
|
||||
if (!user) {
|
||||
throw new Error("Failed to get signed in user!");
|
||||
}
|
||||
let fxc = new FxAccountsClient();
|
||||
let { sessionToken, deviceId } = user;
|
||||
if (deviceId) {
|
||||
Logger.logInfo("Destroying device " + deviceId);
|
||||
Async.promiseSpinningly(fxc.signOutAndDestroyDevice(sessionToken, deviceId, { service: "sync" }));
|
||||
} else {
|
||||
Logger.logError("No device found.");
|
||||
Async.promiseSpinningly(fxc.signOut(sessionToken, { service: "sync" }));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -80,5 +80,9 @@ var Authentication = {
|
|||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
signOut() {
|
||||
Weave.Service.logout();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -97,7 +97,6 @@ var TPS = {
|
|||
_currentPhase: -1,
|
||||
_enabledEngines: null,
|
||||
_errors: 0,
|
||||
_finalPhase: false,
|
||||
_isTracking: false,
|
||||
_operations_pending: 0,
|
||||
_phaseFinished: false,
|
||||
|
@ -164,13 +163,6 @@ var TPS = {
|
|||
break;
|
||||
|
||||
case "quit-application-requested":
|
||||
// Ensure that we eventually wipe the data on the server
|
||||
if (this._errors || !this._phaseFinished || this._finalPhase) {
|
||||
try {
|
||||
this.WipeServer();
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
OBSERVER_TOPICS.forEach(function(topic) {
|
||||
Services.obs.removeObserver(this, topic);
|
||||
}, this);
|
||||
|
@ -586,7 +578,18 @@ var TPS = {
|
|||
Logger.logInfo("mozmill setTest: " + obj.name);
|
||||
},
|
||||
|
||||
|
||||
Cleanup() {
|
||||
try {
|
||||
this.WipeServer();
|
||||
} catch (ex) {
|
||||
Logger.logError("Failed to wipe server: " + Log.exceptionStr(ex));
|
||||
}
|
||||
try {
|
||||
Authentication.signOut();
|
||||
} catch (e) {
|
||||
Logger.logError("Failed to sign out: " + Log.exceptionStr(e));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Use Sync's bookmark validation code to see if we've corrupted the tree.
|
||||
|
@ -656,7 +659,7 @@ var TPS = {
|
|||
RunNextTestAction: function() {
|
||||
try {
|
||||
if (this._currentAction >=
|
||||
this._phaselist["phase" + this._currentPhase].length) {
|
||||
this._phaselist[this._currentPhase].length) {
|
||||
if (this.shouldValidateBookmarks) {
|
||||
// Run bookmark validation and then finish up
|
||||
this.ValidateBookmarks();
|
||||
|
@ -676,7 +679,7 @@ var TPS = {
|
|||
return;
|
||||
}
|
||||
|
||||
let phase = this._phaselist["phase" + this._currentPhase];
|
||||
let phase = this._phaselist[this._currentPhase];
|
||||
let action = phase[this._currentAction];
|
||||
Logger.logInfo("starting action: " + action[0].name);
|
||||
action[0].apply(this, action.slice(1));
|
||||
|
@ -773,14 +776,21 @@ var TPS = {
|
|||
// parse the test file
|
||||
Services.scriptloader.loadSubScript(file, this);
|
||||
this._currentPhase = phase;
|
||||
let this_phase = this._phaselist["phase" + this._currentPhase];
|
||||
if (this._currentPhase.startsWith("cleanup-")) {
|
||||
let profileToClean = Cc["@mozilla.org/toolkit/profile-service;1"]
|
||||
.getService(Ci.nsIToolkitProfileService)
|
||||
.selectedProfile.name;
|
||||
this.phases[this._currentPhase] = profileToClean;
|
||||
this.Phase(this._currentPhase, [[this.Cleanup]]);
|
||||
}
|
||||
let this_phase = this._phaselist[this._currentPhase];
|
||||
|
||||
if (this_phase == undefined) {
|
||||
this.DumpError("invalid phase " + this._currentPhase);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.phases["phase" + this._currentPhase] == undefined) {
|
||||
if (this.phases[this._currentPhase] == undefined) {
|
||||
this.DumpError("no profile defined for phase " + this._currentPhase);
|
||||
return;
|
||||
}
|
||||
|
@ -800,26 +810,10 @@ var TPS = {
|
|||
}
|
||||
}
|
||||
}
|
||||
Logger.logInfo("Starting phase " + this._currentPhase);
|
||||
|
||||
Logger.logInfo("Starting phase " + parseInt(phase, 10) + "/" +
|
||||
Object.keys(this._phaselist).length);
|
||||
|
||||
Logger.logInfo("setting client.name to " + this.phases["phase" + this._currentPhase]);
|
||||
Weave.Svc.Prefs.set("client.name", this.phases["phase" + this._currentPhase]);
|
||||
|
||||
// TODO Phases should be defined in a data type that has strong
|
||||
// ordering, not by lexical sorting.
|
||||
let currentPhase = parseInt(this._currentPhase, 10);
|
||||
|
||||
// Login at the beginning of the test.
|
||||
if (currentPhase <= 1) {
|
||||
this_phase.unshift([this.Login]);
|
||||
}
|
||||
|
||||
// Wipe the server at the end of the final test phase.
|
||||
if (currentPhase >= Object.keys(this.phases).length) {
|
||||
this._finalPhase = true;
|
||||
}
|
||||
Logger.logInfo("setting client.name to " + this.phases[this._currentPhase]);
|
||||
Weave.Svc.Prefs.set("client.name", this.phases[this._currentPhase]);
|
||||
|
||||
// If a custom server was specified, set it now
|
||||
if (this.config["serverURL"]) {
|
||||
|
@ -859,6 +853,10 @@ var TPS = {
|
|||
* Array of functions/actions to perform.
|
||||
*/
|
||||
Phase: function Test__Phase(phasename, fnlist) {
|
||||
if (Object.keys(this._phaselist).length === 0) {
|
||||
// This is the first phase, add that we need to login.
|
||||
fnlist.unshift([this.Login]);
|
||||
}
|
||||
this._phaselist[phasename] = fnlist;
|
||||
},
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ def setup_argument_parser_update():
|
|||
|
||||
def run_firefox_ui_test(testtype=None, topsrcdir=None, **kwargs):
|
||||
from mozlog.structured import commandline
|
||||
from argparse import Namespace
|
||||
import firefox_ui_harness
|
||||
|
||||
if testtype == 'functional':
|
||||
|
@ -67,8 +68,7 @@ def run_firefox_ui_test(testtype=None, topsrcdir=None, **kwargs):
|
|||
kwargs['logger'] = commandline.setup_logging('Firefox UI - {} Tests'.format(testtype),
|
||||
{"mach": sys.stdout})
|
||||
|
||||
# pass tests to parse_args to avoid rereading sys.argv
|
||||
args = parser.parse_args(args=kwargs['tests'])
|
||||
args = Namespace()
|
||||
|
||||
for k, v in kwargs.iteritems():
|
||||
setattr(args, k, v)
|
||||
|
|
|
@ -375,12 +375,12 @@ class BaseMarionetteArguments(ArgumentParser):
|
|||
|
||||
self.argument_containers.append(container)
|
||||
|
||||
def parse_args(self, args=None, values=None):
|
||||
args = ArgumentParser.parse_args(self, args, values)
|
||||
def parse_known_args(self, args=None, namespace=None):
|
||||
args, remainder = ArgumentParser.parse_known_args(self, args, namespace)
|
||||
for container in self.argument_containers:
|
||||
if hasattr(container, 'parse_args_handler'):
|
||||
container.parse_args_handler(args)
|
||||
return args
|
||||
return (args, remainder)
|
||||
|
||||
def _get_preferences(self, prefs_files, prefs_args):
|
||||
"""
|
||||
|
@ -397,11 +397,15 @@ class BaseMarionetteArguments(ArgumentParser):
|
|||
separator = ':'
|
||||
cli_prefs = []
|
||||
if prefs_args:
|
||||
misformatted = []
|
||||
for pref in prefs_args:
|
||||
if separator not in pref:
|
||||
continue
|
||||
cli_prefs.append(pref.split(separator, 1))
|
||||
|
||||
misformatted.append(pref)
|
||||
else:
|
||||
cli_prefs.append(pref.split(separator, 1))
|
||||
if misformatted:
|
||||
self._print_message("Warning: Ignoring preferences not in key{}value format: {}\n"
|
||||
.format(separator, ", ".join(misformatted)))
|
||||
# string preferences
|
||||
prefs.add(cli_prefs, cast=True)
|
||||
|
||||
|
@ -409,17 +413,14 @@ class BaseMarionetteArguments(ArgumentParser):
|
|||
|
||||
def verify_usage(self, args):
|
||||
if not args.tests:
|
||||
print 'must specify one or more test files, manifests, or directories'
|
||||
sys.exit(1)
|
||||
self.error('You must specify one or more test files, manifests, or directories.')
|
||||
|
||||
for path in args.tests:
|
||||
if not os.path.exists(path):
|
||||
print '{0} does not exist'.format(path)
|
||||
sys.exit(1)
|
||||
missing_tests = [path for path in args.tests if not os.path.exists(path)]
|
||||
if missing_tests:
|
||||
self.error("Test file(s) not found: " + " ".join([path for path in missing_tests]))
|
||||
|
||||
if not args.address and not args.binary:
|
||||
print 'must specify --binary, or --address'
|
||||
sys.exit(1)
|
||||
self.error('You must specify --binary, or --address')
|
||||
|
||||
if args.total_chunks is not None and args.this_chunk is None:
|
||||
self.error('You must specify which chunk to run.')
|
||||
|
@ -428,7 +429,7 @@ class BaseMarionetteArguments(ArgumentParser):
|
|||
self.error('You must specify how many chunks to split the tests into.')
|
||||
|
||||
if args.total_chunks is not None:
|
||||
if not 1 <= args.total_chunks:
|
||||
if not 1 < args.total_chunks:
|
||||
self.error('Total chunks must be greater than 1.')
|
||||
if not 1 <= args.this_chunk <= args.total_chunks:
|
||||
self.error('Chunk to run must be between 1 and %s.' % args.total_chunks)
|
||||
|
|
|
@ -40,8 +40,9 @@ def run_marionette(tests, testtype=None, address=None, binary=None, topsrcdir=No
|
|||
tests = [os.path.join(topsrcdir,
|
||||
'testing/marionette/harness/marionette/tests/unit-tests.ini')]
|
||||
|
||||
args = parser.parse_args(args=tests)
|
||||
args = argparse.Namespace(tests=tests)
|
||||
|
||||
args.address = address
|
||||
args.binary = binary
|
||||
|
||||
for k, v in kwargs.iteritems():
|
||||
|
@ -83,7 +84,7 @@ def run_session(tests, testtype=None, address=None, binary=None, topsrcdir=None,
|
|||
tests = [os.path.join(topsrcdir,
|
||||
'testing/marionette/harness/session/tests/unit-tests.ini')]
|
||||
|
||||
args = parser.parse_args(args=tests)
|
||||
args = argparse.Namespace(tests=tests)
|
||||
|
||||
args.binary = binary
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import re
|
|||
class TPSTestPhase(object):
|
||||
|
||||
lineRe = re.compile(
|
||||
r'^(.*?)test phase (?P<matchphase>\d+): (?P<matchstatus>.*)$')
|
||||
r'^(.*?)test phase (?P<matchphase>[^\s]+): (?P<matchstatus>.*)$')
|
||||
|
||||
def __init__(self, phase, profile, testname, testpath, logfile, env,
|
||||
firefoxRunner, logfn, ignore_unused_engines=False):
|
||||
|
@ -23,12 +23,6 @@ class TPSTestPhase(object):
|
|||
self._status = None
|
||||
self.errline = ''
|
||||
|
||||
@property
|
||||
def phasenum(self):
|
||||
match = re.match('.*?(\d+)', self.phase)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._status if self._status else 'unknown'
|
||||
|
@ -36,7 +30,7 @@ class TPSTestPhase(object):
|
|||
def run(self):
|
||||
# launch Firefox
|
||||
args = [ '-tps', self.testpath,
|
||||
'-tpsphase', self.phasenum,
|
||||
'-tpsphase', self.phase,
|
||||
'-tpslogfile', self.logfile ]
|
||||
|
||||
if self.ignore_unused_engines:
|
||||
|
@ -63,7 +57,7 @@ class TPSTestPhase(object):
|
|||
# look for the status of the current phase
|
||||
match = self.lineRe.match(line)
|
||||
if match:
|
||||
if match.group('matchphase') == self.phasenum:
|
||||
if match.group('matchphase') == self.phase:
|
||||
self._status = match.group('matchstatus')
|
||||
break
|
||||
|
||||
|
|
|
@ -203,6 +203,25 @@ class TPSTestRunner(object):
|
|||
for f in files:
|
||||
zip.write(os.path.join(root, f), os.path.join(dir, f))
|
||||
|
||||
def handle_phase_failure(self, profiles):
|
||||
for profile in profiles:
|
||||
self.log('\nDumping sync log for profile %s\n' % profiles[profile].profile)
|
||||
for root, dirs, files in os.walk(os.path.join(profiles[profile].profile, 'weave', 'logs')):
|
||||
for f in files:
|
||||
weavelog = os.path.join(profiles[profile].profile, 'weave', 'logs', f)
|
||||
if os.access(weavelog, os.F_OK):
|
||||
with open(weavelog, 'r') as fh:
|
||||
for line in fh:
|
||||
possible_time = line[0:13]
|
||||
if len(possible_time) == 13 and possible_time.isdigit():
|
||||
time_ms = int(possible_time)
|
||||
formatted = time.strftime('%Y-%m-%d %H:%M:%S',
|
||||
time.localtime(time_ms / 1000))
|
||||
self.log('%s.%03d %s' % (
|
||||
formatted, time_ms % 1000, line[14:] ))
|
||||
else:
|
||||
self.log(line)
|
||||
|
||||
def run_single_test(self, testdir, testname):
|
||||
testpath = os.path.join(testdir, testname)
|
||||
self.log("Running test %s\n" % testname, True)
|
||||
|
@ -251,30 +270,31 @@ class TPSTestRunner(object):
|
|||
phaselist = sorted(phaselist, key=lambda phase: phase.phase)
|
||||
|
||||
# run each phase in sequence, aborting at the first failure
|
||||
failed = False
|
||||
for phase in phaselist:
|
||||
phase.run()
|
||||
|
||||
# if a failure occurred, dump the entire sync log into the test log
|
||||
if phase.status != 'PASS':
|
||||
for profile in profiles:
|
||||
self.log('\nDumping sync log for profile %s\n' % profiles[profile].profile)
|
||||
for root, dirs, files in os.walk(os.path.join(profiles[profile].profile, 'weave', 'logs')):
|
||||
for f in files:
|
||||
weavelog = os.path.join(profiles[profile].profile, 'weave', 'logs', f)
|
||||
if os.access(weavelog, os.F_OK):
|
||||
with open(weavelog, 'r') as fh:
|
||||
for line in fh:
|
||||
possible_time = line[0:13]
|
||||
if len(possible_time) == 13 and possible_time.isdigit():
|
||||
time_ms = int(possible_time)
|
||||
formatted = time.strftime('%Y-%m-%d %H:%M:%S',
|
||||
time.localtime(time_ms / 1000))
|
||||
self.log('%s.%03d %s' % (
|
||||
formatted, time_ms % 1000, line[14:] ))
|
||||
else:
|
||||
self.log(line)
|
||||
failed = True
|
||||
break;
|
||||
|
||||
for profilename in profiles:
|
||||
cleanup_phase = TPSTestPhase(
|
||||
'cleanup-' + profilename,
|
||||
profiles[profilename], testname,
|
||||
tmpfile.filename,
|
||||
self.logfile,
|
||||
self.env,
|
||||
self.firefoxRunner,
|
||||
self.log)
|
||||
|
||||
cleanup_phase.run()
|
||||
if cleanup_phase.status != 'PASS':
|
||||
failed = True
|
||||
# Keep going to run the remaining cleanup phases.
|
||||
|
||||
if failed:
|
||||
self.handle_phase_failure(profiles)
|
||||
|
||||
# grep the log for FF and sync versions
|
||||
f = open(self.logfile)
|
||||
logdata = f.read()
|
||||
|
@ -331,8 +351,6 @@ class TPSTestRunner(object):
|
|||
self.log(logstr, True)
|
||||
for phase in phaselist:
|
||||
print "\t%s: %s" % (phase.phase, phase.status)
|
||||
if phase.status == 'FAIL':
|
||||
break
|
||||
|
||||
return resultdata
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче