Merge fx-team to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2016-08-25 08:12:26 -04:00
commit bfdd703c49
41 changed files with 1456 additions and 610 deletions

View File

@ -762,6 +762,7 @@ html|*#fullscreen-exit-button {
-moz-user-focus: normal;
}
.blocked-permission-icon:not([showing]),
.notification-anchor-icon:not([showing]) {
display: none;
}

View File

@ -692,19 +692,19 @@
onclick="PageProxyClickHandler(event);"/>
<image id="sharing-icon" mousethrough="always"/>
<box id="blocked-permissions-container" align="center" tooltiptext="">
<image data-permission-id="geo" class="notification-anchor-icon geo-icon blocked" role="button"
<image data-permission-id="geo" class="blocked-permission-icon geo-icon" role="button"
aria-label="&urlbar.geolocationNotificationAnchor.label;"/>
<image data-permission-id="desktop-notification" class="notification-anchor-icon desktop-notification-icon blocked" role="button"
<image data-permission-id="desktop-notification" class="blocked-permission-icon desktop-notification-icon" role="button"
aria-label="&urlbar.webNotsNotificationAnchor3.label;"/>
<image data-permission-id="camera" class="notification-anchor-icon camera-icon blocked" role="button"
<image data-permission-id="camera" class="blocked-permission-icon camera-icon" role="button"
aria-label="&urlbar.webRTCShareDevicesNotificationAnchor.label;"/>
<image data-permission-id="indexedDB" class="notification-anchor-icon indexedDB-icon blocked" role="button"
<image data-permission-id="indexedDB" class="blocked-permission-icon indexedDB-icon" role="button"
aria-label="&urlbar.indexedDBNotificationAnchor.label;"/>
<image data-permission-id="microphone" class="notification-anchor-icon microphone-icon blocked" role="button"
<image data-permission-id="microphone" class="blocked-permission-icon microphone-icon" role="button"
aria-label="&urlbar.webRTCShareMicrophoneNotificationAnchor.label;"/>
<image data-permission-id="screen" class="notification-anchor-icon screen-icon blocked" role="button"
<image data-permission-id="screen" class="blocked-permission-icon screen-icon" role="button"
aria-label="&urlbar.webRTCShareScreenNotificationAnchor.label;"/>
<image data-permission-id="pointerLock" class="notification-anchor-icon pointerLock-icon blocked" role="button"
<image data-permission-id="pointerLock" class="blocked-permission-icon pointerLock-icon" role="button"
aria-label="&urlbar.pointerLockNotificationAnchor.label;"/>
</box>
<box id="notification-popup-box"

View File

