Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-07-01 11:18:56 +02:00
Родитель 3e441f64b1 f93a6047fb
Коммит 199d6e3b27
57 изменённых файлов: 914 добавлений и 690 удалений

Просмотреть файл

@ -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 pages 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"

4
dom/media/test/external/mach_commands.py поставляемый
Просмотреть файл

@ -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