@ -186,8 +186,6 @@ add_task(function* testPermissionIcons() {
let geoIcon = gIdentityHandler._identityBox.querySelector("[data-permission-id='geo']");
ok(geoIcon.hasAttribute("showing"), "blocked permission icon is shown");
ok(geoIcon.classList.contains("blocked"),
"blocked permission icon is shown as blocked");
let cameraIcon = gIdentityHandler._identityBox.querySelector("[data-permission-id='camera']");
ok(!cameraIcon.hasAttribute("showing"),

View File

@ -11,13 +11,17 @@
padding-inline-end: 5px;
}
.notification-anchor-icon,
.blocked-permission-icon {
width: 16px;
height: 16px;
margin-inline-start: 2px;
}
/* This class can be used alone or in combination with the class defining the
type of icon displayed. This rule must be defined before the others in order
for its list-style-image to be overridden. */
.notification-anchor-icon {
width: 16px;
height: 16px;
margin-inline-start: 2px;
%ifdef MOZ_WIDGET_GTK
list-style-image: url(moz-icon://stock/gtk-dialog-info?size=16);
%else
@ -76,7 +80,7 @@
list-style-image: url(chrome://browser/skin/notification-icons.svg#desktop-notification);
}
.desktop-notification-icon.blocked {
.desktop-notification-icon.blocked-permission-icon {
list-style-image: url(chrome://browser/skin/notification-icons.svg#desktop-notification-blocked);
}
@ -90,7 +94,7 @@
%endif
}
.geo-icon.blocked {
.geo-icon.blocked-permission-icon {
%ifdef XP_MACOSX
list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-osx-blocked);
%elif defined(MOZ_WIDGET_GTK)
@ -115,7 +119,7 @@
list-style-image: url(chrome://browser/skin/notification-icons.svg#indexedDB);
}
.indexedDB-icon.blocked {
.indexedDB-icon.blocked-permission-icon {
list-style-image: url(chrome://browser/skin/notification-icons.svg#indexedDB-blocked);
}
@ -137,7 +141,7 @@
list-style-image: url(chrome://browser/skin/notification-icons.svg#camera);
}
.camera-icon.blocked {
.camera-icon.blocked-permission-icon {
list-style-image: url(chrome://browser/skin/notification-icons.svg#camera-blocked);
}
@ -145,7 +149,7 @@
list-style-image: url(chrome://browser/skin/notification-icons.svg#microphone);
}
.microphone-icon.blocked {
.microphone-icon.blocked-permission-icon {
list-style-image: url(chrome://browser/skin/notification-icons.svg#microphone-blocked);
}
@ -158,7 +162,7 @@
list-style-image: url(chrome://browser/skin/notification-icons.svg#screen);
}
.screen-icon.blocked {
.screen-icon.blocked-permission-icon {
list-style-image: url(chrome://browser/skin/notification-icons.svg#screen-blocked);
}
@ -167,7 +171,7 @@
list-style-image: url(chrome://browser/skin/notification-icons.svg#pointerLock);
}
.pointerLock-icon.blocked {
.pointerLock-icon.blocked-permission-icon {
list-style-image: url(chrome://browser/skin/notification-icons.svg#pointerLock-blocked);
}

View File

@ -34,13 +34,16 @@ function SourceMapService(target) {
target.on("source-updated", this._onSourceUpdated);
target.on("navigate", this.reset);
target.on("will-navigate", this.reset);
target.on("close", this.destroy);
}
/**
* Clears the store containing the cached promised locations
*/
SourceMapService.prototype.reset = function () {
// Guard to prevent clearing the store when it is not initialized yet.
if (!this._locationStore) {
return;
}
this._locationStore.clear();
this._isNotSourceMapped.clear();
};
@ -84,9 +87,10 @@ SourceMapService.prototype.unsubscribe = function (location, callback) {
// Check to see if the store exists before attempting to clear a location
// Sometimes un-subscribe happens during the destruction cascades and this
// condition is to protect against that. Could be looked into in the future.
if (this._locationStore) {
this._locationStore.clearByURL(location.url);
if (!this._locationStore) {
return;
}
this._locationStore.clearByURL(location.url);
};
/**

View File

@ -11,6 +11,8 @@ const {EyeDropper, HighlighterEnvironment} = require("devtools/server/actors/hig
/* eslint-enable mozilla/reject-some-requires */
const Telemetry = require("devtools/client/shared/telemetry");
const windowEyeDroppers = new WeakMap();
exports.items = [{
item: "command",
runAt: "client",
@ -48,9 +50,28 @@ exports.items = [{
name: "frommenu",
type: "boolean",
hidden: true
}, {
name: "hide",
type: "boolean",
hidden: true
}]
}],
exec: function (args, context) {
exec: function* (args, context) {
if (args.hide) {
context.updateExec("eyedropper_server_hide").catch(e => console.error(e));
return;
}
// If the inspector is already picking a color from the page, cancel it.
let target = context.environment.target;
let toolbox = gDevTools.getToolbox(target);
if (toolbox) {
let inspector = toolbox.getPanel("inspector");
if (inspector) {
yield inspector.hideEyeDropper();
}
}
let telemetry = new Telemetry();
telemetry.toolOpened(args.frommenu ? "menueyedropper" : "eyedropper");
context.updateExec("eyedropper_server").catch(e => console.error(e));
@ -61,15 +82,33 @@ exports.items = [{
name: "eyedropper_server",
hidden: true,
exec: function (args, {environment}) {
let env = new HighlighterEnvironment();
env.initFromWindow(environment.window);
let eyeDropper = new EyeDropper(env);
let eyeDropper = windowEyeDroppers.get(environment.window);
if (!eyeDropper) {
let env = new HighlighterEnvironment();
env.initFromWindow(environment.window);
eyeDropper = new EyeDropper(env);
eyeDropper.once("hidden", () => {
eyeDropper.destroy();
env.destroy();
windowEyeDroppers.delete(environment.window);
});
windowEyeDroppers.set(environment.window, eyeDropper);
}
eyeDropper.show(environment.document.documentElement, {copyOnSelect: true});
eyeDropper.once("hidden", () => {
eyeDropper.destroy();
env.destroy();
});
}
}, {
item: "command",
runAt: "server",
name: "eyedropper_server_hide",
hidden: true,
exec: function (args, {environment}) {
let eyeDropper = windowEyeDroppers.get(environment.window);
if (eyeDropper) {
eyeDropper.hide();
}
}
}];

View File

@ -1304,7 +1304,7 @@ InspectorPanel.prototype = {
this.telemetry.toolOpened("toolbareyedropper");
this.eyeDropperButton.setAttribute("checked", "true");
this.startEyeDropperListeners();
return this.inspector.pickColorFromPage({copyOnSelect: true})
return this.inspector.pickColorFromPage(this.toolbox, {copyOnSelect: true})
.catch(e => console.error(e));
},

View File

@ -43,6 +43,7 @@ function test() {
emptyText: "This is dummy empty text",
highlightUpdated: true,
removableColumns: true,
wrapTextInElements: true,
});
startTests();
});
@ -143,34 +144,48 @@ var testMouseInteraction = Task.async(function* () {
ok(!table.selectedRow, "Nothing should be selected beforehand");
let event = table.once(TableWidget.EVENTS.ROW_SELECTED);
let node = table.tbody.firstChild.firstChild.children[1];
let firstColumnFirstRowCell = table.tbody.firstChild.firstChild.children[1];
info("clicking on the first row");
ok(!node.classList.contains("theme-selected"),
ok(!firstColumnFirstRowCell.classList.contains("theme-selected"),
"Node should not have selected class before clicking");
click(node);
click(firstColumnFirstRowCell);
let id = yield event;
ok(node.classList.contains("theme-selected"),
ok(firstColumnFirstRowCell.classList.contains("theme-selected"),
"Node has selected class after click");
is(id, "id1", "Correct row was selected");
info("clicking on third row to select it");
info("clicking on second row to select it");
event = table.once(TableWidget.EVENTS.ROW_SELECTED);
let node2 = table.tbody.firstChild.firstChild.children[3];
let firstColumnSecondRowCell = table.tbody.firstChild.firstChild.children[2];
// node should not have selected class
ok(!node2.classList.contains("theme-selected"),
ok(!firstColumnSecondRowCell.classList.contains("theme-selected"),
"New node should not have selected class before clicking");
click(node2);
click(firstColumnSecondRowCell);
id = yield event;
ok(node2.classList.contains("theme-selected"),
ok(firstColumnSecondRowCell.classList.contains("theme-selected"),
"New node has selected class after clicking");
is(id, "id3", "Correct table path is emitted for new node");
isnot(node, node2, "Old and new node are different");
ok(!node.classList.contains("theme-selected"),
is(id, "id2", "Correct table path is emitted for new node");
isnot(firstColumnFirstRowCell, firstColumnSecondRowCell,
"Old and new node are different");
ok(!firstColumnFirstRowCell.classList.contains("theme-selected"),
"Old node should not have selected class after the click on new node");
info("clicking on the third row cell content to select third row");
event = table.once(TableWidget.EVENTS.ROW_SELECTED);
let firstColumnThirdRowCell = table.tbody.firstChild.firstChild.children[3];
let firstColumnThirdRowCellInnerNode = firstColumnThirdRowCell.querySelector("span");
// node should not have selected class
ok(!firstColumnThirdRowCell.classList.contains("theme-selected"),
"New node should not have selected class before clicking");
click(firstColumnThirdRowCellInnerNode);
id = yield event;
ok(firstColumnThirdRowCell.classList.contains("theme-selected"),
"New node has selected class after clicking the cell content");
is(id, "id3", "Correct table path is emitted for new node");
// clicking on table header to sort by it
event = table.once(TableWidget.EVENTS.COLUMN_SORTED);
node = table.tbody.children[6].firstChild.children[0];
let node = table.tbody.children[6].firstChild.children[0];
info("clicking on the 4th coulmn header to sort the table by it");
ok(!node.hasAttribute("sorted"),
"Node should not have sorted attribute before clicking");

View File

@ -1428,7 +1428,7 @@ Column.prototype = {
return;
}
let dataid = target.getAttribute("data-id");
let dataid = closest.getAttribute("data-id");
this.table.emit(EVENTS.ROW_SELECTED, dataid);
}
},

View File

@ -776,7 +776,14 @@ Heritage.extend(SwatchBasedEditorTooltip.prototype, {
_openEyeDropper: function () {
let {inspector, toolbox, telemetry} = this.inspector;
telemetry.toolOpened("pickereyedropper");
inspector.pickColorFromPage({copyOnSelect: false}).catch(e => console.error(e));
inspector.pickColorFromPage(toolbox, {copyOnSelect: false}).then(() => {
this.eyedropperOpen = true;
// close the colorpicker tooltip so that only the eyedropper is open.
this.hide();
this.tooltip.emit("eyedropper-opened");
}, e => console.error(e));
inspector.once("color-picked", color => {
toolbox.win.focus();
@ -787,13 +794,6 @@ Heritage.extend(SwatchBasedEditorTooltip.prototype, {
inspector.once("color-pick-canceled", () => {
this._onEyeDropperDone();
});
this.eyedropperOpen = true;
// close the colorpicker tooltip so that only the eyedropper is open.
this.hide();
this.tooltip.emit("eyedropper-opened");
},
_onEyeDropperDone: function () {

View File

@ -232,3 +232,471 @@ input[type="checkbox"]:-moz-focusring,
checkbox:-moz-focusring {
outline: none;
}
/* Toolbar buttons */
.devtools-menulist,
.devtools-toolbarbutton,
.devtools-button {
-moz-appearance: none;
background: transparent;
min-height: 18px;
text-shadow: none;
border: none;
border-radius: 0;
color: var(--theme-body-color);
transition: background 0.05s ease-in-out;
}
.devtools-menulist,
.devtools-toolbarbutton {
-moz-box-align: center;
min-width: 78px;
padding: 1px;
margin: 2px 1px;
}
.devtools-toolbarbutton:not([label]) > .toolbarbutton-icon,
.devtools-button::before {
width: 16px;
height: 16px;
transition: opacity 0.05s ease-in-out;
}
/* HTML buttons */
.devtools-button {
margin: 2px 1px;
padding: 1px;
min-width: 32px;
/* The icon is absolutely positioned in the button using ::before */
position: relative;
}
.devtools-button::before {
content: "";
display: block;
position: absolute;
left: 50%;
top: 50%;
margin: -8px 0 0 -8px;
background-size: cover;
background-repeat: no-repeat;
transition: opacity 0.05s ease-in-out;
}
.devtools-button:-moz-focusring {
outline: none;
}
/* Standalone buttons */
.devtools-button[standalone],
.devtools-button[data-standalone],
.devtools-toolbarbutton[standalone],
.devtools-toolbarbutton[data-standalone] {
border-width: 1px;
border-style: solid;
min-height: 32px;
background-color: var(--theme-toolbar-background);
}
.devtools-toolbarbutton[standalone], .devtools-toolbarbutton[data-standalone] {
margin-inline-end: 5px;
}
.devtools-toolbarbutton[label][standalone] {
min-height: 2em;
}
.devtools-menulist,
.devtools-toolbarbutton,
.devtools-button {
border-color: var(--toolbar-button-border-color);
}
/* Icon button styles */
.devtools-toolbarbutton:not([label]),
.devtools-toolbarbutton[text-as-image] {
min-width: 32px;
}
.devtools-toolbarbutton:not([label]) > .toolbarbutton-text {
display: none;
}
.devtools-toolbarbutton > .toolbarbutton-icon {
margin: 0;
}
/* Menu button styles (eg. web console filters) */
.devtools-toolbarbutton[type=menu-button] > .toolbarbutton-menubutton-button {
-moz-appearance: none;
color: inherit;
border-width: 0;
-moz-box-orient: horizontal;
padding: 0;
}
.devtools-toolbarbutton[type=menu-button] {
padding: 0 1px;
-moz-box-align: stretch;
}
.devtools-toolbarbutton > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
margin-inline-end: 4px;
}
.devtools-menulist > .menulist-dropmarker {
-moz-appearance: none;
display: -moz-box;
list-style-image: url("chrome://devtools/skin/images/dropmarker.svg");
-moz-box-align: center;
min-width: 16px;
}
.devtools-toolbarbutton[type=menu] > .toolbarbutton-menu-dropmarker,
.devtools-toolbarbutton[type=menu-button] > .toolbarbutton-menubutton-dropmarker {
-moz-appearance: none !important;
list-style-image: url("chrome://devtools/skin/images/dropmarker.svg");
-moz-box-align: center;
padding: 0 3px;
}
/* Icon-only buttons */
.devtools-button:empty::before,
.devtools-toolbarbutton:not([label]):not([disabled]) > image {
opacity: 0.8;
}
.devtools-button:hover:empty::before,
.devtools-button[checked]:empty::before,
.devtools-button[open]:empty::before,
.devtools-toolbarbutton:not([label]):hover > image,
.devtools-toolbarbutton:not([label])[checked=true] > image,
.devtools-toolbarbutton:not([label])[open=true] > image {
opacity: 1;
}
.devtools-button:disabled,
.devtools-button[disabled],
.devtools-toolbarbutton[disabled] {
opacity: 0.5 !important;
}
.devtools-button[checked]:empty::before,
.devtools-button[open]:empty::before,
.devtools-button.checked::before,
.devtools-toolbarbutton:not([label])[checked=true] > image,
.devtools-toolbarbutton:not([label])[open=true] > image {
filter: var(--checked-icon-filter);
}
/* Icon-and-text buttons */
.devtools-toolbarbutton.icon-and-text .toolbarbutton-text {
margin-inline-start: .5em !important;
font-weight: 600;
}
/* Text-only buttons */
.theme-light .devtools-toolbarbutton[label]:not([text-as-image]):not([type=menu-button]),
.theme-light .devtools-toolbarbutton[data-text-only] {
background-color: var(--toolbar-tab-hover);
}
.theme-dark .devtools-toolbarbutton[label]:not([text-as-image]):not([type=menu-button]),
.theme-dark .devtools-toolbarbutton[data-text-only] {
background-color: rgba(0, 0, 0, .2); /* Splitter */
}
/* Text-only button states */
.theme-dark .devtools-button:not(:empty):not([disabled]):hover,
.theme-dark .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover {
background: rgba(0, 0, 0, .3); /* Splitters */
}
.theme-light .devtools-button:not(:empty):not([disabled]):hover,
.theme-light .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover {
background: rgba(170, 170, 170, .3); /* Splitters */
}
.theme-dark .devtools-button:not(:empty):not([disabled]):hover:active,
.theme-dark .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover:active {
background: rgba(0, 0, 0, .4); /* Splitters */
}
.theme-light .devtools-button:not(:empty):not([disabled]):hover:active,
.theme-light .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover:active {
background: var(--toolbar-tab-hover-active);
}
.theme-dark .devtools-toolbarbutton:not([disabled])[label][checked=true],
.theme-dark .devtools-toolbarbutton:not([disabled])[label][open],
.theme-dark .devtools-button:not(:empty)[checked=true] {
background: var(--theme-selection-background-semitransparent);
color: var(--theme-selection-color);
}
.theme-light .devtools-toolbarbutton:not([disabled])[label][checked=true],
.theme-light .devtools-toolbarbutton:not([disabled])[label][open],
.theme-light .devtools-button:not(:empty)[checked=true] {
background: rgba(76, 158, 217, .3); /* Select highlight blue */
}
:root {
--clear-icon-url: url("chrome://devtools/skin/images/clear.svg");
}
.devtools-button.devtools-clear-icon::before {
background-image: var(--clear-icon-url);
}
.devtools-button.devtools-filter-icon::before {
background-image: var(--filter-image);
}
.devtools-toolbarbutton.devtools-clear-icon {
list-style-image: var(--clear-icon-url);
}
.devtools-option-toolbarbutton {
list-style-image: var(--tool-options-image);
}
.devtools-toolbarbutton-group > .devtools-toolbarbutton:last-child {
margin-inline-end: 0;
}
.devtools-toolbarbutton-group + .devtools-toolbarbutton {
margin-inline-start: 3px;
}
.devtools-separator + .devtools-toolbarbutton {
margin-inline-start: 1px;
}
/*
* Filter buttons
* @TODO : Fix when https://bugzilla.mozilla.org/show_bug.cgi?id=1255116 lands
*/
.menu-filter-button {
-moz-appearance: none;
background: rgba(128,128,128,0.1);
border: none;
border-radius: 2px;
min-width: 0;
padding: 0 5px;
margin: 2px;
color: var(--theme-body-color);
}
.menu-filter-button:hover {
background: rgba(128,128,128,0.2);
}
.menu-filter-button:hover:active {
background-color: var(--theme-selection-background-semitransparent);
}
.menu-filter-button:not(:active).checked {
background-color: var(--theme-selection-background);
color: var(--theme-selection-color);
}
/* Text input */
.devtools-textinput,
.devtools-searchinput,
.devtools-filterinput {
-moz-appearance: none;
margin: 1px 3px;
border: 1px solid;
border-radius: 2px;
padding: 4px 6px;
border-color: var(--theme-splitter-color);
font: message-box;
}
:root[platform="mac"] .devtools-textinput,
:root[platform="mac"] .devtools-searchinput,
:root[platform="mac"] .devtools-filterinput {
border-radius: 20px;
}
.devtools-searchinput,
.devtools-filterinput {
padding: 0;
padding-inline-start: 22px;
padding-inline-end: 4px;
background-position: 8px center;
background-size: 11px 11px;
background-repeat: no-repeat;
font-size: inherit;
}
.devtools-searchinput {
background-image: var(--magnifying-glass-image);
}
.devtools-filterinput {
background-image: url(images/filter.svg#filterinput);
}
.devtools-searchinput:-moz-locale-dir(rtl),
.devtools-searchinput:dir(rtl),
.devtools-filterinput:-moz-locale-dir(rtl),
.devtools-filterinput:dir(rtl) {
background-position: calc(100% - 8px) center;
}
.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-icon,
.devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-icon {
visibility: hidden;
}
.devtools-searchinput .textbox-input::-moz-placeholder,
.devtools-filterinput .textbox-input::-moz-placeholder {
font-style: normal;
}
.devtools-plaininput {
border-color: transparent;
background-color: transparent;
}
.theme-dark .devtools-plaininput {
color: var(--theme-highlight-gray);
}
/* Searchbox is a div container element for a search input element */
.devtools-searchbox {
display: flex;
flex: 1;
height: 23px;
position: relative;
padding: 0 3px;
}
/* The spacing is accomplished with a padding on the searchbox */
.devtools-searchbox > .devtools-textinput,
.devtools-searchbox > .devtools-searchinput,
.devtools-searchbox > .devtools-filterinput {
margin-left: 0;
margin-right: 0;
}
.devtools-searchbox > .devtools-textinput:-moz-focusring,
.devtools-searchbox > .devtools-searchinput:-moz-focusring,
.devtools-searchbox > .devtools-filterinput:-moz-focusring {
border-color: var(--theme-focus-border-color-textbox);
box-shadow: var(--theme-focus-box-shadow-textbox);
transition: all 0.2s ease-in-out;
outline: none;
}
/* Don't add 'double spacing' for inputs that are at beginning / end
of a toolbar (since the toolbar has it's own spacing). */
.devtools-toolbar > .devtools-textinput:first-child,
.devtools-toolbar > .devtools-searchinput:first-child,
.devtools-toolbar > .devtools-filterinput:first-child {
margin-inline-start: 0;
}
.devtools-toolbar > .devtools-textinput:last-child,
.devtools-toolbar > .devtools-searchinput:last-child,
.devtools-toolbar > .devtools-filterinput:last-child {
margin-inline-end: 0;
}
.devtools-toolbar > .devtools-searchbox:first-child {
padding-inline-start: 0;
}
.devtools-toolbar > .devtools-searchbox:last-child {
padding-inline-end: 0;
}
.devtools-rule-searchbox {
-moz-box-flex: 1;
width: 100%;
}
.devtools-rule-searchbox[filled] {
background-color: var(--searchbox-background-color);
border-color: var(--searchbox-border-color);
padding-inline-end: 23px;
}
.devtools-style-searchbox-no-match {
background-color: var(--searcbox-no-match-background-color) !important;
border-color: var(--searcbox-no-match-border-color) !important;
}
.devtools-searchinput-clear {
position: absolute;
top: 3.5px;
offset-inline-end: 7px;
padding: 0;
border: 0;
width: 16px;
height: 16px;
background-position: 0 0;
background-repeat: no-repeat;
background-color: transparent;
}
.devtools-searchinput-clear:dir(rtl) {
right: unset;
left: 7px;
}
.theme-dark .devtools-searchinput-clear {
background-image: url("chrome://devtools/skin/images/search-clear-dark.svg");
}
.theme-light .devtools-searchinput-clear {
background-image: url("chrome://devtools/skin/images/search-clear-light.svg");
}
.devtools-style-searchbox-no-match + .devtools-searchinput-clear {
background-image: url("chrome://devtools/skin/images/search-clear-failed.svg") !important;
}
.devtools-searchinput-clear:hover {
background-position: -16px 0;
}
.theme-dark .devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear,
.theme-dark .devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
list-style-image: url("chrome://devtools/skin/images/search-clear-dark.svg");
-moz-image-region: rect(0, 16px, 16px, 0);
}
.theme-light .devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear,
.theme-light .devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
list-style-image: url("chrome://devtools/skin/images/search-clear-light.svg");
-moz-image-region: rect(0, 16px, 16px, 0);
}
.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear,
.devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
margin-bottom: 0;
}
.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear:hover,
.devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear:hover {
-moz-image-region: rect(0, 32px, 16px, 16px);
}
/* Throbbers */
.devtools-throbber::before {
content: "";
display: inline-block;
vertical-align: bottom;
margin-inline-end: 0.5em;
width: 1em;
height: 1em;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: 1.1s linear throbber-spin infinite;
}
@keyframes throbber-spin {
from {
transform: none;
}
to {
transform: rotate(360deg);
}
}

View File

@ -65,14 +65,17 @@
padding: 0;
line-height: -moz-block-height;
}
.devtools-toolbar checkbox .checkbox-check {
margin: 0;
padding: 0;
vertical-align: bottom;
}
.devtools-toolbar checkbox .checkbox-label-box {
border: none !important; /* overrides .checkbox-label-box from checkbox.css */
}
.devtools-toolbar checkbox .checkbox-label-box .checkbox-label {
margin: 0 6px !important; /* overrides .checkbox-label from checkbox.css */
padding: 0;
@ -87,434 +90,6 @@
background-position: 0, 1px, 2px;
}
/* Toolbar buttons */
.devtools-menulist,
.devtools-toolbarbutton,
.devtools-button {
-moz-appearance: none;
background: transparent;
min-height: 18px;
text-shadow: none;
border: none;
border-radius: 0;
color: var(--theme-body-color);
transition: background 0.05s ease-in-out;
}
.devtools-menulist,
.devtools-toolbarbutton {
-moz-box-align: center;
min-width: 78px;
padding: 1px;
margin: 2px 1px;
}
.devtools-toolbarbutton:not([label]) > .toolbarbutton-icon,
.devtools-button::before {
width: 16px;
height: 16px;
transition: opacity 0.05s ease-in-out;
}
/* HTML buttons */
.devtools-button {
margin: 2px 1px;
padding: 1px;
min-width: 32px;
/* The icon is absolutely positioned in the button using ::before */
position: relative;
}
.devtools-button::before {
content: "";
display: block;
position: absolute;
left: 50%;
top: 50%;
margin: -8px 0 0 -8px;
background-size: cover;
background-repeat: no-repeat;
transition: opacity 0.05s ease-in-out;
}
.devtools-button:-moz-focusring {
outline: none;
}
/* Standalone buttons */
.devtools-button[standalone],
.devtools-button[data-standalone],
.devtools-toolbarbutton[standalone],
.devtools-toolbarbutton[data-standalone] {
border-width: 1px;
border-style: solid;
min-height: 32px;
background-color: var(--theme-toolbar-background);
}
.devtools-toolbarbutton[standalone], .devtools-toolbarbutton[data-standalone] {
margin-inline-end: 5px;
}
.devtools-toolbarbutton[label][standalone] {
min-height: 2em;
}
.devtools-menulist,
.devtools-toolbarbutton,
.devtools-button {
border-color: var(--toolbar-button-border-color);
}
/* Icon button styles */
.devtools-toolbarbutton:not([label]),
.devtools-toolbarbutton[text-as-image] {
min-width: 32px;
}
/* Set flex attribute to Toolbox buttons and Picker container so,
they don't overlapp with the tab bar */
#toolbox-buttons {
display: flex;
}
#toolbox-picker-container {
display: flex;
}
.devtools-toolbarbutton:not([label]) > .toolbarbutton-text {
display: none;
}
.devtools-toolbarbutton > .toolbarbutton-icon {
margin: 0;
}
/* Menu button styles (eg. web console filters) */
.devtools-toolbarbutton[type=menu-button] > .toolbarbutton-menubutton-button {
-moz-appearance: none;
color: inherit;
border-width: 0;
-moz-box-orient: horizontal;
padding: 0;
}
.devtools-toolbarbutton[type=menu-button] {
padding: 0 1px;
-moz-box-align: stretch;
}
.devtools-toolbarbutton > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
margin-inline-end: 4px;
}
.devtools-menulist > .menulist-dropmarker {
-moz-appearance: none;
display: -moz-box;
list-style-image: url("chrome://devtools/skin/images/dropmarker.svg");
-moz-box-align: center;
min-width: 16px;
}
.devtools-toolbarbutton[type=menu] > .toolbarbutton-menu-dropmarker,
.devtools-toolbarbutton[type=menu-button] > .toolbarbutton-menubutton-dropmarker {
-moz-appearance: none !important;
list-style-image: url("chrome://devtools/skin/images/dropmarker.svg");
-moz-box-align: center;
padding: 0 3px;
}
/* Icon-only buttons */
.devtools-button:empty::before,
.devtools-toolbarbutton:not([label]):not([disabled]) > image {
opacity: 0.8;
}
.devtools-button:hover:empty::before,
.devtools-button[checked]:empty::before,
.devtools-button[open]:empty::before,
.devtools-toolbarbutton:not([label]):hover > image,
.devtools-toolbarbutton:not([label])[checked=true] > image,
.devtools-toolbarbutton:not([label])[open=true] > image {
opacity: 1;
}
.devtools-button:disabled,
.devtools-button[disabled],
.devtools-toolbarbutton[disabled] {
opacity: 0.5 !important;
}
.devtools-button[checked]:empty::before,
.devtools-button[open]:empty::before,
.devtools-button.checked::before,
.devtools-toolbarbutton:not([label])[checked=true] > image,
.devtools-toolbarbutton:not([label])[open=true] > image {
filter: var(--checked-icon-filter);
}
/* Icon-and-text buttons */
.devtools-toolbarbutton.icon-and-text .toolbarbutton-text {
margin-inline-start: .5em !important;
font-weight: 600;
}
/* Text-only buttons */
.theme-light .devtools-toolbarbutton[label]:not([text-as-image]):not([type=menu-button]),
.theme-light .devtools-toolbarbutton[data-text-only] {
background-color: var(--toolbar-tab-hover);
}
.theme-dark .devtools-toolbarbutton[label]:not([text-as-image]):not([type=menu-button]),
.theme-dark .devtools-toolbarbutton[data-text-only] {
background-color: rgba(0, 0, 0, .2); /* Splitter */
}
/* Text-only button states */
.theme-dark .devtools-button:not(:empty):not([disabled]):hover,
.theme-dark .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover {
background: rgba(0, 0, 0, .3); /* Splitters */
}
.theme-light .devtools-button:not(:empty):not([disabled]):hover,
.theme-light .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover {
background: rgba(170, 170, 170, .3); /* Splitters */
}
.theme-dark .devtools-button:not(:empty):not([disabled]):hover:active,
.theme-dark .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover:active {
background: rgba(0, 0, 0, .4); /* Splitters */
}
.theme-light .devtools-button:not(:empty):not([disabled]):hover:active,
.theme-light .devtools-toolbarbutton:not(:-moz-any([checked=true],[disabled],[text-as-image]))[label]:hover:active {
background: var(--toolbar-tab-hover-active);
}
.theme-dark .devtools-toolbarbutton:not([disabled])[label][checked=true],
.theme-dark .devtools-toolbarbutton:not([disabled])[label][open],
.theme-dark .devtools-button:not(:empty)[checked=true] {
background: var(--theme-selection-background-semitransparent);
color: var(--theme-selection-color);
}
.theme-light .devtools-toolbarbutton:not([disabled])[label][checked=true],
.theme-light .devtools-toolbarbutton:not([disabled])[label][open],
.theme-light .devtools-button:not(:empty)[checked=true] {
background: rgba(76, 158, 217, .3); /* Select highlight blue */
}
:root {
--clear-icon-url: url("chrome://devtools/skin/images/clear.svg");
}
.devtools-button.devtools-clear-icon::before {
background-image: var(--clear-icon-url);
}
.devtools-button.devtools-filter-icon::before {
background-image: var(--filter-image);
}
.devtools-toolbarbutton.devtools-clear-icon {
list-style-image: var(--clear-icon-url);
}
.devtools-option-toolbarbutton {
list-style-image: var(--tool-options-image);
}
.devtools-toolbarbutton-group > .devtools-toolbarbutton:last-child {
margin-inline-end: 0;
}
.devtools-toolbarbutton-group + .devtools-toolbarbutton {
margin-inline-start: 3px;
}
.devtools-separator + .devtools-toolbarbutton {
margin-inline-start: 1px;
}
/* Text input */
.devtools-textinput,
.devtools-searchinput,
.devtools-filterinput {
-moz-appearance: none;
margin: 1px 3px;
border: 1px solid;
border-radius: 2px;
padding: 4px 6px;
border-color: var(--theme-splitter-color);
font: message-box;
}
:root[platform="mac"] .devtools-textinput,
:root[platform="mac"] .devtools-searchinput,
:root[platform="mac"] .devtools-filterinput {
border-radius: 20px;
}
.devtools-searchinput,
.devtools-filterinput {
padding: 0;
padding-inline-start: 22px;
padding-inline-end: 4px;
background-position: 8px center;
background-size: 11px 11px;
background-repeat: no-repeat;
font-size: inherit;
}
.devtools-searchinput {
background-image: var(--magnifying-glass-image);
}
.devtools-filterinput {
background-image: url(images/filter.svg#filterinput);
}
.devtools-searchinput:-moz-locale-dir(rtl),
.devtools-searchinput:dir(rtl),
.devtools-filterinput:-moz-locale-dir(rtl),
.devtools-filterinput:dir(rtl) {
background-position: calc(100% - 8px) center;
}
.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-icon,
.devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-icon {
visibility: hidden;
}
.devtools-searchinput .textbox-input::-moz-placeholder,
.devtools-filterinput .textbox-input::-moz-placeholder {
font-style: normal;
}
.devtools-plaininput {
border-color: transparent;
background-color: transparent;
}
.theme-dark .devtools-plaininput {
color: var(--theme-highlight-gray);
}
/* Searchbox is a div container element for a search input element */
.devtools-searchbox {
display: flex;
flex: 1;
height: 23px;
position: relative;
padding: 0 3px;
}
/* The spacing is accomplished with a padding on the searchbox */
.devtools-searchbox > .devtools-textinput,
.devtools-searchbox > .devtools-searchinput,
.devtools-searchbox > .devtools-filterinput {
margin-left: 0;
margin-right: 0;
}
.devtools-searchbox > .devtools-textinput:-moz-focusring,
.devtools-searchbox > .devtools-searchinput:-moz-focusring,
.devtools-searchbox > .devtools-filterinput:-moz-focusring {
border-color: var(--theme-focus-border-color-textbox);
box-shadow: var(--theme-focus-box-shadow-textbox);
transition: all 0.2s ease-in-out;
outline: none;
}
/* Don't add 'double spacing' for inputs that are at beginning / end
of a toolbar (since the toolbar has it's own spacing). */
.devtools-toolbar > .devtools-textinput:first-child,
.devtools-toolbar > .devtools-searchinput:first-child,
.devtools-toolbar > .devtools-filterinput:first-child {
margin-inline-start: 0;
}
.devtools-toolbar > .devtools-textinput:last-child,
.devtools-toolbar > .devtools-searchinput:last-child,
.devtools-toolbar > .devtools-filterinput:last-child {
margin-inline-end: 0;
}
.devtools-toolbar > .devtools-searchbox:first-child {
padding-inline-start: 0;
}
.devtools-toolbar > .devtools-searchbox:last-child {
padding-inline-end: 0;
}
.devtools-rule-searchbox {
-moz-box-flex: 1;
width: 100%;
}
.devtools-rule-searchbox[filled] {
background-color: var(--searchbox-background-color);
border-color: var(--searchbox-border-color);
padding-inline-end: 23px;
}
.devtools-style-searchbox-no-match {
background-color: var(--searcbox-no-match-background-color) !important;
border-color: var(--searcbox-no-match-border-color) !important;
}
.devtools-searchinput-clear {
position: absolute;
top: 3.5px;
offset-inline-end: 7px;
padding: 0;
border: 0;
width: 16px;
height: 16px;
background-position: 0 0;
background-repeat: no-repeat;
background-color: transparent;
}
.devtools-searchinput-clear:dir(rtl) {
right: unset;
left: 7px;
}
.theme-dark .devtools-searchinput-clear {
background-image: url("chrome://devtools/skin/images/search-clear-dark.svg");
}
.theme-light .devtools-searchinput-clear {
background-image: url("chrome://devtools/skin/images/search-clear-light.svg");
}
.devtools-style-searchbox-no-match + .devtools-searchinput-clear {
background-image: url("chrome://devtools/skin/images/search-clear-failed.svg") !important;
}
.devtools-searchinput-clear:hover {
background-position: -16px 0;
}
.theme-dark .devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear,
.theme-dark .devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
list-style-image: url("chrome://devtools/skin/images/search-clear-dark.svg");
-moz-image-region: rect(0, 16px, 16px, 0);
}
.theme-light .devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear,
.theme-light .devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
list-style-image: url("chrome://devtools/skin/images/search-clear-light.svg");
-moz-image-region: rect(0, 16px, 16px, 0);
}
.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear,
.devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
margin-bottom: 0;
}
.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear:hover,
.devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear:hover {
-moz-image-region: rect(0, 32px, 16px, 16px);
}
/* In-tools sidebar */
.devtools-sidebar-tabs {
-moz-appearance: none;
@ -640,54 +215,3 @@
.devtools-side-splitter {
background-color: var(--theme-splitter-color);
}
/* Throbbers */
.devtools-throbber::before {
content: "";
display: inline-block;
vertical-align: bottom;
margin-inline-end: 0.5em;
width: 1em;
height: 1em;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: 1.1s linear throbber-spin infinite;
}
@keyframes throbber-spin {
from {
transform: none;
}
to {
transform: rotate(360deg);
}
}
/*
* Filter buttons
* @TODO : Fix when https://bugzilla.mozilla.org/show_bug.cgi?id=1255116 lands
*/
.menu-filter-button {
-moz-appearance: none;
background: rgba(128,128,128,0.1);
border: none;
border-radius: 2px;
min-width: 0;
padding: 0 5px;
margin: 2px;
color: var(--theme-body-color);
}
.menu-filter-button:hover {
background: rgba(128,128,128,0.2);
}
.menu-filter-button:hover:active {
background-color: var(--theme-selection-background-semitransparent);
}
.menu-filter-button:not(:active).checked {
background-color: var(--theme-selection-background);
color: var(--theme-selection-color);
}

View File

@ -55,6 +55,16 @@
margin: 0;
}
/* Set flex attribute to Toolbox buttons and Picker container so,
they don't overlapp with the tab bar */
#toolbox-buttons {
display: flex;
}
#toolbox-picker-container {
display: flex;
}
/* Toolbox tabs */
.devtools-tab {

View File

@ -42,7 +42,9 @@ const GRID_LINES_PROPERTIES = {
* h.destroy();
*
* Available Options:
* - infiniteLines {Boolean}
* - showGridLineNumbers {Boolean}
* Displays the grid line numbers
* - showInfiniteLines {Boolean}
* Displays an infinite line to represent the grid lines
*/
function CssGridHighlighter(highlighterEnv) {
@ -243,7 +245,7 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
let lineStartPos = (bounds[crossSide] / getCurrentZoom(this.win)) + startPos;
let lineEndPos = (bounds[crossSide] / getCurrentZoom(this.win)) + endPos;
if (this.options.infiniteLines) {
if (this.options.showInfiniteLines) {
lineStartPos = 0;
lineEndPos = parseInt(this.canvas.getAttribute(mainSize), 10);
}
@ -254,6 +256,10 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
let line = gridDimension.lines[i];
let linePos = (bounds[mainSide] / getCurrentZoom(this.win)) + line.start;
if (this.options.showGridLineNumbers) {
this.renderGridLineNumber(line.number, linePos, lineStartPos, dimensionType);
}
if (i == 0 || i == lastEdgeLineIndex) {
this.renderLine(linePos, lineStartPos, lineEndPos, dimensionType, "edge");
} else {
@ -291,7 +297,7 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
this.ctx.beginPath();
this.ctx.translate(.5, .5);
if (dimensionType == COLUMNS) {
if (dimensionType === COLUMNS) {
this.ctx.moveTo(linePos, startPos);
this.ctx.lineTo(linePos, endPos);
} else {
@ -304,6 +310,28 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
this.ctx.restore();
},
/**
* Render the grid line number on the css grid highlighter canvas.
*
* @param {Number} lineNumber
* The grid line number.
* @param {Number} linePos
* The line position along the x-axis for a column grid line and
* y-axis for a row grid line.
* @param {Number} startPos
* The start position of the cross side of the grid line.
* @param {String} dimensionType
* The grid dimension type which is either the constant COLUMNS or ROWS.
*/
renderGridLineNumber(lineNumber, linePos, startPos, dimensionType) {
if (dimensionType === COLUMNS) {
this.ctx.fillText(lineNumber, linePos, startPos);
} else {
let textWidth = this.ctx.measureText(lineNumber).width;
this.ctx.fillText(lineNumber, startPos - textWidth, linePos);
}
},
_hide() {
setIgnoreLayoutChanges(true);
this._hideGrid();

View File

@ -178,6 +178,8 @@ EyeDropper.prototype = {
this.getElement("root").setAttribute("hidden", "true");
this.getElement("root").removeAttribute("drawn");
this.emit("hidden");
},
prepareImageCapture() {

View File

@ -26,6 +26,8 @@ const { Class } = require("sdk/core/heritage");
const events = require("sdk/event/core");
const object = require("sdk/util/object");
const nodeConstants = require("devtools/shared/dom-node-constants.js");
loader.lazyRequireGetter(this, "CommandUtils",
"devtools/client/shared/developer-toolbar", true);
const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
@ -977,6 +979,22 @@ var InspectorFront = FrontClassWithSpec(inspectorSpec, {
});
}, {
impl: "_getPageStyle"
}),
pickColorFromPage: custom(Task.async(function* (toolbox, options) {
if (toolbox) {
// If the eyedropper was already started using the gcli command, hide it so we don't
// end up with 2 instances of the eyedropper on the page.
let {target} = toolbox;
let requisition = yield CommandUtils.createRequisition(target, {
environment: CommandUtils.createEnvironment({target})
});
yield requisition.updateExec("eyedropper --hide");
}
yield this._pickColorFromPage(options);
}), {
impl: "_pickColorFromPage"
})
});

View File

@ -6,6 +6,7 @@
#include "Grid.h"
#include "GridArea.h"
#include "GridDimension.h"
#include "mozilla/dom/GridBinding.h"
#include "nsGridContainerFrame.h"
@ -13,7 +14,7 @@
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Grid, mParent, mRows, mCols)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Grid, mParent, mRows, mCols, mAreas)
NS_IMPL_CYCLE_COLLECTING_ADDREF(Grid)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Grid)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Grid)
@ -43,6 +44,40 @@ Grid::Grid(nsISupports* aParent,
aFrame->GetComputedTemplateColumnLines();
mCols->SetTrackInfo(columnTrackInfo);
mCols->SetLineInfo(columnTrackInfo, columnLineInfo);
// Add implicit areas first.
nsGridContainerFrame::ImplicitNamedAreas* implicitAreas =
aFrame->GetImplicitNamedAreas();
if (implicitAreas) {
for (auto iter = implicitAreas->Iter(); !iter.Done(); iter.Next()) {
nsStringHashKey* entry = iter.Get();
GridArea* area = new GridArea(this,
nsString(entry->GetKey()),
GridDeclaration::Implicit,
0,
0,
0,
0);
mAreas.AppendElement(area);
}
}
// Add explicit areas next.
nsGridContainerFrame::ExplicitNamedAreas* explicitAreas =
aFrame->GetExplicitNamedAreas();
if (explicitAreas) {
for (auto areaInfo : *explicitAreas) {
GridArea* area = new GridArea(this,
areaInfo.mName,
GridDeclaration::Explicit,
areaInfo.mRowStart,
areaInfo.mRowEnd,
areaInfo.mColumnStart,
areaInfo.mColumnEnd);
mAreas.AppendElement(area);
}
}
}
Grid::~Grid()
@ -67,5 +102,11 @@ Grid::Cols() const
return mCols;
}
void
Grid::GetAreas(nsTArray<RefPtr<GridArea>>& aAreas) const
{
aAreas = mAreas;
}
} // namespace dom
} // namespace mozilla

View File

@ -7,6 +7,7 @@
#ifndef mozilla_dom_Grid_h
#define mozilla_dom_Grid_h
#include "GridArea.h"
#include "mozilla/dom/Element.h"
#include "nsGridContainerFrame.h"
#include "nsISupports.h"
@ -38,11 +39,13 @@ public:
GridDimension* Rows() const;
GridDimension* Cols() const;
void GetAreas(nsTArray<RefPtr<GridArea>>& aAreas) const;
protected:
nsCOMPtr<Element> mParent;
RefPtr<GridDimension> mRows;
RefPtr<GridDimension> mCols;
nsTArray<RefPtr<GridArea>> mAreas;
};
} // namespace dom

86
dom/grid/GridArea.cpp Normal file
View File

@ -0,0 +1,86 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "GridArea.h"
#include "mozilla/dom/GridBinding.h"
#include "Grid.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(GridArea, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(GridArea)
NS_IMPL_CYCLE_COLLECTING_RELEASE(GridArea)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GridArea)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
GridArea::GridArea(Grid* aParent,
const nsString& aName,
GridDeclaration aType,
uint32_t aRowStart,
uint32_t aRowEnd,
uint32_t aColumnStart,
uint32_t aColumnEnd)
: mParent(aParent)
, mName(aName)
, mType(aType)
, mRowStart(aRowStart)
, mRowEnd(aRowEnd)
, mColumnStart(aColumnStart)
, mColumnEnd(aColumnEnd)
{
}
GridArea::~GridArea()
{
}
JSObject*
GridArea::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return GridAreaBinding::Wrap(aCx, this, aGivenProto);
}
void
GridArea::GetName(nsString& aName) const
{
aName = mName;
}
GridDeclaration
GridArea::Type() const
{
return mType;
}
uint32_t
GridArea::RowStart() const
{
return mRowStart;
}
uint32_t
GridArea::RowEnd() const
{
return mRowEnd;
}
uint32_t
GridArea::ColumnStart() const
{
return mColumnStart;
}
uint32_t
GridArea::ColumnEnd() const
{
return mColumnEnd;
}
} // namespace dom
} // namespace mozilla

63
dom/grid/GridArea.h Normal file
View File

@ -0,0 +1,63 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_GridArea_h
#define mozilla_dom_GridArea_h
#include "mozilla/dom/GridBinding.h"
#include "nsWrapperCache.h"
namespace mozilla {
namespace dom {
class Grid;
class GridArea : public nsISupports
, public nsWrapperCache
{
public:
explicit GridArea(Grid *aParent,
const nsString& aName,
GridDeclaration aType,
uint32_t aRowStart,
uint32_t aRowEnd,
uint32_t aColumnStart,
uint32_t aColumnEnd);
protected:
virtual ~GridArea();
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(GridArea)
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
Grid* GetParentObject()
{
return mParent;
}
void GetName(nsString& aName) const;
GridDeclaration Type() const;
uint32_t RowStart() const;
uint32_t RowEnd() const;
uint32_t ColumnStart() const;
uint32_t ColumnEnd() const;
protected:
RefPtr<Grid> mParent;
const nsString mName;
const GridDeclaration mType;
const uint32_t mRowStart;
const uint32_t mRowEnd;
const uint32_t mColumnStart;
const uint32_t mColumnEnd;
};
} // namespace dom
} // namespace mozilla
#endif /* mozilla_dom_GridTrack_h */

View File

@ -24,6 +24,7 @@ GridLine::GridLine(GridLines *aParent)
: mParent(aParent)
, mStart(0.0)
, mBreadth(0.0)
, mType(GridDeclaration::Implicit)
, mNumber(0)
{
MOZ_ASSERT(aParent, "Should never be instantiated with a null GridLines");
@ -57,6 +58,12 @@ GridLine::Breadth() const
return mBreadth;
}
GridDeclaration
GridLine::Type() const
{
return mType;
}
uint32_t
GridLine::Number() const
{
@ -64,15 +71,17 @@ GridLine::Number() const
}
void
GridLine::SetLineValues(double aStart,
GridLine::SetLineValues(const nsTArray<nsString>& aNames,
double aStart,
double aBreadth,
uint32_t aNumber,
const nsTArray<nsString>& aNames)
GridDeclaration aType)
{
mNames = aNames;
mStart = aStart;
mBreadth = aBreadth;
mNumber = aNumber;
mNames = aNames;
mType = aType;
}
} // namespace dom

View File

@ -7,6 +7,7 @@
#ifndef mozilla_dom_GridLine_h
#define mozilla_dom_GridLine_h
#include "mozilla/dom/GridBinding.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsWrapperCache.h"
@ -39,19 +40,22 @@ public:
double Start() const;
double Breadth() const;
GridDeclaration Type() const;
uint32_t Number() const;
void SetLineValues(double aStart,
void SetLineValues(const nsTArray<nsString>& aNames,
double aStart,
double aBreadth,
uint32_t aNumber,
const nsTArray<nsString>& aNames);
GridDeclaration aType);
protected:
RefPtr<GridLines> mParent;
nsTArray<nsString> mNames;
double mStart;
double mBreadth;
GridDeclaration mType;
uint32_t mNumber;
nsTArray<nsString> mNames;
};
} // namespace dom

View File

@ -97,11 +97,22 @@ GridLines::SetLineInfo(const ComputedGridTrackInfo* aTrackInfo,
}
line->SetLineValues(
lineNames,
nsPresContext::AppUnitsToDoubleCSSPixels(endOfLastTrack),
nsPresContext::AppUnitsToDoubleCSSPixels(startOfNextTrack -
endOfLastTrack),
i + 1,
lineNames
(
// Implicit if there are no explicit tracks, or if the index
// is before the first explicit track, or after
// a track beyond the last explicit track.
(aTrackInfo->mNumExplicitTracks == 0) ||
(i < aTrackInfo->mNumLeadingImplicitTracks) ||
(i > aTrackInfo->mNumLeadingImplicitTracks +
aTrackInfo->mNumExplicitTracks) ?
GridDeclaration::Implicit :
GridDeclaration::Explicit
)
);
if (i < aTrackInfo->mEndFragmentTrack) {

View File

@ -24,7 +24,7 @@ GridTrack::GridTrack(GridTracks* aParent)
: mParent(aParent)
, mStart(0.0)
, mBreadth(0.0)
, mType(GridTrackType::Implicit)
, mType(GridDeclaration::Implicit)
, mState(GridTrackState::Static)
{
MOZ_ASSERT(aParent, "Should never be instantiated with a null GridTracks");
@ -52,7 +52,7 @@ GridTrack::Breadth() const
return mBreadth;
}
GridTrackType
GridDeclaration
GridTrack::Type() const
{
return mType;
@ -67,7 +67,7 @@ GridTrack::State() const
void
GridTrack::SetTrackValues(double aStart,
double aBreadth,
GridTrackType aType,
GridDeclaration aType,
GridTrackState aState)
{
mStart = aStart;

View File

@ -36,16 +36,19 @@ public:
double Start() const;
double Breadth() const;
GridTrackType Type() const;
GridDeclaration Type() const;
GridTrackState State() const;
void SetTrackValues(double aStart, double aBreadth, GridTrackType aType, GridTrackState aState);
void SetTrackValues(double aStart,
double aBreadth,
GridDeclaration aType,
GridTrackState aState);
protected:
RefPtr<GridTracks> mParent;
double mStart;
double mBreadth;
GridTrackType mType;
GridDeclaration mType;
GridTrackState mState;
};

View File

@ -86,8 +86,8 @@ GridTracks::SetTrackInfo(const ComputedGridTrackInfo* aTrackInfo)
(i < aTrackInfo->mNumLeadingImplicitTracks) ||
(i >= aTrackInfo->mNumLeadingImplicitTracks +
aTrackInfo->mNumExplicitTracks) ?
GridTrackType::Implicit :
GridTrackType::Explicit
GridDeclaration::Implicit :
GridDeclaration::Explicit
),
GridTrackState(aTrackInfo->mStates[i])
);

View File

@ -8,6 +8,7 @@ MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
EXPORTS.mozilla.dom += [
'Grid.h',
'GridArea.h',
'GridDimension.h',
'GridLine.h',
'GridLines.h',
@ -17,6 +18,7 @@ EXPORTS.mozilla.dom += [
UNIFIED_SOURCES += [
'Grid.cpp',
'GridArea.cpp',
'GridDimension.cpp',
'GridLine.cpp',
'GridLines.cpp',

View File

@ -1,4 +1,6 @@
[chrome/test_grid_areas.html]
[chrome/test_grid_fragmentation.html]
[chrome/test_grid_implicit.html]
[chrome/test_grid_lines.html]
[chrome/test_grid_object.html]
[chrome/test_grid_track_state.html]

View File

@ -0,0 +1,96 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<style>
body {
margin: 40px;
}
.wrapper {
display: grid;
width: 600px;
height: 600px;
grid-gap: 20px;
grid-template-columns: 50px 1fr 50px;
grid-template-rows: 1fr 1fr 1fr;
grid-template-areas: "areaA areaA ....."
"areaB areaC areaC"
"areaB areaC areaC";
background-color: #f00;
}
.box {
background-color: #444;
color: #fff;
}
.a {
grid-area: areaA;
}
.b {
grid-area: areaB;
}
.c {
grid-area: areaC;
}
</style>
<script>
'use strict';
SimpleTest.waitForExplicitFinish();
function runTests() {
var wrapper = document.getElementById("wrapper");
var grid = wrapper.getGridFragments()[0];
// test existence of property
isnot(typeof(grid.areas), "undefined", "Grid.areas property exists.");
if (typeof(grid.areas) != "undefined") {
// test that the right number of areas are reported
is(grid.areas.length, 3, "3 areas are reported.");
if (grid.areas.length == 3) {
// test area names
is(grid.areas[0].name, "areaA", "Area 0 has proper name.");
is(grid.areas[1].name, "areaB", "Area 1 has proper name.");
is(grid.areas[2].name, "areaC", "Area 2 has proper name.");
// test area types
is(grid.areas[0].type, "explicit", "Area 0 is explicit.");
is(grid.areas[1].type, "explicit", "Area 1 is explicit.");
is(grid.areas[2].type, "explicit", "Area 2 is explicit.");
// test start and end lines
is(grid.areas[0].rowStart, 1, "Area 0 has start row line of 1.");
is(grid.areas[0].rowEnd, 2, "Area 0 has end row line of 2.");
is(grid.areas[0].columnStart, 1, "Area 0 has start column line of 1.");
is(grid.areas[0].columnEnd, 3, "Area 0 has end column line of 3.");
is(grid.areas[1].rowStart, 2, "Area 1 has start row line of 2.");
is(grid.areas[1].rowEnd, 4, "Area 1 has end row line of 4.");
is(grid.areas[1].columnStart, 1, "Area 1 has start column line of 1.");
is(grid.areas[1].columnEnd, 2, "Area 1 has end column line of 2.");
is(grid.areas[2].rowStart, 2, "Area 2 has start row line of 2.");
is(grid.areas[2].rowEnd, 4, "Area 2 has end row line of 4.");
is(grid.areas[2].columnStart, 2, "Area 2 has start column line of 2.");
is(grid.areas[2].columnEnd, 4, "Area 2 has end column line of 4.");
}
}
SimpleTest.finish();
}
</script>
</head>
<body onLoad="runTests();">
<div id="wrapper" class="wrapper">
<div id="boxA" class="box a">A</div>
<div id="boxB" class="box b">B</div>
<div id="boxC" class="box c">C</div>
</div>
</body>
</html>

View File

@ -0,0 +1,129 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<style>
body {
margin: 40px;
}
.wrapper {
display: grid;
grid-gap: 10px;
grid-template-columns: 100px 50px 100px;
grid-template-rows: 50px [areaD-start] 50px [areaD-end];
grid-template-areas: "areaA areaA ....."
"..... areaC areaC";
grid-auto-columns: 20px;
grid-auto-rows: 20px;
background-color: #f00;
}
.box {
background-color: #444;
color: #fff;
}
.a {
grid-area: areaA;
}
.b {
grid-row: span 2 / 2;
grid-column: areaA-end / span 2;
}
.c {
grid-area: areaC;
}
.d {
grid-area: areaD;
}
</style>
<script>
'use strict';
SimpleTest.waitForExplicitFinish();
function runTests() {
var wrapper = document.getElementById("wrapper");
var grid = wrapper.getGridFragments()[0];
var boxA = document.getElementById("boxA");
var boxB = document.getElementById("boxB");
var boxC = document.getElementById("boxC");
// test column and row line counts
is(grid.cols.lines.length, 6,
"Grid.cols.lines property has length that respects implicit expansion."
);
is(grid.rows.lines.length, 4,
"Grid.rows.lines property has length that respects implicit expansion."
);
if((grid.cols.lines.length == 6) &&
(grid.rows.lines.length == 4)) {
// test explicit / implicit lines
is(grid.cols.lines[0].type, "explicit", "Grid column line 1 is explicit.");
is(grid.cols.lines[4].type, "implicit", "Grid column line 5 is implicit.");
is(grid.cols.lines[5].type, "implicit", "Grid column line 6 is implicit.");
is(grid.rows.lines[0].type, "implicit", "Grid row line 1 is implicit.");
is(grid.rows.lines[1].type, "explicit", "Grid row line 2 is explicit.");
is(grid.rows.lines[3].type, "explicit", "Grid row line 4 is explicit.");
}
// test column and row track counts
is(grid.cols.tracks.length, 5,
"Grid.cols.tracks property has length that respects implicit expansion."
);
is(grid.rows.tracks.length, 3,
"Grid.rows.tracks property has length that respects implicit expansion."
);
if((grid.cols.tracks.length == 5) &&
(grid.rows.tracks.length == 3)) {
// test explicit / implicit tracks
is(grid.cols.tracks[0].type, "explicit", "Grid column track 1 is explicit.");
is(grid.cols.tracks[3].type, "implicit", "Grid column track 4 is implicit.");
is(grid.cols.tracks[4].type, "implicit", "Grid column track 5 is implicit.");
is(grid.rows.tracks[0].type, "implicit", "Grid row track 1 is implicit.");
is(grid.rows.tracks[1].type, "explicit", "Grid row track 2 is explicit.");
is(grid.rows.tracks[2].type, "explicit", "Grid row track 3 is explicit.");
}
// test area count
is(grid.areas.length, 3,
"Grid.areas property has length that respects implicit expansion."
);
for(var i = 0; i < grid.areas.length; i++) {
var area = grid.areas[i];
if(area.name == "areaD") {
is(area.type, "implicit", area.name + " is implicit.");
// test lines of implicit areas
todo_is(area.rowStart, 2, area.name + " has start row line of 2.");
todo_is(area.rowEnd, 3, area.name + " has end row line of 3.");
todo_is(area.columnStart, 4, area.name + " has start column line of 4.");
todo_is(area.columnEnd, 5, area.name + " has end column line of 5.");
} else {
is(area.type, "explicit", area.name + " is explicit.");
}
}
SimpleTest.finish();
}
</script>
</head>
<body onLoad="runTests();">
<div id="wrapper" class="wrapper">
<div id="boxA" class="box a">A</div>
<div id="boxB" class="box b">B</div>
<div id="boxC" class="box c">C</div>
<div id="boxD" class="box d">D</div>
</div>
</body>
</html>

View File

@ -1,13 +1,31 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* These objects support visualization of a css-grid by the dev tools. */
/**
* Explicit and implicit types apply to tracks, lines, and areas.
* https://drafts.csswg.org/css-grid/#explicit-grids
* https://drafts.csswg.org/css-grid/#implicit-grids
*/
enum GridDeclaration { "explicit", "implicit" };
/**
* Tracks expanded from auto-fill or auto-fit have repeat state, other tracks
* are static.
*/
enum GridTrackState { "static", "repeat" };
[ChromeOnly]
interface Grid
{
readonly attribute GridDimension rows;
readonly attribute GridDimension cols;
[Cached, Constant]
readonly attribute sequence<GridArea> areas;
};
[ChromeOnly]
@ -21,34 +39,77 @@ interface GridDimension
interface GridLines
{
readonly attribute unsigned long length;
/**
* This accessor method allows array-like access to lines.
* @param index A 0-indexed value.
*/
getter GridLine? item(unsigned long index);
};
[ChromeOnly]
interface GridLine
{
readonly attribute double start;
readonly attribute double breadth;
readonly attribute unsigned long number;
[Cached, Pure]
/**
* Names include both explicit names and implicit names, which will be
* assigned if the line contributes to a named area.
* https://drafts.csswg.org/css-grid/#implicit-named-lines
*/
[Cached, Constant]
readonly attribute sequence<DOMString> names;
readonly attribute double start;
/**
* Breadth is the gap between the start of this line and the start of the
* next track in flow direction. It primarily is set by use of the -gap
* properties.
* https://drafts.csswg.org/css-grid/#gutters
*/
readonly attribute double breadth;
readonly attribute GridDeclaration type;
/**
* Number is the 1-indexed index of the line in flow order.
*/
readonly attribute unsigned long number;
};
[ChromeOnly]
interface GridTracks
{
readonly attribute unsigned long length;
/**
* This accessor method allows array-like access to tracks.
* @param index A 0-indexed value.
*/
getter GridTrack? item(unsigned long index);
};
enum GridTrackType { "explicit", "implicit" };
enum GridTrackState { "static", "repeat" };
[ChromeOnly]
interface GridTrack
{
readonly attribute double start;
readonly attribute double breadth;
readonly attribute GridTrackType type;
readonly attribute GridDeclaration type;
readonly attribute GridTrackState state;
};
[ChromeOnly]
interface GridArea
{
readonly attribute DOMString name;
readonly attribute GridDeclaration type;
/**
* These values are 1-indexed line numbers bounding the area.
* FIXME: Bug 1297189 - Implicit grid areas need boundary line numbers
* exposed to dev tools
*/
readonly attribute unsigned long rowStart;
readonly attribute unsigned long rowEnd;
readonly attribute unsigned long columnStart;
readonly attribute unsigned long columnEnd;
};

View File

@ -5767,6 +5767,16 @@ nsGridContainerFrame::Reflow(nsPresContext* aPresContext,
ComputedGridLineInfo* rowLineInfo = new ComputedGridLineInfo(
Move(rowLineNames));
Properties().Set(GridRowLineInfo(), rowLineInfo);
// Generate area info for explicit areas. Implicit areas are handled
// elsewhere.
if (gridReflowInput.mGridStyle->mGridTemplateAreas) {
nsTArray<css::GridNamedArea>* areas = new nsTArray<css::GridNamedArea>(
gridReflowInput.mGridStyle->mGridTemplateAreas->mNamedAreas);
Properties().Set(ExplicitNamedAreasProperty(), areas);
} else {
Properties().Delete(ExplicitNamedAreasProperty());
}
}
if (!prevInFlow) {

View File

@ -151,6 +151,20 @@ public:
return info;
}
typedef nsTHashtable<nsStringHashKey> ImplicitNamedAreas;
NS_DECLARE_FRAME_PROPERTY_DELETABLE(ImplicitNamedAreasProperty,
ImplicitNamedAreas)
ImplicitNamedAreas* GetImplicitNamedAreas() const {
return Properties().Get(ImplicitNamedAreasProperty());
}
typedef nsTArray<mozilla::css::GridNamedArea> ExplicitNamedAreas;
NS_DECLARE_FRAME_PROPERTY_DELETABLE(ExplicitNamedAreasProperty,
ExplicitNamedAreas)
ExplicitNamedAreas* GetExplicitNamedAreas() const {
return Properties().Get(ExplicitNamedAreasProperty());
}
/**
* Return a containing grid frame, and ensure it has computed grid info
* @return nullptr if aFrame has no grid container, or frame was destroyed
@ -195,14 +209,8 @@ protected:
* grid-template-columns / grid-template-rows are stored in this frame
* property when needed, as a ImplicitNamedAreas* value.
*/
typedef nsTHashtable<nsStringHashKey> ImplicitNamedAreas;
NS_DECLARE_FRAME_PROPERTY_DELETABLE(ImplicitNamedAreasProperty,
ImplicitNamedAreas)
void InitImplicitNamedAreas(const nsStylePosition* aStyle);
void AddImplicitNamedAreas(const nsTArray<nsTArray<nsString>>& aLineNameLists);
ImplicitNamedAreas* GetImplicitNamedAreas() const {
return Properties().Get(ImplicitNamedAreasProperty());
}
/**
* Reflow and place our children.

View File

@ -989,9 +989,8 @@ var loadManifestFromWebManifest = Task.async(function*(aUri) {
addon.targetApplications = [{
id: TOOLKIT_ID,
minVersion: (bss.strict_min_version ||
AddonManagerPrivate.webExtensionsMinPlatformVersion),
maxVersion: bss.strict_max_version || "*",
minVersion: bss.strict_min_version,
maxVersion: bss.strict_max_version,
}];
addon.targetPlatforms = [];
@ -3994,6 +3993,13 @@ this.XPIProvider = {
}
let addon = yield loadManifestFromFile(aFile, TemporaryInstallLocation);
if (!addon.isCompatible) {
let app = addon.matchingTargetApplication;
throw new Error(`Add-on ${addon.id} is not compatible with application version. ` +
`add-on minVersion: ${app.minVersion}, ` +
`add-on maxVersion: ${app.maxVersion}`);
}
if (!addon.bootstrap) {
throw new Error("Only restartless (bootstrap) add-ons"
+ " can be temporarily installed:", addon.id);
@ -7014,6 +7020,10 @@ AddonInternal.prototype = {
if (!app)
return false;
// set reasonable defaults for minVersion and maxVersion
let minVersion = app.minVersion || "0";
let maxVersion = app.maxVersion || "*";
if (!aAppVersion)
aAppVersion = Services.appinfo.version;
if (!aPlatformVersion)
@ -7050,14 +7060,14 @@ AddonInternal.prototype = {
minCompatVersion = XPIProvider.minCompatiblePlatformVersion;
if (minCompatVersion &&
Services.vc.compare(minCompatVersion, app.maxVersion) > 0)
Services.vc.compare(minCompatVersion, maxVersion) > 0)
return false;
return Services.vc.compare(version, app.minVersion) >= 0;
return Services.vc.compare(version, minVersion) >= 0;
}
return (Services.vc.compare(version, app.minVersion) >= 0) &&
(Services.vc.compare(version, app.maxVersion) <= 0)
return (Services.vc.compare(version, minVersion) >= 0) &&
(Services.vc.compare(version, maxVersion) <= 0)
},
get matchingTargetApplication() {

View File

@ -717,13 +717,11 @@ function writeInstallRDFForExtension(aData, aDir, aId, aExtraFile) {
* An optional string to override the default installation aId
* @return A file pointing to where the extension was installed
*/
function writeWebManifestForExtension(aData, aDir, aId = aData.applications.gecko.id) {
function promiseWriteWebManifestForExtension(aData, aDir, aId = aData.applications.gecko.id) {
let files = {
"manifest.json": JSON.stringify(aData),
}
let promise = AddonTestUtils.promiseWriteFilesToExtension(aDir.path, aId, files);
return awaitPromise(promise);
return AddonTestUtils.promiseWriteFilesToExtension(aDir.path, aId, files);
}
var {writeFilesToZip} = AddonTestUtils;

View File

@ -20,6 +20,19 @@ const manifestSample = {
}],
};
const { Management } = Components.utils.import("resource://gre/modules/Extension.jsm", {});
function promiseAddonStartup() {
return new Promise(resolve => {
let listener = (extension) => {
Management.off("startup", listener);
resolve(extension);
};
Management.on("startup", listener);
});
}
function* installAddon(fixtureName, addonID) {
yield promiseInstallAllFiles([do_get_addon(fixtureName)]);
return promiseAddonByID(addonID);
@ -110,38 +123,58 @@ add_task(function* test_can_reload_permanent_addon() {
yield tearDownAddon(addon);
});
add_task(function* test_disabled_addon_can_be_enabled_after_reload() {
add_task(function* test_reload_to_invalid_version_fails() {
yield promiseRestartManager();
let tempdir = gTmpD.clone();
// Create an add-on with strictCompatibility which should cause it
// to be appDisabled.
const unpackedAddon = writeInstallRDFToDir(
Object.assign({}, manifestSample, {
strictCompatibility: true,
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "0.1",
maxVersion: "0.1"
}],
}), tempdir, manifestSample.id, "bootstrap.js");
// The initial version of the add-on will be compatible, and will therefore load
const addonId = "invalid_version_cannot_be_reloaded@tests.mozilla.org";
let manifest = {
name: "invalid_version_cannot_be_reloaded",
description: "test invalid_version_cannot_be_reloaded",
manifest_version: 2,
version: "1.0",
applications: {
gecko: {
id: addonId,
}
},
};
yield AddonManager.installTemporaryAddon(unpackedAddon);
const addon = yield promiseAddonByID(manifestSample.id);
let addonDir = yield promiseWriteWebManifestForExtension(manifest, tempdir, "invalid_version");
yield AddonManager.installTemporaryAddon(addonDir);
yield promiseAddonStartup();
let addon = yield promiseAddonByID(addonId);
notEqual(addon, null);
equal(addon.appDisabled, true);
equal(addon.id, addonId);
equal(addon.version, "1.0");
equal(addon.appDisabled, false);
equal(addon.userDisabled, false);
addonDir.remove(true);
// Remove strictCompatibility from the manifest.
writeInstallRDFToDir(manifestSample, tempdir, manifestSample.id);
// update the manifest to make the add-on version incompatible, so the reload will reject
manifest.applications.gecko.strict_min_version = "1";
manifest.applications.gecko.strict_max_version = "1";
manifest.version = "2.0";
yield addon.reload();
addonDir = yield promiseWriteWebManifestForExtension(manifest, tempdir, "invalid_version", false);
let expectedMsg = new RegExp("Add-on invalid_version_cannot_be_reloaded@tests.mozilla.org is not compatible with application version. " +
"add-on minVersion: 1, add-on maxVersion: 1");
const reloadedAddon = yield promiseAddonByID(manifestSample.id);
yield Assert.rejects(addon.reload(),
expectedMsg,
"Reload rejects when application version does not fall between minVersion and maxVersion");
let reloadedAddon = yield promiseAddonByID(addonId);
notEqual(reloadedAddon, null);
equal(reloadedAddon.id, addonId);
equal(reloadedAddon.version, "1.0");
equal(reloadedAddon.appDisabled, false);
equal(reloadedAddon.userDisabled, false);
yield tearDownAddon(reloadedAddon);
unpackedAddon.remove(true);
addonDir.remove(true);
});
add_task(function* test_manifest_changes_are_refreshed() {

View File

@ -124,7 +124,7 @@ add_task(function*() {
// Writing the manifest direct to the profile should work
add_task(function*() {
writeWebManifestForExtension({
yield promiseWriteWebManifestForExtension({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
@ -190,7 +190,7 @@ add_task(function* test_manifest_localization() {
// Missing version should cause a failure
add_task(function*() {
writeWebManifestForExtension({
yield promiseWriteWebManifestForExtension({
name: "Web Extension Name",
manifest_version: 2,
applications: {
@ -213,7 +213,7 @@ add_task(function*() {
// Incorrect manifest version should cause a failure
add_task(function*() {
writeWebManifestForExtension({
yield promiseWriteWebManifestForExtension({
name: "Web Extension Name",
version: "1.0",
manifest_version: 1,

View File

@ -26,7 +26,7 @@ function promiseAddonStartup() {
// Test simple icon set parsing
add_task(function*() {
writeWebManifestForExtension({
yield promiseWriteWebManifestForExtension({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
@ -90,7 +90,7 @@ add_task(function*() {
// Test AddonManager.getPreferredIconURL for retina screen sizes
add_task(function*() {
writeWebManifestForExtension({
yield promiseWriteWebManifestForExtension({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
@ -136,7 +136,7 @@ add_task(function*() {
// Handles no icons gracefully
add_task(function*() {
writeWebManifestForExtension({
yield promiseWriteWebManifestForExtension({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,

View File

@ -79,7 +79,7 @@ add_task(function* test_unsigned_no_id_temp_install() {
version: "1.0"
};
const addonDir = writeWebManifestForExtension(manifest, gTmpD,
const addonDir = yield promiseWriteWebManifestForExtension(manifest, gTmpD,
"the-addon-sub-dir");
const addon = yield AddonManager.installTemporaryAddon(addonDir);
ok(addon.id, "ID should have been auto-generated");
@ -112,9 +112,9 @@ add_task(function* test_multiple_no_id_extensions() {
version: "1.0"
};
const firstAddonDir = writeWebManifestForExtension(manifest, gTmpD,
const firstAddonDir = yield promiseWriteWebManifestForExtension(manifest, gTmpD,
"addon-sub-dir-one");
const secondAddonDir = writeWebManifestForExtension(manifest, gTmpD,
const secondAddonDir = yield promiseWriteWebManifestForExtension(manifest, gTmpD,
"addon-sub-dir-two");
const [firstAddon, secondAddon] = yield Promise.all([
AddonManager.installTemporaryAddon(firstAddonDir),
@ -155,7 +155,7 @@ add_task(function* test_bss_id() {
let addon = yield promiseAddonByID(ID);
do_check_eq(addon, null);
writeWebManifestForExtension(manifest, profileDir, ID);
yield promiseWriteWebManifestForExtension(manifest, profileDir, ID);
yield promiseRestartManager();
addon = yield promiseAddonByID(ID);
@ -189,7 +189,7 @@ add_task(function* test_two_ids() {
}
}
writeWebManifestForExtension(manifest, profileDir, GOOD_ID);
yield promiseWriteWebManifestForExtension(manifest, profileDir, GOOD_ID);
yield promiseRestartManager();
let addon = yield promiseAddonByID(BAD_ID);
@ -199,3 +199,168 @@ add_task(function* test_two_ids() {
addon.uninstall();
});
// Test that strict_min_version and strict_max_version are enforced for
// loading temporary extension.
add_task(function* test_strict_min_max() {
// the app version being compared to is 1.9.2
const addonId = "strict_min_max@tests.mozilla.org";
const MANIFEST = {
name: "strict min max test",
description: "test strict min and max with temporary loading",
manifest_version: 2,
version: "1.0",
};
function flushAndRemove(file) {
// flush JAR cache and remove the file
Services.obs.notifyObservers(file, "flush-cache-entry", null);
file.remove(true);
}
// bad max good min
let apps = {
applications: {
gecko: {
id: addonId,
strict_min_version: "1",
strict_max_version: "1"
},
},
}
let testManifest = Object.assign(apps, MANIFEST);
let addonDir = yield promiseWriteWebManifestForExtension(testManifest, gTmpD,
"the-addon-sub-dir");
let expectedMsg = new RegExp("Add-on strict_min_max@tests.mozilla.org is not compatible with application version. " +
"add-on minVersion: 1, add-on maxVersion: 1");
yield Assert.rejects(AddonManager.installTemporaryAddon(addonDir),
expectedMsg,
"Install rejects when specified maxVersion is not valid");
let addon = yield promiseAddonByID(addonId);
do_check_eq(addon, null);
flushAndRemove(addonDir);
// bad min good max
apps = {
applications: {
gecko: {
id: addonId,
strict_min_version: "2",
strict_max_version: "2"
},
},
}
testManifest = Object.assign(apps, MANIFEST);
addonDir = yield promiseWriteWebManifestForExtension(testManifest, gTmpD,
"the-addon-sub-dir");
expectedMsg = new RegExp("Add-on strict_min_max@tests.mozilla.org is not compatible with application version. " +
"add-on minVersion: 2, add-on maxVersion: 2");
yield Assert.rejects(AddonManager.installTemporaryAddon(addonDir),
expectedMsg,
"Install rejects when specified minVersion is not valid");
addon = yield promiseAddonByID(addonId);
do_check_eq(addon, null);
flushAndRemove(addonDir);
// bad both
apps = {
applications: {
gecko: {
id: addonId,
strict_min_version: "2",
strict_max_version: "1"
},
},
}
testManifest = Object.assign(apps, MANIFEST);
addonDir = yield promiseWriteWebManifestForExtension(testManifest, gTmpD,
"the-addon-sub-dir");
expectedMsg = new RegExp("Add-on strict_min_max@tests.mozilla.org is not compatible with application version. " +
"add-on minVersion: 2, add-on maxVersion: 1");
yield Assert.rejects(AddonManager.installTemporaryAddon(addonDir),
expectedMsg,
"Install rejects when specified minVersion and maxVersion are not valid");
addon = yield promiseAddonByID(addonId);
do_check_eq(addon, null);
flushAndRemove(addonDir);
// good both
apps = {
applications: {
gecko: {
id: addonId,
strict_min_version: "1",
strict_max_version: "2"
},
},
}
testManifest = Object.assign(apps, MANIFEST);
addonDir = yield promiseWriteWebManifestForExtension(testManifest, gTmpD,
"strict_min_max");
yield AddonManager.installTemporaryAddon(addonDir);
addon = yield promiseAddonByID(addonId);
do_check_neq(addon, null);
do_check_eq(addon.id, addonId);
addon.uninstall();
flushAndRemove(addonDir);
// good only min
let newId = "strict_min_only@tests.mozilla.org";
apps = {
applications: {
gecko: {
id: newId,
strict_min_version: "1",
},
},
}
testManifest = Object.assign(apps, MANIFEST);
addonDir = yield promiseWriteWebManifestForExtension(testManifest, gTmpD,
"strict_min_only");
yield AddonManager.installTemporaryAddon(addonDir);
addon = yield promiseAddonByID(newId);
do_check_neq(addon, null);
do_check_eq(addon.id, newId);
addon.uninstall();
flushAndRemove(addonDir);
// good only max
newId = "strict_max_only@tests.mozilla.org";
apps = {
applications: {
gecko: {
id: newId,
strict_max_version: "2",
},
},
}
testManifest = Object.assign(apps, MANIFEST);
addonDir = yield promiseWriteWebManifestForExtension(testManifest, gTmpD,
"strict_max_only");
yield AddonManager.installTemporaryAddon(addonDir);
addon = yield promiseAddonByID(newId);
do_check_neq(addon, null);
do_check_eq(addon.id, newId);
addon.uninstall();
flushAndRemove(addonDir);
});

View File

@ -37,7 +37,7 @@ add_task(function* test_bad_unpacked_path() {
for (let dir of directories) {
try {
writeWebManifestForExtension(manifest, profileDir, dir);
yield promiseWriteWebManifestForExtension(manifest, profileDir, dir);
} catch (ex) {
// This can fail if the underlying filesystem (looking at you windows)
// doesn't handle some of the characters in the ID. In that case,

View File

@ -6,7 +6,8 @@ tags = addons
[test_AddonRepository.js]
[test_reload.js]
# Bug 676992: test consistently hangs on Android
skip-if = os == "android"
# There's a problem removing a temp file without manually clearing the cache on Windows
skip-if = os == "android" || os == "win"
tags = webextensions
[test_AddonRepository_cache.js]
# Bug 676992: test consistently hangs on Android