зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to central, a=merge
MozReview-Commit-ID: 47exp5fFJe1
This commit is contained in:
Коммит
008683d974
|
@ -134,3 +134,6 @@ GPATH
|
|||
|
||||
# tup database
|
||||
^\.tup
|
||||
|
||||
subinclude:servo/.hgignore
|
||||
|
||||
|
|
|
@ -27,6 +27,11 @@ function* test_bookmarks_popup({isNewBookmark, popupShowFn, popupEditFn,
|
|||
});
|
||||
}
|
||||
|
||||
info(`BookmarkingUI.status is ${BookmarkingUI.status}`);
|
||||
yield BrowserTestUtils.waitForCondition(
|
||||
() => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING,
|
||||
"BookmarkingUI should not be updating");
|
||||
|
||||
is(bookmarkStar.hasAttribute("starred"), !isNewBookmark,
|
||||
"Page should only be starred prior to popupshown if editing bookmark");
|
||||
is(bookmarkPanel.state, "closed", "Panel should be 'closed' to start test");
|
||||
|
|
|
@ -94,6 +94,18 @@ const PAGECONTENT_COLORS =
|
|||
' <option value="Four" class="defaultColor defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
|
||||
' <option value="Five" class="defaultColor">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
|
||||
' <option value="Six" class="defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
|
||||
' <option value="Seven" selected="true">{"unstyled": "true"}</option>' +
|
||||
"</select></body></html>";
|
||||
|
||||
const PAGECONTENT_COLORS_ON_SELECT =
|
||||
"<html><head><style>" +
|
||||
" #one { background-color: #7E3A3A; color: #fff }" +
|
||||
"</style>" +
|
||||
"<body><select id='one'>" +
|
||||
' <option value="One">{"color": "rgb(255, 255, 255)", "backgroundColor": "transparent"}</option>' +
|
||||
' <option value="Two">{"color": "rgb(255, 255, 255)", "backgroundColor": "transparent"}</option>' +
|
||||
' <option value="Three">{"color": "rgb(255, 255, 255)", "backgroundColor": "transparent"}</option>' +
|
||||
' <option value="Four" selected="true">{"end": "true"}</option>' +
|
||||
"</select></body></html>";
|
||||
|
||||
function openSelectPopup(selectPopup, mode = "key", selector = "select", win = window) {
|
||||
|
@ -150,6 +162,38 @@ function getClickEvents() {
|
|||
});
|
||||
}
|
||||
|
||||
function testOptionColors(index, item, menulist) {
|
||||
let expected = JSON.parse(item.label);
|
||||
|
||||
for (let color of Object.keys(expected)) {
|
||||
if (color.toLowerCase().includes("color") &&
|
||||
!expected[color].startsWith("rgb")) {
|
||||
// Need to convert system color to RGB color.
|
||||
let textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
|
||||
textarea.style.color = expected[color];
|
||||
expected[color] = getComputedStyle(textarea).color;
|
||||
}
|
||||
}
|
||||
|
||||
// Press Down to move the selected item to the next item in the
|
||||
// list and check the colors of this item when it's not selected.
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
|
||||
|
||||
if (expected.end) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (expected.unstyled) {
|
||||
ok(!item.hasAttribute("customoptionstyling"),
|
||||
`Item ${index} should not have any custom option styling`);
|
||||
} else {
|
||||
is(getComputedStyle(item).color, expected.color,
|
||||
"Item " + (index) + " has correct foreground color");
|
||||
is(getComputedStyle(item).backgroundColor, expected.backgroundColor,
|
||||
"Item " + (index) + " has correct background color");
|
||||
}
|
||||
}
|
||||
|
||||
function* doSelectTests(contentType, dtd) {
|
||||
const pageUrl = "data:" + contentType + "," + escape(dtd + "\n" + PAGECONTENT);
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
|
||||
|
@ -745,11 +789,13 @@ add_task(function* test_somehidden() {
|
|||
yield BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(function* test_colors_applied_to_popup() {
|
||||
// This test checks when a <select> element has styles applied to <option>s within it.
|
||||
add_task(function* test_colors_applied_to_popup_items() {
|
||||
const pageUrl = "data:text/html," + escape(PAGECONTENT_COLORS);
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
|
||||
|
||||
let selectPopup = document.getElementById("ContentSelectDropdown").menupopup;
|
||||
let menulist = document.getElementById("ContentSelectDropdown");
|
||||
let selectPopup = menulist.menupopup;
|
||||
|
||||
let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
|
||||
|
@ -757,38 +803,47 @@ add_task(function* test_colors_applied_to_popup() {
|
|||
|
||||
// The label contains a JSON string of the expected colors for
|
||||
// `color` and `background-color`.
|
||||
is(selectPopup.parentNode.itemCount, 6, "Correct number of items");
|
||||
is(selectPopup.parentNode.itemCount, 7, "Correct number of items");
|
||||
let child = selectPopup.firstChild;
|
||||
let idx = 1;
|
||||
|
||||
ok(child.selected, "The first child should be selected");
|
||||
ok(!child.selected, "The first child should not be selected");
|
||||
while (child) {
|
||||
let expected = JSON.parse(child.label);
|
||||
|
||||
for (let color of Object.keys(expected)) {
|
||||
if (color.toLowerCase().includes("color") &&
|
||||
!expected[color].startsWith("rgb")) {
|
||||
// Need to convert system color to RGB color.
|
||||
let textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
|
||||
textarea.style.color = expected[color];
|
||||
expected[color] = getComputedStyle(textarea).color;
|
||||
}
|
||||
}
|
||||
|
||||
// Press Down to move the selected item to the next item in the
|
||||
// list and check the colors of this item when it's not selected.
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
|
||||
|
||||
if (expected.unstyled) {
|
||||
ok(!child.hasAttribute("customoptionstyling"),
|
||||
`Item ${idx} should not have any custom option styling`);
|
||||
} else {
|
||||
is(getComputedStyle(child).color, expected.color,
|
||||
"Item " + (idx) + " has correct foreground color");
|
||||
is(getComputedStyle(child).backgroundColor, expected.backgroundColor,
|
||||
"Item " + (idx) + " has correct background color");
|
||||
}
|
||||
|
||||
testOptionColors(idx, child, menulist);
|
||||
idx++;
|
||||
child = child.nextSibling;
|
||||
}
|
||||
|
||||
yield hideSelectPopup(selectPopup, "escape");
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
// This test checks when a <select> element has styles applied to itself.
|
||||
add_task(function* test_colors_applied_to_popup() {
|
||||
const pageUrl = "data:text/html," + escape(PAGECONTENT_COLORS_ON_SELECT);
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
|
||||
|
||||
let menulist = document.getElementById("ContentSelectDropdown");
|
||||
let selectPopup = menulist.menupopup;
|
||||
|
||||
let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
|
||||
yield popupShownPromise;
|
||||
|
||||
// The label contains a JSON string of the expected colors for
|
||||
// `color` and `background-color`.
|
||||
is(selectPopup.parentNode.itemCount, 4, "Correct number of items");
|
||||
let child = selectPopup.firstChild;
|
||||
let idx = 1;
|
||||
|
||||
is(getComputedStyle(selectPopup).color, "rgb(255, 255, 255)",
|
||||
"popup has expected foreground color");
|
||||
is(getComputedStyle(selectPopup).backgroundColor, "rgb(126, 58, 58)",
|
||||
"popup has expected background color");
|
||||
|
||||
ok(!child.selected, "The first child should not be selected");
|
||||
while (child) {
|
||||
testOptionColors(idx, child, menulist);
|
||||
idx++;
|
||||
child = child.nextSibling;
|
||||
}
|
||||
|
|
|
@ -148,9 +148,11 @@ var tests = [
|
|||
EventUtils.synthesizeMouseAtCenter(checkbox, {});
|
||||
dismissNotification(popup);
|
||||
},
|
||||
onHidden(popup) {
|
||||
*onHidden(popup) {
|
||||
let icon = document.getElementById("default-notification-icon");
|
||||
let shown = waitForNotificationPanel();
|
||||
EventUtils.synthesizeMouseAtCenter(icon, {});
|
||||
yield shown;
|
||||
let notification = popup.childNodes[0];
|
||||
let checkbox = notification.checkbox;
|
||||
checkCheckbox(checkbox, "This is a checkbox", true);
|
||||
|
|
|
@ -101,8 +101,8 @@ function* runNextTest() {
|
|||
});
|
||||
onPopupEvent("popuphidden", function() {
|
||||
info("[" + nextTest.id + "] popup hidden");
|
||||
nextTest.onHidden(this);
|
||||
goNext();
|
||||
Task.spawn(() => nextTest.onHidden(this))
|
||||
.then(() => goNext(), ex => Assert.ok(false, "onHidden failed: " + ex));
|
||||
}, () => shownState);
|
||||
info("[" + nextTest.id + "] added listeners; panel is open: " + PopupNotifications.isPanelOpen);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js")
|
|||
const TEST_HOST = "example.com";
|
||||
const TEST_ORIGIN = "http://" + TEST_HOST;
|
||||
const TEST_BASE_URL = TEST_ORIGIN + "/browser/browser/components/preferences/in-content/tests/";
|
||||
const REMOVE_DIALOG_URL = "chrome://browser/content/preferences/siteDataRemoveSelected.xul";
|
||||
|
||||
const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
|
||||
const { SiteDataManager } = Cu.import("resource:///modules/SiteDataManager.jsm", {});
|
||||
|
@ -187,6 +188,22 @@ function promiseCookiesCleared() {
|
|||
});
|
||||
}
|
||||
|
||||
function assertSitesListed(doc, origins) {
|
||||
let frameDoc = doc.getElementById("dialogFrame").contentDocument;
|
||||
let removeBtn = frameDoc.getElementById("removeSelected");
|
||||
let removeAllBtn = frameDoc.getElementById("removeAll");
|
||||
let sitesList = frameDoc.getElementById("sitesList");
|
||||
let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
|
||||
is(totalSitesNumber, origins.length, "Should list the right sites number");
|
||||
origins.forEach(origin => {
|
||||
let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
|
||||
let host = site.getAttribute("host");
|
||||
ok(origin.includes(host), `Should list the site of ${origin}`);
|
||||
});
|
||||
is(removeBtn.disabled, false, "Should enable the removeSelected button");
|
||||
is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button");
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.sinon;
|
||||
delete window.setImmediate;
|
||||
|
@ -370,28 +387,18 @@ add_task(function* () {
|
|||
|
||||
searchBox.value = "xyz";
|
||||
searchBox.doCommand();
|
||||
assertSitesListed(mockOrigins.filter(o => o.includes("xyz")));
|
||||
assertSitesListed(doc, mockOrigins.filter(o => o.includes("xyz")));
|
||||
|
||||
searchBox.value = "bar";
|
||||
searchBox.doCommand();
|
||||
assertSitesListed(mockOrigins.filter(o => o.includes("bar")));
|
||||
assertSitesListed(doc, mockOrigins.filter(o => o.includes("bar")));
|
||||
|
||||
searchBox.value = "";
|
||||
searchBox.doCommand();
|
||||
assertSitesListed(mockOrigins);
|
||||
assertSitesListed(doc, mockOrigins);
|
||||
|
||||
mockSiteDataManager.unregister();
|
||||
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
|
||||
function assertSitesListed(origins) {
|
||||
let sitesList = frameDoc.getElementById("sitesList");
|
||||
let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
|
||||
is(totalSitesNumber, origins.length, "Should list the right sites number");
|
||||
origins.forEach(origin => {
|
||||
let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
|
||||
ok(site instanceof XULElement, `Should list the site of ${origin}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Test selecting and removing all sites one by one
|
||||
|
@ -478,19 +485,23 @@ add_task(function* () {
|
|||
function assertAllSitesListed() {
|
||||
frameDoc = doc.getElementById("dialogFrame").contentDocument;
|
||||
let removeBtn = frameDoc.getElementById("removeSelected");
|
||||
let removeAllBtn = frameDoc.getElementById("removeAll");
|
||||
let sitesList = frameDoc.getElementById("sitesList");
|
||||
let sites = sitesList.getElementsByTagName("richlistitem");
|
||||
is(sites.length, fakeOrigins.length, "Should list all sites");
|
||||
is(removeBtn.disabled, false, "Should enable the removeSelected button");
|
||||
is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button");
|
||||
}
|
||||
|
||||
function assertAllSitesNotListed() {
|
||||
frameDoc = doc.getElementById("dialogFrame").contentDocument;
|
||||
let removeBtn = frameDoc.getElementById("removeSelected");
|
||||
let removeAllBtn = frameDoc.getElementById("removeAll");
|
||||
let sitesList = frameDoc.getElementById("sitesList");
|
||||
let sites = sitesList.getElementsByTagName("richlistitem");
|
||||
is(sites.length, 0, "Should not list all sites");
|
||||
is(removeBtn.disabled, true, "Should disable the removeSelected button");
|
||||
is(removeAllBtn.disabled, true, "Should disable the removeAllBtn button");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -512,7 +523,6 @@ add_task(function* () {
|
|||
yield updatePromise;
|
||||
yield openSettingsDialog();
|
||||
|
||||
const removeDialogURL = "chrome://browser/content/preferences/siteDataRemoveSelected.xul";
|
||||
let doc = gBrowser.selectedBrowser.contentDocument;
|
||||
let frameDoc = null;
|
||||
let saveBtn = null;
|
||||
|
@ -521,44 +531,44 @@ add_task(function* () {
|
|||
let settingsDialogClosePromise = null;
|
||||
|
||||
// Test the initial state
|
||||
assertSitesListed(fakeOrigins);
|
||||
assertSitesListed(doc, fakeOrigins);
|
||||
|
||||
// Test the "Cancel" button
|
||||
settingsDialogClosePromise = promiseSettingsDialogClose();
|
||||
frameDoc = doc.getElementById("dialogFrame").contentDocument;
|
||||
cancelBtn = frameDoc.getElementById("cancel");
|
||||
removeSelectedSite(fakeOrigins.slice(0, 4));
|
||||
assertSitesListed(fakeOrigins.slice(4));
|
||||
assertSitesListed(doc, fakeOrigins.slice(4));
|
||||
cancelBtn.doCommand();
|
||||
yield settingsDialogClosePromise;
|
||||
yield openSettingsDialog();
|
||||
assertSitesListed(fakeOrigins);
|
||||
assertSitesListed(doc, fakeOrigins);
|
||||
|
||||
// Test the "Save Changes" button but canceling save
|
||||
removeDialogOpenPromise = promiseWindowDialogOpen("cancel", removeDialogURL);
|
||||
removeDialogOpenPromise = promiseWindowDialogOpen("cancel", REMOVE_DIALOG_URL);
|
||||
settingsDialogClosePromise = promiseSettingsDialogClose();
|
||||
frameDoc = doc.getElementById("dialogFrame").contentDocument;
|
||||
saveBtn = frameDoc.getElementById("save");
|
||||
removeSelectedSite(fakeOrigins.slice(0, 4));
|
||||
assertSitesListed(fakeOrigins.slice(4));
|
||||
assertSitesListed(doc, fakeOrigins.slice(4));
|
||||
saveBtn.doCommand();
|
||||
yield removeDialogOpenPromise;
|
||||
yield settingsDialogClosePromise;
|
||||
yield openSettingsDialog();
|
||||
assertSitesListed(fakeOrigins);
|
||||
assertSitesListed(doc, fakeOrigins);
|
||||
|
||||
// Test the "Save Changes" button and accepting save
|
||||
removeDialogOpenPromise = promiseWindowDialogOpen("accept", removeDialogURL);
|
||||
removeDialogOpenPromise = promiseWindowDialogOpen("accept", REMOVE_DIALOG_URL);
|
||||
settingsDialogClosePromise = promiseSettingsDialogClose();
|
||||
frameDoc = doc.getElementById("dialogFrame").contentDocument;
|
||||
saveBtn = frameDoc.getElementById("save");
|
||||
removeSelectedSite(fakeOrigins.slice(0, 4));
|
||||
assertSitesListed(fakeOrigins.slice(4));
|
||||
assertSitesListed(doc, fakeOrigins.slice(4));
|
||||
saveBtn.doCommand();
|
||||
yield removeDialogOpenPromise;
|
||||
yield settingsDialogClosePromise;
|
||||
yield openSettingsDialog();
|
||||
assertSitesListed(fakeOrigins.slice(4));
|
||||
assertSitesListed(doc, fakeOrigins.slice(4));
|
||||
|
||||
// Always clean up the fake origins
|
||||
fakeOrigins.forEach(origin => removePersistentStoragePerm(origin));
|
||||
|
@ -578,17 +588,48 @@ add_task(function* () {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
function assertSitesListed(origins) {
|
||||
frameDoc = doc.getElementById("dialogFrame").contentDocument;
|
||||
let removeBtn = frameDoc.getElementById("removeSelected");
|
||||
let sitesList = frameDoc.getElementById("sitesList");
|
||||
let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
|
||||
is(totalSitesNumber, origins.length, "Should list the right sites number");
|
||||
origins.forEach(origin => {
|
||||
let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
|
||||
ok(!!site, `Should list the site of ${origin}`);
|
||||
});
|
||||
is(removeBtn.disabled, false, "Should enable the removeSelected button");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
|
||||
let fakeOrigins = [
|
||||
"https://news.foo.com/",
|
||||
"https://books.foo.com/",
|
||||
"https://mails.bar.com/",
|
||||
"https://account.bar.com/",
|
||||
"https://videos.xyz.com/",
|
||||
"https://shopping.xyz.com/"
|
||||
];
|
||||
fakeOrigins.forEach(origin => addPersistentStoragePerm(origin));
|
||||
|
||||
let updatePromise = promiseSitesUpdated();
|
||||
yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
|
||||
yield updatePromise;
|
||||
yield openSettingsDialog();
|
||||
|
||||
// Search "foo" to only list foo.com sites
|
||||
let doc = gBrowser.selectedBrowser.contentDocument;
|
||||
let frameDoc = doc.getElementById("dialogFrame").contentDocument;
|
||||
let searchBox = frameDoc.getElementById("searchBox");
|
||||
searchBox.value = "foo";
|
||||
searchBox.doCommand();
|
||||
assertSitesListed(doc, fakeOrigins.slice(0, 2));
|
||||
|
||||
// Test only removing all visible sites listed
|
||||
updatePromise = promiseSitesUpdated();
|
||||
let acceptRemovePromise = promiseWindowDialogOpen("accept", REMOVE_DIALOG_URL);
|
||||
let settingsDialogClosePromise = promiseSettingsDialogClose();
|
||||
let removeAllBtn = frameDoc.getElementById("removeAll");
|
||||
let saveBtn = frameDoc.getElementById("save");
|
||||
removeAllBtn.doCommand();
|
||||
saveBtn.doCommand();
|
||||
yield acceptRemovePromise;
|
||||
yield settingsDialogClosePromise;
|
||||
yield updatePromise;
|
||||
yield openSettingsDialog();
|
||||
assertSitesListed(doc, fakeOrigins.slice(2));
|
||||
|
||||
// Always clean up the fake origins
|
||||
fakeOrigins.forEach(origin => removePersistentStoragePerm(origin));
|
||||
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
|
|
|
@ -40,23 +40,25 @@ let gSiteDataSettings = {
|
|||
let sortCol = document.getElementById("hostCol");
|
||||
this._sortSites(this._sites, sortCol);
|
||||
this._buildSitesList(this._sites);
|
||||
this._updateButtonsState();
|
||||
Services.obs.notifyObservers(null, "sitedata-settings-init", null);
|
||||
});
|
||||
|
||||
setEventListener("hostCol", "click", this.onClickTreeCol);
|
||||
setEventListener("usageCol", "click", this.onClickTreeCol);
|
||||
setEventListener("statusCol", "click", this.onClickTreeCol);
|
||||
setEventListener("searchBox", "command", this.onCommandSearch);
|
||||
setEventListener("cancel", "command", this.close);
|
||||
setEventListener("save", "command", this.saveChanges);
|
||||
setEventListener("removeSelected", "command", this.removeSelected);
|
||||
setEventListener("searchBox", "command", this.onCommandSearch);
|
||||
setEventListener("removeAll", "command", this.onClickRemoveAll);
|
||||
setEventListener("removeSelected", "command", this.onClickRemoveSelected);
|
||||
},
|
||||
|
||||
_updateButtonsState() {
|
||||
let items = this._list.getElementsByTagName("richlistitem");
|
||||
let removeBtn = document.getElementById("removeSelected");
|
||||
removeBtn.disabled = !(items.length > 0);
|
||||
let removeSelectedBtn = document.getElementById("removeSelected");
|
||||
let removeAllBtn = document.getElementById("removeAll");
|
||||
removeSelectedBtn.disabled = items.length == 0;
|
||||
removeAllBtn.disabled = removeSelectedBtn.disabled;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -136,30 +138,22 @@ let gSiteDataSettings = {
|
|||
item.setAttribute("usage", prefStrBundle.getFormattedString("siteUsage", size));
|
||||
this._list.appendChild(item);
|
||||
}
|
||||
this._updateButtonsState();
|
||||
},
|
||||
|
||||
onClickTreeCol(e) {
|
||||
this._sortSites(this._sites, e.target);
|
||||
this._buildSitesList(this._sites);
|
||||
},
|
||||
|
||||
onCommandSearch() {
|
||||
this._buildSitesList(this._sites);
|
||||
},
|
||||
|
||||
removeSelected() {
|
||||
let selected = this._list.selectedItem;
|
||||
if (selected) {
|
||||
let origin = selected.getAttribute("data-origin");
|
||||
_removeSiteItems(items) {
|
||||
for (let i = items.length - 1; i >= 0; --i) {
|
||||
let item = items[i];
|
||||
let origin = item.getAttribute("data-origin");
|
||||
for (let site of this._sites) {
|
||||
if (site.uri.spec === origin) {
|
||||
site.userAction = "remove";
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._list.removeChild(selected);
|
||||
this._updateButtonsState();
|
||||
item.remove();
|
||||
}
|
||||
this._updateButtonsState();
|
||||
},
|
||||
|
||||
saveChanges() {
|
||||
|
@ -235,5 +229,28 @@ let gSiteDataSettings = {
|
|||
|
||||
close() {
|
||||
window.close();
|
||||
},
|
||||
|
||||
onClickTreeCol(e) {
|
||||
this._sortSites(this._sites, e.target);
|
||||
this._buildSitesList(this._sites);
|
||||
},
|
||||
|
||||
onCommandSearch() {
|
||||
this._buildSitesList(this._sites);
|
||||
},
|
||||
|
||||
onClickRemoveSelected() {
|
||||
let selected = this._list.selectedItem;
|
||||
if (selected) {
|
||||
this._removeSiteItems([selected]);
|
||||
}
|
||||
},
|
||||
|
||||
onClickRemoveAll() {
|
||||
let siteItems = this._list.getElementsByTagName("richlistitem");
|
||||
if (siteItems.length > 0) {
|
||||
this._removeSiteItems(siteItems);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
|
||||
<hbox align="start">
|
||||
<button id="removeSelected" label="&removeSelected.label;" accesskey="&removeSelected.accesskey;"/>
|
||||
<button id="removeAll" label="&removeAll.label;" accesskey="&removeAll.accesskey;"/>
|
||||
</hbox>
|
||||
|
||||
<vbox align="end">
|
||||
|
|
|
@ -1561,10 +1561,22 @@
|
|||
if (rowCount == 1 && hasDummyItems) {
|
||||
// When there's only one row, make the compact settings button
|
||||
// hug the right edge of the panel. It may not due to the panel's
|
||||
// width not being an integral factor of the button width. (See
|
||||
// width not being an integral multiple of the button width. (See
|
||||
// the "There will be an emtpy area" comment above.) Increase the
|
||||
// width of the last dummy item by the remainder.
|
||||
//
|
||||
// There's one weird thing to guard against. When layout pixels
|
||||
// aren't an integral multiple of device pixels, the calculated
|
||||
// remainder can end up being ~1px too big, at least on Windows,
|
||||
// which pushes the settings button to a new row. The remainder
|
||||
// is integral, not a fraction, so that's not the problem. To
|
||||
// work around that, unscale the remainder, floor it, scale it
|
||||
// back, and then floor that.
|
||||
let scale = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.screenPixelsPerCSSPixel;
|
||||
let remainder = panelWidth - (enginesPerRow * buttonWidth);
|
||||
remainder = Math.floor(Math.floor(remainder * scale) / scale);
|
||||
let width = remainder + buttonWidth;
|
||||
let lastDummyItem = this.settingsButton.previousSibling;
|
||||
lastDummyItem.setAttribute("width", width);
|
||||
|
|
|
@ -27,9 +27,12 @@ skip-if = os == "mac" # bug 967013
|
|||
[browser_ddg.js]
|
||||
[browser_ddg_behavior.js]
|
||||
[browser_google.js]
|
||||
skip-if = artifact # bug 1315953
|
||||
[browser_google_codes.js]
|
||||
skip-if = artifact # bug 1315953
|
||||
[browser_google_nocodes.js]
|
||||
[browser_google_behavior.js]
|
||||
skip-if = artifact # bug 1315953
|
||||
[browser_healthreport.js]
|
||||
[browser_hiddenOneOffs_cleanup.js]
|
||||
[browser_hiddenOneOffs_diacritics.js]
|
||||
|
|
|
@ -41,19 +41,19 @@
|
|||
"filename": "dmg.tar.xz"
|
||||
},
|
||||
{
|
||||
"size": 188880,
|
||||
"visibility": "public",
|
||||
"digest": "1ffddd43efb03aed897ee42035d9d8d758a8d66ab6c867599ef755e1a586768fc22011ce03698af61454920b00fe8bed08c9a681e7bd324d7f8f78c026c83943",
|
||||
"algorithm": "sha512",
|
||||
"unpack": true,
|
||||
"filename": "genisoimage.tar.xz"
|
||||
},
|
||||
{
|
||||
"version": "rustc 1.14.0 (e8a012324 2016-12-16) repack",
|
||||
"size": 152573516,
|
||||
"digest": "eef2f10bf57005d11c34b9b49eb76d0f09d026295055039fea89952a3be51580abdab29366278ed4bfa393b33c5cee4d51d3af4221e9e7d7d833d0fc1347597c",
|
||||
"algorithm": "sha512",
|
||||
"filename": "rustc.tar.xz",
|
||||
"unpack": true
|
||||
},
|
||||
{
|
||||
"size": 281576,
|
||||
"visibility": "public",
|
||||
"digest": "71616564533d138fb12f08e761c2638d054814fdf9c9439638ec57b201e100445c364d73d8d7a4f0e3b784898d5fe6264e8242863fc5ac40163f1791468bbc46",
|
||||
"algorithm": "sha512",
|
||||
"filename": "hfsplus-tools.tar.xz",
|
||||
"unpack": true
|
||||
}
|
||||
]
|
||||
|
|
|
@ -7,6 +7,14 @@ const {utils: Cu} = Components;
|
|||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LogManager",
|
||||
"resource://shield-recipe-client/lib/LogManager.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "RecipeRunner",
|
||||
"resource://shield-recipe-client/lib/RecipeRunner.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CleanupManager",
|
||||
"resource://shield-recipe-client/lib/CleanupManager.jsm");
|
||||
|
||||
const REASONS = {
|
||||
APP_STARTUP: 1, // The application is starting up.
|
||||
|
@ -16,7 +24,7 @@ const REASONS = {
|
|||
ADDON_INSTALL: 5, // The add-on is being installed.
|
||||
ADDON_UNINSTALL: 6, // The add-on is being uninstalled.
|
||||
ADDON_UPGRADE: 7, // The add-on is being upgraded.
|
||||
ADDON_DOWNGRADE: 8, //The add-on is being downgraded.
|
||||
ADDON_DOWNGRADE: 8, // The add-on is being downgraded.
|
||||
};
|
||||
|
||||
const PREF_BRANCH = "extensions.shield-recipe-client.";
|
||||
|
@ -53,19 +61,15 @@ this.startup = function() {
|
|||
}
|
||||
|
||||
// Setup logging and listen for changes to logging prefs
|
||||
Cu.import("resource://shield-recipe-client/lib/LogManager.jsm");
|
||||
LogManager.configure(Services.prefs.getIntPref(PREF_LOGGING_LEVEL));
|
||||
Preferences.observe(PREF_LOGGING_LEVEL, LogManager.configure);
|
||||
CleanupManager.addCleanupHandler(
|
||||
() => Preferences.ignore(PREF_LOGGING_LEVEL, LogManager.configure));
|
||||
|
||||
Cu.import("resource://shield-recipe-client/lib/RecipeRunner.jsm");
|
||||
RecipeRunner.init();
|
||||
};
|
||||
|
||||
this.shutdown = function(data, reason) {
|
||||
Preferences.ignore(PREF_LOGGING_LEVEL, LogManager.configure);
|
||||
|
||||
Cu.import("resource://shield-recipe-client/lib/CleanupManager.jsm");
|
||||
|
||||
CleanupManager.cleanup();
|
||||
|
||||
if (reason === REASONS.ADDON_DISABLE || reason === REASONS.ADDON_UNINSTALL) {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["CleanupManager"];
|
||||
|
||||
const cleanupHandlers = new Set();
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/TelemetryArchive.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
@ -74,14 +75,16 @@ this.EnvExpressions = {
|
|||
|
||||
eval(expr, extraContext = {}) {
|
||||
// First clone the extra context
|
||||
const context = Object.assign({}, extraContext);
|
||||
const context = Object.assign({normandy: {}}, extraContext);
|
||||
// jexl handles promises, so it is fine to include them in this data.
|
||||
context.telemetry = EnvExpressions.getLatestTelemetry();
|
||||
context.normandy = context.normandy || {};
|
||||
context.normandy.userId = EnvExpressions.getUserId();
|
||||
|
||||
context.normandy = Object.assign(context.normandy, {
|
||||
userId: EnvExpressions.getUserId(),
|
||||
distribution: Preferences.get("distribution.id", "default"),
|
||||
});
|
||||
|
||||
const onelineExpr = expr.replace(/[\t\n\r]/g, " ");
|
||||
|
||||
return jexl.eval(onelineExpr, context);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ Cu.import("resource://gre/modules/Log.jsm");
|
|||
|
||||
this.EXPORTED_SYMBOLS = ["LogManager"];
|
||||
|
||||
const ROOT_LOGGER_NAME = "extensions.shield-recipe-client"
|
||||
const ROOT_LOGGER_NAME = "extensions.shield-recipe-client";
|
||||
let rootLogger = null;
|
||||
|
||||
this.LogManager = {
|
||||
|
|
|
@ -75,8 +75,12 @@ this.NormandyDriver = function(sandboxManager, extraContext = {}) {
|
|||
isDefaultBrowser: ShellService.isDefaultBrowser() || null,
|
||||
searchEngine: null,
|
||||
syncSetup: Preferences.isSet("services.sync.username"),
|
||||
syncDesktopDevices: Preferences.get("services.sync.clients.devices.desktop", 0),
|
||||
syncMobileDevices: Preferences.get("services.sync.clients.devices.mobile", 0),
|
||||
syncTotalDevices: Preferences.get("services.sync.numClients", 0),
|
||||
plugins: {},
|
||||
doNotTrack: Preferences.get("privacy.donottrackheader.enabled", false),
|
||||
distribution: Preferences.get("distribution.id", "default"),
|
||||
};
|
||||
|
||||
const searchEnginePromise = new Promise(resolve => {
|
||||
|
|
|
@ -82,4 +82,13 @@ add_task(function* () {
|
|||
"[normandy.userId, normandy.injectedValue]",
|
||||
{normandy: {injectedValue: "injected"}});
|
||||
Assert.deepEqual(val, ["fake id", "injected"], "context is correctly merged");
|
||||
|
||||
// distribution id defaults to "default"
|
||||
val = yield EnvExpressions.eval("normandy.distribution");
|
||||
Assert.equal(val, "default", "distribution has a default value");
|
||||
|
||||
// distribution id is in the context
|
||||
yield SpecialPowers.pushPrefEnv({set: [["distribution.id", "funnelcake"]]});
|
||||
val = yield EnvExpressions.eval("normandy.distribution");
|
||||
Assert.equal(val, "funnelcake", "distribution is read from preferences");
|
||||
});
|
||||
|
|
|
@ -18,3 +18,32 @@ add_task(Utils.withDriver(Assert, function* userId(driver) {
|
|||
// Test that userId is a UUID
|
||||
ok(Utils.UUID_REGEX.test(driver.userId), "userId is a uuid");
|
||||
}));
|
||||
|
||||
add_task(Utils.withDriver(Assert, function* syncDeviceCounts(driver) {
|
||||
let client = yield driver.client();
|
||||
is(client.syncMobileDevices, 0, "syncMobileDevices defaults to zero");
|
||||
is(client.syncDesktopDevices, 0, "syncDesktopDevices defaults to zero");
|
||||
is(client.syncTotalDevices, 0, "syncTotalDevices defaults to zero");
|
||||
|
||||
yield SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["services.sync.numClients", 9],
|
||||
["services.sync.clients.devices.mobile", 5],
|
||||
["services.sync.clients.devices.desktop", 4],
|
||||
],
|
||||
});
|
||||
|
||||
client = yield driver.client();
|
||||
is(client.syncMobileDevices, 5, "syncMobileDevices is read when set");
|
||||
is(client.syncDesktopDevices, 4, "syncDesktopDevices is read when set");
|
||||
is(client.syncTotalDevices, 9, "syncTotalDevices is read when set");
|
||||
}));
|
||||
|
||||
add_task(Utils.withDriver(Assert, function* distribution(driver) {
|
||||
let client = yield driver.client();
|
||||
is(client.distribution, "default", "distribution has a default value");
|
||||
|
||||
yield SpecialPowers.pushPrefEnv({set: [["distribution.id", "funnelcake"]]});
|
||||
client = yield driver.client();
|
||||
is(client.distribution, "funnelcake", "distribution is read from preferences");
|
||||
}));
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
<!ENTITY search.accesskey "S">
|
||||
<!ENTITY removeSelected.label "Remove Selected">
|
||||
<!ENTITY removeSelected.accesskey "r">
|
||||
<!ENTITY removeAll.label "Remove All">
|
||||
<!ENTITY removeAll.accesskey "e">
|
||||
<!ENTITY save.label "Save Changes">
|
||||
<!ENTITY save.accesskey "a">
|
||||
<!ENTITY cancel.label "Cancel">
|
||||
|
|
|
@ -112,6 +112,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Override tab close icon (to disable inversion) for better contrast with
|
||||
light theme on Windows 7 Classic theme. */
|
||||
@media not all and (min-resolution: 1.1dppx) {
|
||||
#TabsToolbar[brighttext] .tab-close-button:-moz-lwtheme-darktext:not([selected="true"]) {
|
||||
list-style-image: url("chrome://global/skin/icons/close.png");
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
#TabsToolbar[brighttext] .tab-close-button:-moz-lwtheme-darktext:not([selected="true"]) {
|
||||
list-style-image: url("chrome://global/skin/icons/close@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
@media (-moz-os-version: windows-win7),
|
||||
(-moz-os-version: windows-win8) {
|
||||
:root {
|
||||
|
|
|
@ -31,8 +31,9 @@ export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config
|
|||
export LDFLAGS="-Wl,-syslibroot,$CROSS_SYSROOT -Wl,-dead_strip"
|
||||
export TOOLCHAIN_PREFIX=$CROSS_CCTOOLS_PATH/bin/x86_64-apple-darwin10-
|
||||
export DSYMUTIL=$topsrcdir/clang/bin/llvm-dsymutil
|
||||
export GENISOIMAGE=$topsrcdir/genisoimage/genisoimage
|
||||
export MKFSHFS=$topsrcdir/hfsplus-tools/newfs_hfs
|
||||
export DMG_TOOL=$topsrcdir/dmg/dmg
|
||||
export HFS_TOOL=$topsrcdir/dmg/hfsplus
|
||||
|
||||
export HOST_CC="$topsrcdir/clang/bin/clang"
|
||||
export HOST_CXX="$topsrcdir/clang/bin/clang++"
|
||||
|
|
|
@ -763,6 +763,8 @@ def compiler(language, host_or_target, c_compiler=None, other_compiler=None,
|
|||
# Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow
|
||||
# old-configure to do some of its still existing checks.
|
||||
if language == 'C':
|
||||
set_config(
|
||||
'%s_TYPE' % var, delayed_getattr(valid_compiler, 'type'))
|
||||
add_old_configure_assignment(
|
||||
'%s_TYPE' % var, delayed_getattr(valid_compiler, 'type'))
|
||||
add_old_configure_assignment(
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -x
|
||||
|
||||
hfplus_version=540.1.linux3
|
||||
md5sum=0435afc389b919027b69616ad1b05709
|
||||
filename=diskdev_cmds-${hfplus_version}.tar.gz
|
||||
make_flags="-j$(getconf _NPROCESSORS_ONLN)"
|
||||
|
||||
root_dir="$1"
|
||||
if [ -z "$root_dir" -o ! -d "$root_dir" ]; then
|
||||
root_dir=$(mktemp -d)
|
||||
fi
|
||||
cd $root_dir
|
||||
|
||||
if test -z $TMPDIR; then
|
||||
TMPDIR=/tmp/
|
||||
fi
|
||||
|
||||
# Install clang first
|
||||
yum install -y clang
|
||||
|
||||
# Set an md5 check file to validate input
|
||||
echo "${md5sum} *${TMPDIR}/${filename}" > $TMPDIR/hfsplus.MD5
|
||||
|
||||
# Most-upstream is https://opensource.apple.com/source/diskdev_cmds/
|
||||
|
||||
# Download the source of the specified version of binutils
|
||||
wget -c -P $TMPDIR http://pkgs.fedoraproject.org/repo/pkgs/hfsplus-tools/${filename}/${md5sum}/${filename} || exit 1
|
||||
md5sum -c $TMPDIR/hfsplus.MD5 || exit 1
|
||||
mkdir hfsplus-source
|
||||
tar xzf $TMPDIR/${filename} -C hfsplus-source --strip-components=1
|
||||
|
||||
# Build
|
||||
cd hfsplus-source
|
||||
make $make_flags || exit 1
|
||||
cd ..
|
||||
|
||||
mkdir hfsplus-tools
|
||||
cp hfsplus-source/newfs_hfs.tproj/newfs_hfs hfsplus-tools/newfs_hfs
|
||||
## XXX fsck_hfs is unused, but is small and built from the package.
|
||||
cp hfsplus-source/fsck_hfs.tproj/fsck_hfs hfsplus-tools/fsck_hfs
|
||||
|
||||
# Make a package of the built utils
|
||||
cd $root_dir
|
||||
tar caf $root_dir/hfsplus-tools.tar.xz hfsplus-tools
|
|
@ -85,6 +85,9 @@ else:
|
|||
elif CONFIG['FFI_TARGET'] == 'X86_64':
|
||||
ffi_srcs = ('ffi64.c', 'unix64.S', 'ffi.c', 'sysv.S')
|
||||
elif CONFIG['FFI_TARGET'] == 'X86_WIN32':
|
||||
# MinGW Build for 32 bit
|
||||
if CONFIG['CC_TYPE'] == 'gcc':
|
||||
DEFINES['SYMBOL_UNDERSCORE'] = True
|
||||
ffi_srcs = ('ffi.c', 'win32.S')
|
||||
elif CONFIG['FFI_TARGET'] == 'X86_WIN64':
|
||||
ffi_srcs = ('ffi.c', 'win64.S')
|
||||
|
|
|
@ -958,6 +958,7 @@ CARGO_BUILD = env $(rustflags_override) \
|
|||
MOZ_DIST=$(ABS_DIST) \
|
||||
LIBCLANG_PATH=$(MOZ_LIBCLANG_PATH) \
|
||||
CLANG_PATH=$(MOZ_CLANG_PATH) \
|
||||
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||
$(CARGO) build $(cargo_build_flags)
|
||||
|
||||
ifdef RUST_LIBRARY_FILE
|
||||
|
|
|
@ -79,12 +79,20 @@ var submit = Task.async(function* () {
|
|||
var onConnectionReady = Task.async(function* ([aType, aTraits]) {
|
||||
clearTimeout(gConnectionTimeout);
|
||||
|
||||
let response = yield gClient.listAddons();
|
||||
let addons = [];
|
||||
try {
|
||||
let response = yield gClient.listAddons();
|
||||
if (!response.error && response.addons.length > 0) {
|
||||
addons = response.addons;
|
||||
}
|
||||
} catch(e) {
|
||||
// listAddons throws if the runtime doesn't support addons
|
||||
}
|
||||
|
||||
let parent = document.getElementById("addonActors");
|
||||
if (!response.error && response.addons.length > 0) {
|
||||
if (addons.length > 0) {
|
||||
// Add one entry for each add-on.
|
||||
for (let addon of response.addons) {
|
||||
for (let addon of addons) {
|
||||
if (!addon.debuggable) {
|
||||
continue;
|
||||
}
|
||||
|
@ -97,7 +105,7 @@ var onConnectionReady = Task.async(function* ([aType, aTraits]) {
|
|||
parent.remove();
|
||||
}
|
||||
|
||||
response = yield gClient.listTabs();
|
||||
let response = yield gClient.listTabs();
|
||||
|
||||
parent = document.getElementById("tabActors");
|
||||
|
||||
|
|
|
@ -34,8 +34,6 @@ const { BrowserLoader } =
|
|||
const {LocalizationHelper} = require("devtools/shared/l10n");
|
||||
const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
|
||||
|
||||
loader.lazyRequireGetter(this, "CommandUtils",
|
||||
"devtools/client/shared/developer-toolbar", true);
|
||||
loader.lazyRequireGetter(this, "getHighlighterUtils",
|
||||
"devtools/client/framework/toolbox-highlighter-utils", true);
|
||||
loader.lazyRequireGetter(this, "Selection",
|
||||
|
@ -1204,15 +1202,6 @@ Toolbox.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the toolbar spec for toolbox
|
||||
*/
|
||||
getToolbarSpec: function () {
|
||||
let spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
|
||||
|
||||
return spec;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return all toolbox buttons (command buttons, plus any others that were
|
||||
* added manually).
|
||||
|
|
|
@ -82,3 +82,55 @@ responsive.devicePixelRatio=Device Pixel Ratio
|
|||
# The argument (%1$S) is the selected device (e.g. iPhone 6) that set
|
||||
# automatically the DPR value.
|
||||
responsive.autoDPR=DPR automatically set by %1$S
|
||||
|
||||
# LOCALIZATION NOTE (responsive.customDeviceName): Default value in a form to
|
||||
# add a custom device based on an arbitrary size (no association to an existing
|
||||
# device).
|
||||
responsive.customDeviceName=Custom Device
|
||||
|
||||
# LOCALIZATION NOTE (responsive.customDeviceNameFromBase): Default value in a
|
||||
# form to add a custom device based on the properties of another. %1$S is the
|
||||
# name of the device we're staring from, such as "Apple iPhone 6".
|
||||
responsive.customDeviceNameFromBase=%1$S (Custom)
|
||||
|
||||
# LOCALIZATION NOTE (responsive.addDevice): Button text that reveals a form to
|
||||
# be used for adding custom devices.
|
||||
responsive.addDevice=Add Device
|
||||
|
||||
# LOCALIZATION NOTE (responsive.deviceAdderName): Label of form field for the
|
||||
# name of a new device. The available width is very low, so you might see
|
||||
# overlapping text if the length is much longer than 5 or so characters.
|
||||
responsive.deviceAdderName=Name
|
||||
|
||||
# LOCALIZATION NOTE (responsive.deviceAdderSize): Label of form field for the
|
||||
# size of a new device. The available width is very low, so you might see
|
||||
# overlapping text if the length is much longer than 5 or so characters.
|
||||
responsive.deviceAdderSize=Size
|
||||
|
||||
# LOCALIZATION NOTE (responsive.deviceAdderPixelRatio): Label of form field for
|
||||
# the devicePixelRatio of a new device. The available width is very low, so you
|
||||
# might see overlapping text if the length is much longer than 5 or so
|
||||
# characters.
|
||||
responsive.deviceAdderPixelRatio=DPR
|
||||
|
||||
# LOCALIZATION NOTE (responsive.deviceAdderUserAgent): Label of form field for
|
||||
# the user agent of a new device. The available width is very low, so you might
|
||||
# see overlapping text if the length is much longer than 5 or so characters.
|
||||
responsive.deviceAdderUserAgent=UA
|
||||
|
||||
# LOCALIZATION NOTE (responsive.deviceAdderTouch): Label of form field for the
|
||||
# touch input support of a new device. The available width is very low, so you
|
||||
# might see overlapping text if the length is much longer than 5 or so
|
||||
# characters.
|
||||
responsive.deviceAdderTouch=Touch
|
||||
|
||||
# LOCALIZATION NOTE (responsive.deviceAdderSave): Button text that submits a
|
||||
# form to add a new device.
|
||||
responsive.deviceAdderSave=Save
|
||||
|
||||
# LOCALIZATION NOTE (responsive.deviceDetails): Tooltip that appears when
|
||||
# hovering on a device in the device modal. %1$S is the width of the device.
|
||||
# %2$S is the height of the device. %3$S is the devicePixelRatio value of the
|
||||
# device. %4$S is the user agent of the device. %5$S is a boolean value
|
||||
# noting whether touch input is supported.
|
||||
responsive.deviceDetails=Size: %1$S x %2$S\nDPR: %3$S\nUA: %4$S\nTouch: %5$S
|
||||
|
|
|
@ -10,11 +10,13 @@ const {
|
|||
LOAD_DEVICE_LIST_START,
|
||||
LOAD_DEVICE_LIST_ERROR,
|
||||
LOAD_DEVICE_LIST_END,
|
||||
REMOVE_DEVICE,
|
||||
UPDATE_DEVICE_DISPLAYED,
|
||||
UPDATE_DEVICE_MODAL_OPEN,
|
||||
UPDATE_DEVICE_MODAL,
|
||||
} = require("./index");
|
||||
const { removeDeviceAssociation } = require("./viewports");
|
||||
|
||||
const { getDevices } = require("devtools/client/shared/devices");
|
||||
const { addDevice, getDevices, removeDevice } = require("devtools/client/shared/devices");
|
||||
|
||||
const Services = require("Services");
|
||||
const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";
|
||||
|
@ -71,6 +73,18 @@ module.exports = {
|
|||
|
||||
updatePreferredDevices: updatePreferredDevices,
|
||||
|
||||
addCustomDevice(device) {
|
||||
return function* (dispatch) {
|
||||
// Add custom device to device storage
|
||||
yield addDevice(device, "custom");
|
||||
dispatch({
|
||||
type: ADD_DEVICE,
|
||||
device,
|
||||
deviceType: "custom",
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
addDevice(device, deviceType) {
|
||||
return {
|
||||
type: ADD_DEVICE,
|
||||
|
@ -86,6 +100,26 @@ module.exports = {
|
|||
};
|
||||
},
|
||||
|
||||
removeCustomDevice(device) {
|
||||
return function* (dispatch, getState) {
|
||||
// Check if the custom device is currently associated with any viewports
|
||||
let { viewports } = getState();
|
||||
for (let viewport of viewports) {
|
||||
if (viewport.device == device.name) {
|
||||
dispatch(removeDeviceAssociation(viewport.id));
|
||||
}
|
||||
}
|
||||
|
||||
// Remove custom device from device storage
|
||||
yield removeDevice(device, "custom");
|
||||
dispatch({
|
||||
type: REMOVE_DEVICE,
|
||||
device,
|
||||
deviceType: "custom",
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
updateDeviceDisplayed(device, deviceType, displayed) {
|
||||
return {
|
||||
type: UPDATE_DEVICE_DISPLAYED,
|
||||
|
@ -96,8 +130,8 @@ module.exports = {
|
|||
},
|
||||
|
||||
loadDevices() {
|
||||
return function* (dispatch, getState) {
|
||||
yield dispatch({ type: LOAD_DEVICE_LIST_START });
|
||||
return function* (dispatch) {
|
||||
dispatch({ type: LOAD_DEVICE_LIST_START });
|
||||
let preferredDevices = loadPreferredDevices();
|
||||
let devices;
|
||||
|
||||
|
@ -124,14 +158,21 @@ module.exports = {
|
|||
dispatch(module.exports.addDevice(newDevice, type));
|
||||
}
|
||||
}
|
||||
|
||||
// Add an empty "custom" type if it doesn't exist in device storage
|
||||
if (!devices.TYPES.find(type => type == "custom")) {
|
||||
dispatch(module.exports.addDeviceType("custom"));
|
||||
}
|
||||
|
||||
dispatch({ type: LOAD_DEVICE_LIST_END });
|
||||
};
|
||||
},
|
||||
|
||||
updateDeviceModalOpen(isOpen) {
|
||||
updateDeviceModal(isOpen, modalOpenedFromViewport = null) {
|
||||
return {
|
||||
type: UPDATE_DEVICE_MODAL_OPEN,
|
||||
type: UPDATE_DEVICE_MODAL,
|
||||
isOpen,
|
||||
modalOpenedFromViewport,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -53,9 +53,12 @@ createEnum([
|
|||
// Indicates that the device list has been loaded successfully
|
||||
"LOAD_DEVICE_LIST_END",
|
||||
|
||||
// Remove the viewport's device assocation.
|
||||
// Remove a device.
|
||||
"REMOVE_DEVICE",
|
||||
|
||||
// Remove the viewport's device assocation.
|
||||
"REMOVE_DEVICE_ASSOCIATION",
|
||||
|
||||
// Resize the viewport.
|
||||
"RESIZE_VIEWPORT",
|
||||
|
||||
|
@ -71,7 +74,7 @@ createEnum([
|
|||
// Update the device display state in the device selector.
|
||||
"UPDATE_DEVICE_DISPLAYED",
|
||||
|
||||
// Update the device modal open state.
|
||||
"UPDATE_DEVICE_MODAL_OPEN",
|
||||
// Update the device modal state.
|
||||
"UPDATE_DEVICE_MODAL",
|
||||
|
||||
], module.exports);
|
||||
|
|
|
@ -8,7 +8,7 @@ const {
|
|||
ADD_VIEWPORT,
|
||||
CHANGE_DEVICE,
|
||||
CHANGE_PIXEL_RATIO,
|
||||
REMOVE_DEVICE,
|
||||
REMOVE_DEVICE_ASSOCIATION,
|
||||
RESIZE_VIEWPORT,
|
||||
ROTATE_VIEWPORT
|
||||
} = require("./index");
|
||||
|
@ -27,11 +27,12 @@ module.exports = {
|
|||
/**
|
||||
* Change the viewport device.
|
||||
*/
|
||||
changeDevice(id, device) {
|
||||
changeDevice(id, device, deviceType) {
|
||||
return {
|
||||
type: CHANGE_DEVICE,
|
||||
id,
|
||||
device,
|
||||
deviceType,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -49,9 +50,9 @@ module.exports = {
|
|||
/**
|
||||
* Remove the viewport's device assocation.
|
||||
*/
|
||||
removeDevice(id) {
|
||||
removeDeviceAssociation(id) {
|
||||
return {
|
||||
type: REMOVE_DEVICE,
|
||||
type: REMOVE_DEVICE_ASSOCIATION,
|
||||
id,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -11,8 +11,10 @@ const { createClass, createFactory, PropTypes, DOM: dom } =
|
|||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const {
|
||||
addCustomDevice,
|
||||
removeCustomDevice,
|
||||
updateDeviceDisplayed,
|
||||
updateDeviceModalOpen,
|
||||
updateDeviceModal,
|
||||
updatePreferredDevices,
|
||||
} = require("./actions/devices");
|
||||
const { changeNetworkThrottling } = require("./actions/network-throttling");
|
||||
|
@ -21,7 +23,7 @@ const { changeTouchSimulation } = require("./actions/touch-simulation");
|
|||
const {
|
||||
changeDevice,
|
||||
changePixelRatio,
|
||||
removeDevice,
|
||||
removeDeviceAssociation,
|
||||
resizeViewport,
|
||||
rotateViewport,
|
||||
} = require("./actions/viewports");
|
||||
|
@ -44,16 +46,20 @@ let App = createClass({
|
|||
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
|
||||
},
|
||||
|
||||
onAddCustomDevice(device) {
|
||||
this.props.dispatch(addCustomDevice(device));
|
||||
},
|
||||
|
||||
onBrowserMounted() {
|
||||
window.postMessage({ type: "browser-mounted" }, "*");
|
||||
},
|
||||
|
||||
onChangeDevice(id, device) {
|
||||
onChangeDevice(id, device, deviceType) {
|
||||
window.postMessage({
|
||||
type: "change-device",
|
||||
device,
|
||||
}, "*");
|
||||
this.props.dispatch(changeDevice(id, device.name));
|
||||
this.props.dispatch(changeDevice(id, device.name, deviceType));
|
||||
this.props.dispatch(changeTouchSimulation(device.touch));
|
||||
this.props.dispatch(changePixelRatio(id, device.pixelRatio));
|
||||
},
|
||||
|
@ -99,12 +105,16 @@ let App = createClass({
|
|||
window.postMessage({ type: "exit" }, "*");
|
||||
},
|
||||
|
||||
onRemoveDevice(id) {
|
||||
onRemoveCustomDevice(device) {
|
||||
this.props.dispatch(removeCustomDevice(device));
|
||||
},
|
||||
|
||||
onRemoveDeviceAssociation(id) {
|
||||
// TODO: Bug 1332754: Move messaging and logic into the action creator.
|
||||
window.postMessage({
|
||||
type: "remove-device",
|
||||
type: "remove-device-association",
|
||||
}, "*");
|
||||
this.props.dispatch(removeDevice(id));
|
||||
this.props.dispatch(removeDeviceAssociation(id));
|
||||
this.props.dispatch(changeTouchSimulation(false));
|
||||
this.props.dispatch(changePixelRatio(id, 0));
|
||||
},
|
||||
|
@ -125,8 +135,8 @@ let App = createClass({
|
|||
this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed));
|
||||
},
|
||||
|
||||
onUpdateDeviceModalOpen(isOpen) {
|
||||
this.props.dispatch(updateDeviceModalOpen(isOpen));
|
||||
onUpdateDeviceModal(isOpen, modalOpenedFromViewport) {
|
||||
this.props.dispatch(updateDeviceModal(isOpen, modalOpenedFromViewport));
|
||||
},
|
||||
|
||||
render() {
|
||||
|
@ -141,6 +151,7 @@ let App = createClass({
|
|||
} = this.props;
|
||||
|
||||
let {
|
||||
onAddCustomDevice,
|
||||
onBrowserMounted,
|
||||
onChangeDevice,
|
||||
onChangeNetworkThrottling,
|
||||
|
@ -149,12 +160,13 @@ let App = createClass({
|
|||
onContentResize,
|
||||
onDeviceListUpdate,
|
||||
onExit,
|
||||
onRemoveDevice,
|
||||
onRemoveCustomDevice,
|
||||
onRemoveDeviceAssociation,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onScreenshot,
|
||||
onUpdateDeviceDisplayed,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
} = this;
|
||||
|
||||
let selectedDevice = "";
|
||||
|
@ -165,6 +177,11 @@ let App = createClass({
|
|||
selectedPixelRatio = viewports[0].pixelRatio;
|
||||
}
|
||||
|
||||
let deviceAdderViewportTemplate = {};
|
||||
if (devices.modalOpenedFromViewport !== null) {
|
||||
deviceAdderViewportTemplate = viewports[devices.modalOpenedFromViewport];
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
id: "app",
|
||||
|
@ -191,16 +208,19 @@ let App = createClass({
|
|||
onBrowserMounted,
|
||||
onChangeDevice,
|
||||
onContentResize,
|
||||
onRemoveDevice,
|
||||
onRemoveDeviceAssociation,
|
||||
onRotateViewport,
|
||||
onResizeViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
}),
|
||||
DeviceModal({
|
||||
deviceAdderViewportTemplate,
|
||||
devices,
|
||||
onAddCustomDevice,
|
||||
onDeviceListUpdate,
|
||||
onRemoveCustomDevice,
|
||||
onUpdateDeviceDisplayed,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
})
|
||||
);
|
||||
},
|
||||
|
|
|
@ -0,0 +1,252 @@
|
|||
/* 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/. */
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { DOM: dom, createClass, createFactory, PropTypes, addons } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
const { getFormatStr, getStr } = require("../utils/l10n");
|
||||
const Types = require("../types");
|
||||
const ViewportDimension = createFactory(require("./viewport-dimension"));
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "DeviceAdder",
|
||||
|
||||
propTypes: {
|
||||
devices: PropTypes.shape(Types.devices).isRequired,
|
||||
viewportTemplate: PropTypes.shape(Types.viewport).isRequired,
|
||||
onAddCustomDevice: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
getInitialState() {
|
||||
return {};
|
||||
},
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
let {
|
||||
width,
|
||||
height,
|
||||
} = nextProps.viewportTemplate;
|
||||
|
||||
this.setState({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
},
|
||||
|
||||
onChangeSize(width, height) {
|
||||
this.setState({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
},
|
||||
|
||||
onDeviceAdderShow() {
|
||||
this.setState({
|
||||
deviceAdderDisplayed: true,
|
||||
});
|
||||
},
|
||||
|
||||
onDeviceAdderSave() {
|
||||
let {
|
||||
devices,
|
||||
onAddCustomDevice,
|
||||
} = this.props;
|
||||
|
||||
if (!this.pixelRatioInput.checkValidity()) {
|
||||
return;
|
||||
}
|
||||
if (devices.custom.find(device => device.name == this.nameInput.value)) {
|
||||
this.nameInput.setCustomValidity("Device name already in use");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
deviceAdderDisplayed: false,
|
||||
});
|
||||
onAddCustomDevice({
|
||||
name: this.nameInput.value,
|
||||
width: this.state.width,
|
||||
height: this.state.height,
|
||||
pixelRatio: parseFloat(this.pixelRatioInput.value),
|
||||
userAgent: this.userAgentInput.value,
|
||||
touch: this.touchInput.checked,
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
let {
|
||||
devices,
|
||||
viewportTemplate,
|
||||
} = this.props;
|
||||
|
||||
let {
|
||||
deviceAdderDisplayed,
|
||||
height,
|
||||
width,
|
||||
} = this.state;
|
||||
|
||||
if (!deviceAdderDisplayed) {
|
||||
return dom.div(
|
||||
{
|
||||
id: "device-adder"
|
||||
},
|
||||
dom.button(
|
||||
{
|
||||
id: "device-adder-show",
|
||||
onClick: this.onDeviceAdderShow,
|
||||
},
|
||||
getStr("responsive.addDevice")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// If a device is currently selected, fold its attributes into a single object for use
|
||||
// as the starting values of the form. If no device is selected, use the values for
|
||||
// the current window.
|
||||
let deviceName;
|
||||
let normalizedViewport = Object.assign({}, viewportTemplate);
|
||||
if (viewportTemplate.device) {
|
||||
let device = devices[viewportTemplate.deviceType].find(d => {
|
||||
return d.name == viewportTemplate.device;
|
||||
});
|
||||
deviceName = getFormatStr("responsive.customDeviceNameFromBase", device.name);
|
||||
Object.assign(normalizedViewport, {
|
||||
pixelRatio: device.pixelRatio,
|
||||
userAgent: device.userAgent,
|
||||
touch: device.touch,
|
||||
});
|
||||
} else {
|
||||
deviceName = getStr("responsive.customDeviceName");
|
||||
Object.assign(normalizedViewport, {
|
||||
pixelRatio: window.devicePixelRatio,
|
||||
userAgent: navigator.userAgent,
|
||||
touch: false,
|
||||
});
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
id: "device-adder"
|
||||
},
|
||||
dom.div(
|
||||
{
|
||||
id: "device-adder-content",
|
||||
},
|
||||
dom.div(
|
||||
{
|
||||
id: "device-adder-column-1",
|
||||
},
|
||||
dom.label(
|
||||
{
|
||||
id: "device-adder-name",
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
className: "device-adder-label",
|
||||
},
|
||||
getStr("responsive.deviceAdderName")
|
||||
),
|
||||
dom.input({
|
||||
defaultValue: deviceName,
|
||||
ref: input => {
|
||||
this.nameInput = input;
|
||||
},
|
||||
})
|
||||
),
|
||||
dom.label(
|
||||
{
|
||||
id: "device-adder-size",
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
className: "device-adder-label"
|
||||
},
|
||||
getStr("responsive.deviceAdderSize")
|
||||
),
|
||||
ViewportDimension({
|
||||
viewport: {
|
||||
width,
|
||||
height,
|
||||
},
|
||||
onChangeSize: this.onChangeSize,
|
||||
onRemoveDeviceAssociation: () => {},
|
||||
})
|
||||
),
|
||||
dom.label(
|
||||
{
|
||||
id: "device-adder-pixel-ratio",
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
className: "device-adder-label"
|
||||
},
|
||||
getStr("responsive.deviceAdderPixelRatio")
|
||||
),
|
||||
dom.input({
|
||||
type: "number",
|
||||
step: "any",
|
||||
defaultValue: normalizedViewport.pixelRatio,
|
||||
ref: input => {
|
||||
this.pixelRatioInput = input;
|
||||
},
|
||||
})
|
||||
)
|
||||
),
|
||||
dom.div(
|
||||
{
|
||||
id: "device-adder-column-2",
|
||||
},
|
||||
dom.label(
|
||||
{
|
||||
id: "device-adder-user-agent",
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
className: "device-adder-label"
|
||||
},
|
||||
getStr("responsive.deviceAdderUserAgent")
|
||||
),
|
||||
dom.input({
|
||||
defaultValue: normalizedViewport.userAgent,
|
||||
ref: input => {
|
||||
this.userAgentInput = input;
|
||||
},
|
||||
})
|
||||
),
|
||||
dom.label(
|
||||
{
|
||||
id: "device-adder-touch",
|
||||
},
|
||||
dom.span(
|
||||
{
|
||||
className: "device-adder-label"
|
||||
},
|
||||
getStr("responsive.deviceAdderTouch")
|
||||
),
|
||||
dom.input({
|
||||
defaultChecked: normalizedViewport.touch,
|
||||
type: "checkbox",
|
||||
ref: input => {
|
||||
this.touchInput = input;
|
||||
},
|
||||
})
|
||||
)
|
||||
),
|
||||
),
|
||||
dom.button(
|
||||
{
|
||||
id: "device-adder-save",
|
||||
onClick: this.onDeviceAdderSave,
|
||||
},
|
||||
getStr("responsive.deviceAdderSave")
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
|
@ -6,19 +6,24 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { DOM: dom, createClass, PropTypes, addons } =
|
||||
const { DOM: dom, createClass, createFactory, PropTypes, addons } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const { getStr } = require("../utils/l10n");
|
||||
|
||||
const { getStr, getFormatStr } = require("../utils/l10n");
|
||||
const Types = require("../types");
|
||||
const DeviceAdder = createFactory(require("./device-adder"));
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "DeviceModal",
|
||||
|
||||
propTypes: {
|
||||
deviceAdderViewportTemplate: PropTypes.shape(Types.viewport).isRequired,
|
||||
devices: PropTypes.shape(Types.devices).isRequired,
|
||||
onAddCustomDevice: PropTypes.func.isRequired,
|
||||
onDeviceListUpdate: PropTypes.func.isRequired,
|
||||
onRemoveCustomDevice: PropTypes.func.isRequired,
|
||||
onUpdateDeviceDisplayed: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModal: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
@ -63,7 +68,7 @@ module.exports = createClass({
|
|||
devices,
|
||||
onDeviceListUpdate,
|
||||
onUpdateDeviceDisplayed,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
} = this.props;
|
||||
|
||||
let preferredDevices = {
|
||||
|
@ -88,7 +93,7 @@ module.exports = createClass({
|
|||
}
|
||||
|
||||
onDeviceListUpdate(preferredDevices);
|
||||
onUpdateDeviceModalOpen(false);
|
||||
onUpdateDeviceModal(false);
|
||||
},
|
||||
|
||||
onKeyDown(event) {
|
||||
|
@ -98,16 +103,19 @@ module.exports = createClass({
|
|||
// Escape keycode
|
||||
if (event.keyCode === 27) {
|
||||
let {
|
||||
onUpdateDeviceModalOpen
|
||||
onUpdateDeviceModal
|
||||
} = this.props;
|
||||
onUpdateDeviceModalOpen(false);
|
||||
onUpdateDeviceModal(false);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
let {
|
||||
deviceAdderViewportTemplate,
|
||||
devices,
|
||||
onUpdateDeviceModalOpen,
|
||||
onAddCustomDevice,
|
||||
onRemoveCustomDevice,
|
||||
onUpdateDeviceModal,
|
||||
} = this.props;
|
||||
|
||||
const sortedDevices = {};
|
||||
|
@ -128,7 +136,7 @@ module.exports = createClass({
|
|||
dom.button({
|
||||
id: "device-close-button",
|
||||
className: "toolbar-button devtools-button",
|
||||
onClick: () => onUpdateDeviceModalOpen(false),
|
||||
onClick: () => onUpdateDeviceModal(false),
|
||||
}),
|
||||
dom.div(
|
||||
{
|
||||
|
@ -147,10 +155,24 @@ module.exports = createClass({
|
|||
type
|
||||
),
|
||||
sortedDevices[type].map(device => {
|
||||
let details = getFormatStr(
|
||||
"responsive.deviceDetails", device.width, device.height,
|
||||
device.pixelRatio, device.userAgent, device.touch
|
||||
);
|
||||
|
||||
let removeDeviceButton;
|
||||
if (type == "custom") {
|
||||
removeDeviceButton = dom.button({
|
||||
className: "device-remove-button toolbar-button devtools-button",
|
||||
onClick: () => onRemoveCustomDevice(device),
|
||||
});
|
||||
}
|
||||
|
||||
return dom.label(
|
||||
{
|
||||
className: "device-label",
|
||||
key: device.name,
|
||||
title: details,
|
||||
},
|
||||
dom.input({
|
||||
className: "device-input-checkbox",
|
||||
|
@ -159,12 +181,23 @@ module.exports = createClass({
|
|||
checked: this.state[device.name],
|
||||
onChange: this.onDeviceCheckboxChange,
|
||||
}),
|
||||
device.name
|
||||
dom.span(
|
||||
{
|
||||
className: "device-name",
|
||||
},
|
||||
device.name
|
||||
),
|
||||
removeDeviceButton
|
||||
);
|
||||
})
|
||||
);
|
||||
})
|
||||
),
|
||||
DeviceAdder({
|
||||
devices,
|
||||
viewportTemplate: deviceAdderViewportTemplate,
|
||||
onAddCustomDevice,
|
||||
}),
|
||||
dom.button(
|
||||
{
|
||||
id: "device-submit-button",
|
||||
|
@ -176,7 +209,7 @@ module.exports = createClass({
|
|||
dom.div(
|
||||
{
|
||||
className: "modal-overlay",
|
||||
onClick: () => onUpdateDeviceModalOpen(false),
|
||||
onClick: () => onUpdateDeviceModal(false),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
|
@ -17,9 +17,10 @@ module.exports = createClass({
|
|||
propTypes: {
|
||||
devices: PropTypes.shape(Types.devices).isRequired,
|
||||
selectedDevice: PropTypes.string.isRequired,
|
||||
viewportId: PropTypes.number.isRequired,
|
||||
onChangeDevice: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModal: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
@ -27,20 +28,21 @@ module.exports = createClass({
|
|||
onSelectChange({ target }) {
|
||||
let {
|
||||
devices,
|
||||
viewportId,
|
||||
onChangeDevice,
|
||||
onResizeViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
} = this.props;
|
||||
|
||||
if (target.value === OPEN_DEVICE_MODAL_VALUE) {
|
||||
onUpdateDeviceModalOpen(true);
|
||||
onUpdateDeviceModal(true, viewportId);
|
||||
return;
|
||||
}
|
||||
for (let type of devices.types) {
|
||||
for (let device of devices[type]) {
|
||||
if (device.name === target.value) {
|
||||
onResizeViewport(device.width, device.height);
|
||||
onChangeDevice(device);
|
||||
onChangeDevice(device, type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
DevToolsModules(
|
||||
'browser.js',
|
||||
'device-adder.js',
|
||||
'device-modal.js',
|
||||
'device-selector.js',
|
||||
'dpr-selector.js',
|
||||
|
|
|
@ -30,10 +30,10 @@ module.exports = createClass({
|
|||
onBrowserMounted: PropTypes.func.isRequired,
|
||||
onChangeDevice: PropTypes.func.isRequired,
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
onRemoveDevice: PropTypes.func.isRequired,
|
||||
onRemoveDeviceAssociation: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModal: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
|
@ -114,7 +114,7 @@ module.exports = createClass({
|
|||
// the properties of the device on resize. However, at the moment, there is no
|
||||
// way to edit dPR when a device is selected, and there is no UI at all for editing
|
||||
// UA, so it's important to keep doing this for now.
|
||||
this.props.onRemoveDevice();
|
||||
this.props.onRemoveDeviceAssociation();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
|
@ -135,7 +135,7 @@ module.exports = createClass({
|
|||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
} = this.props;
|
||||
|
||||
let resizeHandleClass = "viewport-resize-handle";
|
||||
|
@ -154,11 +154,11 @@ module.exports = createClass({
|
|||
},
|
||||
ViewportToolbar({
|
||||
devices,
|
||||
selectedDevice: viewport.device,
|
||||
viewport,
|
||||
onChangeDevice,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
}),
|
||||
dom.div(
|
||||
{
|
||||
|
|
|
@ -15,8 +15,8 @@ module.exports = createClass({
|
|||
|
||||
propTypes: {
|
||||
viewport: PropTypes.shape(Types.viewport).isRequired,
|
||||
onRemoveDevice: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onChangeSize: PropTypes.func.isRequired,
|
||||
onRemoveDeviceAssociation: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
|
@ -116,10 +116,10 @@ module.exports = createClass({
|
|||
// Change the device selector back to an unselected device
|
||||
// TODO: Bug 1332754: Logic like this probably belongs in the action creator.
|
||||
if (this.props.viewport.device) {
|
||||
this.props.onRemoveDevice();
|
||||
this.props.onRemoveDeviceAssociation();
|
||||
}
|
||||
this.props.onResizeViewport(parseInt(this.state.width, 10),
|
||||
parseInt(this.state.height, 10));
|
||||
this.props.onChangeSize(parseInt(this.state.width, 10),
|
||||
parseInt(this.state.height, 10));
|
||||
},
|
||||
|
||||
render() {
|
||||
|
|
|
@ -16,11 +16,11 @@ module.exports = createClass({
|
|||
|
||||
propTypes: {
|
||||
devices: PropTypes.shape(Types.devices).isRequired,
|
||||
selectedDevice: PropTypes.string.isRequired,
|
||||
viewport: PropTypes.shape(Types.viewport).isRequired,
|
||||
onChangeDevice: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModal: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
@ -28,11 +28,11 @@ module.exports = createClass({
|
|||
render() {
|
||||
let {
|
||||
devices,
|
||||
selectedDevice,
|
||||
viewport,
|
||||
onChangeDevice,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
} = this.props;
|
||||
|
||||
return dom.div(
|
||||
|
@ -41,10 +41,11 @@ module.exports = createClass({
|
|||
},
|
||||
DeviceSelector({
|
||||
devices,
|
||||
selectedDevice,
|
||||
selectedDevice: viewport.device,
|
||||
viewportId: viewport.id,
|
||||
onChangeDevice,
|
||||
onResizeViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
}),
|
||||
dom.button({
|
||||
className: "viewport-rotate-button toolbar-button devtools-button",
|
||||
|
|
|
@ -24,28 +24,28 @@ module.exports = createClass({
|
|||
onBrowserMounted: PropTypes.func.isRequired,
|
||||
onChangeDevice: PropTypes.func.isRequired,
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
onRemoveDevice: PropTypes.func.isRequired,
|
||||
onRemoveDeviceAssociation: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModal: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
onChangeDevice(device) {
|
||||
onChangeDevice(device, deviceType) {
|
||||
let {
|
||||
viewport,
|
||||
onChangeDevice,
|
||||
} = this.props;
|
||||
|
||||
onChangeDevice(viewport.id, device);
|
||||
onChangeDevice(viewport.id, device, deviceType);
|
||||
},
|
||||
|
||||
onRemoveDevice() {
|
||||
onRemoveDeviceAssociation() {
|
||||
let {
|
||||
viewport,
|
||||
onRemoveDevice,
|
||||
onRemoveDeviceAssociation,
|
||||
} = this.props;
|
||||
|
||||
onRemoveDevice(viewport.id);
|
||||
onRemoveDeviceAssociation(viewport.id);
|
||||
},
|
||||
|
||||
onResizeViewport(width, height) {
|
||||
|
@ -75,12 +75,12 @@ module.exports = createClass({
|
|||
viewport,
|
||||
onBrowserMounted,
|
||||
onContentResize,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
} = this.props;
|
||||
|
||||
let {
|
||||
onChangeDevice,
|
||||
onRemoveDevice,
|
||||
onRemoveDeviceAssociation,
|
||||
onRotateViewport,
|
||||
onResizeViewport,
|
||||
} = this;
|
||||
|
@ -91,8 +91,8 @@ module.exports = createClass({
|
|||
},
|
||||
ViewportDimension({
|
||||
viewport,
|
||||
onRemoveDevice,
|
||||
onResizeViewport,
|
||||
onChangeSize: onResizeViewport,
|
||||
onRemoveDeviceAssociation,
|
||||
}),
|
||||
ResizableViewport({
|
||||
devices,
|
||||
|
@ -103,10 +103,10 @@ module.exports = createClass({
|
|||
onBrowserMounted,
|
||||
onChangeDevice,
|
||||
onContentResize,
|
||||
onRemoveDevice,
|
||||
onRemoveDeviceAssociation,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
})
|
||||
);
|
||||
},
|
||||
|
|
|
@ -22,10 +22,10 @@ module.exports = createClass({
|
|||
onBrowserMounted: PropTypes.func.isRequired,
|
||||
onChangeDevice: PropTypes.func.isRequired,
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
onRemoveDevice: PropTypes.func.isRequired,
|
||||
onRemoveDeviceAssociation: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
|
||||
onUpdateDeviceModal: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
render() {
|
||||
|
@ -37,10 +37,10 @@ module.exports = createClass({
|
|||
onBrowserMounted,
|
||||
onChangeDevice,
|
||||
onContentResize,
|
||||
onRemoveDevice,
|
||||
onRemoveDeviceAssociation,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
} = this.props;
|
||||
|
||||
return dom.div(
|
||||
|
@ -58,10 +58,10 @@ module.exports = createClass({
|
|||
onBrowserMounted,
|
||||
onChangeDevice,
|
||||
onContentResize,
|
||||
onRemoveDevice,
|
||||
onRemoveDeviceAssociation,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
onUpdateDeviceModalOpen,
|
||||
onUpdateDeviceModal,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
|
|
@ -38,7 +38,8 @@
|
|||
}
|
||||
|
||||
#root,
|
||||
html, body {
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -366,6 +367,7 @@ select > option.divider {
|
|||
.viewport-dimension-editable.editing,
|
||||
.viewport-dimension-input.editing {
|
||||
color: var(--viewport-active-color);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.viewport-dimension-editable.editing {
|
||||
|
@ -424,7 +426,7 @@ select > option.divider {
|
|||
left: 0;
|
||||
right: 0;
|
||||
width: 642px;
|
||||
height: 612px;
|
||||
height: 650px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
@ -457,9 +459,9 @@ select > option.divider {
|
|||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
overflow: auto;
|
||||
height: 550px;
|
||||
height: 515px;
|
||||
width: 600px;
|
||||
margin: 20px;
|
||||
margin: 20px 20px 0;
|
||||
}
|
||||
|
||||
#device-close-button,
|
||||
|
@ -494,12 +496,29 @@ select > option.divider {
|
|||
padding-bottom: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* Largest size without horizontal scrollbars */
|
||||
max-width: 181px;
|
||||
}
|
||||
|
||||
.device-input-checkbox {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.device-remove-button,
|
||||
.device-remove-button::before {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.device-remove-button::before {
|
||||
background-image: url("./images/close.svg");
|
||||
margin: -6px 0 0 -6px;
|
||||
}
|
||||
|
||||
#device-submit-button {
|
||||
background-color: var(--theme-tab-toolbar-background);
|
||||
border-width: 1px 0 0 0;
|
||||
|
@ -509,6 +528,8 @@ select > option.divider {
|
|||
color: var(--theme-body-color);
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#device-submit-button:hover {
|
||||
|
@ -519,3 +540,74 @@ select > option.divider {
|
|||
background-color: var(--submit-button-active-background-color);
|
||||
color: var(--submit-button-active-color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Device Adder
|
||||
*/
|
||||
|
||||
#device-adder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
#device-adder-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#device-adder-column-1 {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#device-adder-column-2 {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
#device-adder button {
|
||||
background-color: var(--theme-tab-toolbar-background);
|
||||
border: 1px solid var(--theme-splitter-color);
|
||||
border-radius: 2px;
|
||||
color: var(--theme-body-color);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#device-adder label {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#device-adder label > input,
|
||||
#device-adder label > .viewport-dimension {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#device-adder input {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
text-align: center;
|
||||
color: var(--theme-body-color-inactive);
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
#device-adder input:focus {
|
||||
color: var(--viewport-active-color);
|
||||
}
|
||||
|
||||
#device-adder label > input:focus,
|
||||
#device-adder label > .viewport-dimension:focus {
|
||||
border-bottom: 1px solid var(--theme-selection-background);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.device-adder-label {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
min-width: 35px;
|
||||
}
|
||||
|
||||
#device-adder #device-adder-save {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
|
|
@ -467,8 +467,8 @@ ResponsiveUI.prototype = {
|
|||
case "exit":
|
||||
this.onExit();
|
||||
break;
|
||||
case "remove-device":
|
||||
this.onRemoveDevice(event);
|
||||
case "remove-device-association":
|
||||
this.onRemoveDeviceAssociation(event);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
@ -512,7 +512,7 @@ ResponsiveUI.prototype = {
|
|||
ResponsiveUIManager.closeIfNeeded(browserWindow, tab);
|
||||
},
|
||||
|
||||
onRemoveDevice: Task.async(function* (event) {
|
||||
onRemoveDeviceAssociation: Task.async(function* (event) {
|
||||
yield this.updateUserAgent();
|
||||
yield this.updateDPPX();
|
||||
yield this.updateTouchSimulation();
|
||||
|
|
|
@ -10,8 +10,9 @@ const {
|
|||
LOAD_DEVICE_LIST_START,
|
||||
LOAD_DEVICE_LIST_ERROR,
|
||||
LOAD_DEVICE_LIST_END,
|
||||
REMOVE_DEVICE,
|
||||
UPDATE_DEVICE_DISPLAYED,
|
||||
UPDATE_DEVICE_MODAL_OPEN,
|
||||
UPDATE_DEVICE_MODAL,
|
||||
} = require("../actions/index");
|
||||
|
||||
const Types = require("../types");
|
||||
|
@ -19,6 +20,7 @@ const Types = require("../types");
|
|||
const INITIAL_DEVICES = {
|
||||
types: [],
|
||||
isModalOpen: false,
|
||||
modalOpenedFromViewport: null,
|
||||
listState: Types.deviceListState.INITIALIZED,
|
||||
};
|
||||
|
||||
|
@ -69,9 +71,23 @@ let reducers = {
|
|||
});
|
||||
},
|
||||
|
||||
[UPDATE_DEVICE_MODAL_OPEN](devices, { isOpen }) {
|
||||
[REMOVE_DEVICE](devices, { device, deviceType }) {
|
||||
let index = devices[deviceType].indexOf(device);
|
||||
if (index < 0) {
|
||||
return devices;
|
||||
}
|
||||
|
||||
let list = [...devices[deviceType]];
|
||||
list.splice(index, 1);
|
||||
return Object.assign({}, devices, {
|
||||
[deviceType]: list
|
||||
});
|
||||
},
|
||||
|
||||
[UPDATE_DEVICE_MODAL](devices, { isOpen, modalOpenedFromViewport }) {
|
||||
return Object.assign({}, devices, {
|
||||
isModalOpen: isOpen,
|
||||
modalOpenedFromViewport,
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ const {
|
|||
ADD_VIEWPORT,
|
||||
CHANGE_DEVICE,
|
||||
CHANGE_PIXEL_RATIO,
|
||||
REMOVE_DEVICE,
|
||||
REMOVE_DEVICE_ASSOCIATION,
|
||||
RESIZE_VIEWPORT,
|
||||
ROTATE_VIEWPORT,
|
||||
} = require("../actions/index");
|
||||
|
@ -19,6 +19,7 @@ const INITIAL_VIEWPORTS = [];
|
|||
const INITIAL_VIEWPORT = {
|
||||
id: nextViewportId++,
|
||||
device: "",
|
||||
deviceType: "",
|
||||
width: 320,
|
||||
height: 480,
|
||||
pixelRatio: {
|
||||
|
@ -36,7 +37,7 @@ let reducers = {
|
|||
return [...viewports, Object.assign({}, INITIAL_VIEWPORT)];
|
||||
},
|
||||
|
||||
[CHANGE_DEVICE](viewports, { id, device }) {
|
||||
[CHANGE_DEVICE](viewports, { id, device, deviceType }) {
|
||||
return viewports.map(viewport => {
|
||||
if (viewport.id !== id) {
|
||||
return viewport;
|
||||
|
@ -44,6 +45,7 @@ let reducers = {
|
|||
|
||||
return Object.assign({}, viewport, {
|
||||
device,
|
||||
deviceType,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -62,7 +64,7 @@ let reducers = {
|
|||
});
|
||||
},
|
||||
|
||||
[REMOVE_DEVICE](viewports, { id }) {
|
||||
[REMOVE_DEVICE_ASSOCIATION](viewports, { id }) {
|
||||
return viewports.map(viewport => {
|
||||
if (viewport.id !== id) {
|
||||
return viewport;
|
||||
|
@ -70,6 +72,7 @@ let reducers = {
|
|||
|
||||
return Object.assign({}, viewport, {
|
||||
device: "",
|
||||
deviceType: "",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -17,6 +17,7 @@ support-files =
|
|||
!/devtools/client/shared/test/test-actor-registry.js
|
||||
|
||||
[browser_device_change.js]
|
||||
[browser_device_custom.js]
|
||||
[browser_device_modal_error.js]
|
||||
[browser_device_modal_exit.js]
|
||||
[browser_device_modal_submit.js]
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test adding and removing custom devices via the modal.
|
||||
|
||||
const device = {
|
||||
name: "Test Device",
|
||||
width: 400,
|
||||
height: 570,
|
||||
pixelRatio: 1.5,
|
||||
userAgent: "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
|
||||
touch: true,
|
||||
firefoxOS: false,
|
||||
os: "android",
|
||||
};
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8,";
|
||||
const Types = require("devtools/client/responsive.html/types");
|
||||
|
||||
addRDMTask(TEST_URL, function* ({ ui }) {
|
||||
let { toolWindow } = ui;
|
||||
let { store, document } = toolWindow;
|
||||
let React = toolWindow.require("devtools/client/shared/vendor/react");
|
||||
let { Simulate } = React.addons.TestUtils;
|
||||
|
||||
// Wait until the viewport has been added and the device list has been loaded
|
||||
yield waitUntilState(store, state => state.viewports.length == 1
|
||||
&& state.devices.listState == Types.deviceListState.LOADED);
|
||||
|
||||
let deviceSelector = document.querySelector(".viewport-device-selector");
|
||||
let submitButton = document.querySelector("#device-submit-button");
|
||||
|
||||
openDeviceModal(ui);
|
||||
|
||||
info("Reveal device adder form, check that defaults match the viewport");
|
||||
let adderShow = document.querySelector("#device-adder-show");
|
||||
Simulate.click(adderShow);
|
||||
testDeviceAdder(ui, {
|
||||
name: "Custom Device",
|
||||
width: 320,
|
||||
height: 480,
|
||||
pixelRatio: window.devicePixelRatio,
|
||||
userAgent: navigator.userAgent,
|
||||
touch: false,
|
||||
});
|
||||
|
||||
info("Fill out device adder form and save");
|
||||
setDeviceAdder(ui, device);
|
||||
let adderSave = document.querySelector("#device-adder-save");
|
||||
let saved = waitUntilState(store, state => state.devices.custom.length == 1);
|
||||
Simulate.click(adderSave);
|
||||
yield saved;
|
||||
|
||||
info("Enable device in modal");
|
||||
let deviceCb = [...document.querySelectorAll(".device-input-checkbox")].find(cb => {
|
||||
return cb.value == device.name;
|
||||
});
|
||||
ok(deviceCb, "Custom device checkbox added to modal");
|
||||
deviceCb.click();
|
||||
Simulate.click(submitButton);
|
||||
|
||||
info("Look for custom device in device selector");
|
||||
let selectorOption = [...deviceSelector.options].find(opt => opt.value == device.name);
|
||||
ok(selectorOption, "Custom device option added to device selector");
|
||||
});
|
||||
|
||||
addRDMTask(TEST_URL, function* ({ ui }) {
|
||||
let { toolWindow } = ui;
|
||||
let { store, document } = toolWindow;
|
||||
let React = toolWindow.require("devtools/client/shared/vendor/react");
|
||||
let { Simulate } = React.addons.TestUtils;
|
||||
|
||||
// Wait until the viewport has been added and the device list has been loaded
|
||||
yield waitUntilState(store, state => state.viewports.length == 1
|
||||
&& state.devices.listState == Types.deviceListState.LOADED);
|
||||
|
||||
let deviceSelector = document.querySelector(".viewport-device-selector");
|
||||
let submitButton = document.querySelector("#device-submit-button");
|
||||
|
||||
info("Select existing device from the selector");
|
||||
yield selectDevice(ui, "Test Device");
|
||||
|
||||
openDeviceModal(ui);
|
||||
|
||||
info("Reveal device adder form, check that defaults are based on selected device");
|
||||
let adderShow = document.querySelector("#device-adder-show");
|
||||
Simulate.click(adderShow);
|
||||
testDeviceAdder(ui, Object.assign({}, device, {
|
||||
name: "Test Device (Custom)",
|
||||
}));
|
||||
|
||||
info("Remove previously added custom device");
|
||||
let deviceRemoveButton = document.querySelector(".device-remove-button");
|
||||
let removed = waitUntilState(store, state => state.devices.custom.length == 0);
|
||||
Simulate.click(deviceRemoveButton);
|
||||
yield removed;
|
||||
Simulate.click(submitButton);
|
||||
|
||||
info("Ensure custom device was removed from device selector");
|
||||
yield waitUntilState(store, state => state.viewports[0].device == "");
|
||||
is(deviceSelector.value, "", "Device selector reset to no device");
|
||||
let selectorOption = [...deviceSelector.options].find(opt => opt.value == device.name);
|
||||
ok(!selectorOption, "Custom device option removed from device selector");
|
||||
});
|
||||
|
||||
function testDeviceAdder(ui, expected) {
|
||||
let { document } = ui.toolWindow;
|
||||
|
||||
let nameInput = document.querySelector("#device-adder-name input");
|
||||
let [ widthInput, heightInput ] = document.querySelectorAll("#device-adder-size input");
|
||||
let pixelRatioInput = document.querySelector("#device-adder-pixel-ratio input");
|
||||
let userAgentInput = document.querySelector("#device-adder-user-agent input");
|
||||
let touchInput = document.querySelector("#device-adder-touch input");
|
||||
|
||||
is(nameInput.value, expected.name, "Device name matches");
|
||||
is(parseInt(widthInput.value, 10), expected.width, "Width matches");
|
||||
is(parseInt(heightInput.value, 10), expected.height, "Height matches");
|
||||
is(parseFloat(pixelRatioInput.value), expected.pixelRatio,
|
||||
"devicePixelRatio matches");
|
||||
is(userAgentInput.value, expected.userAgent, "User agent matches");
|
||||
is(touchInput.checked, expected.touch, "Touch matches");
|
||||
}
|
||||
|
||||
function setDeviceAdder(ui, value) {
|
||||
let { toolWindow } = ui;
|
||||
let { document } = ui.toolWindow;
|
||||
let React = toolWindow.require("devtools/client/shared/vendor/react");
|
||||
let { Simulate } = React.addons.TestUtils;
|
||||
|
||||
let nameInput = document.querySelector("#device-adder-name input");
|
||||
let [ widthInput, heightInput ] = document.querySelectorAll("#device-adder-size input");
|
||||
let pixelRatioInput = document.querySelector("#device-adder-pixel-ratio input");
|
||||
let userAgentInput = document.querySelector("#device-adder-user-agent input");
|
||||
let touchInput = document.querySelector("#device-adder-touch input");
|
||||
|
||||
nameInput.value = value.name;
|
||||
Simulate.change(nameInput);
|
||||
widthInput.value = value.width;
|
||||
Simulate.change(widthInput);
|
||||
Simulate.blur(widthInput);
|
||||
heightInput.value = value.height;
|
||||
Simulate.change(heightInput);
|
||||
Simulate.blur(heightInput);
|
||||
pixelRatioInput.value = value.pixelRatio;
|
||||
Simulate.change(pixelRatioInput);
|
||||
userAgentInput.value = value.userAgent;
|
||||
Simulate.change(userAgentInput);
|
||||
touchInput.checked = value.touch;
|
||||
Simulate.change(touchInput);
|
||||
}
|
|
@ -60,6 +60,7 @@ registerCleanupFunction(() => {
|
|||
Services.prefs.clearUserPref("devtools.responsive.html.enabled");
|
||||
Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
|
||||
asyncStorage.removeItem("devtools.devices.url_cache");
|
||||
asyncStorage.removeItem("devtools.devices.local");
|
||||
});
|
||||
|
||||
// This depends on the "devtools.responsive.html.enabled" pref
|
||||
|
@ -242,30 +243,20 @@ function openDeviceModal({ toolWindow }) {
|
|||
}
|
||||
|
||||
function changeSelectValue({ toolWindow }, selector, value) {
|
||||
let { document } = toolWindow;
|
||||
let React = toolWindow.require("devtools/client/shared/vendor/react");
|
||||
let { Simulate } = React.addons.TestUtils;
|
||||
|
||||
info(`Selecting ${value} in ${selector}.`);
|
||||
|
||||
return new Promise(resolve => {
|
||||
let select = toolWindow.document.querySelector(selector);
|
||||
isnot(select, null, `selector "${selector}" should match an existing element.`);
|
||||
let select = document.querySelector(selector);
|
||||
isnot(select, null, `selector "${selector}" should match an existing element.`);
|
||||
|
||||
let option = [...select.options].find(o => o.value === String(value));
|
||||
isnot(option, undefined, `value "${value}" should match an existing option.`);
|
||||
let option = [...select.options].find(o => o.value === String(value));
|
||||
isnot(option, undefined, `value "${value}" should match an existing option.`);
|
||||
|
||||
let event = new toolWindow.UIEvent("change", {
|
||||
view: toolWindow,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
|
||||
select.addEventListener("change", () => {
|
||||
is(select.value, value,
|
||||
`Select's option with value "${value}" should be selected.`);
|
||||
resolve();
|
||||
}, { once: true });
|
||||
|
||||
select.value = value;
|
||||
select.dispatchEvent(event);
|
||||
});
|
||||
select.value = value;
|
||||
Simulate.change(select);
|
||||
}
|
||||
|
||||
const selectDevice = (ui, value) => Promise.all([
|
||||
|
|
|
@ -34,7 +34,7 @@ add_task(function* () {
|
|||
let viewport = getState().viewports[0];
|
||||
equal(viewport.device, "", "Default device is unselected");
|
||||
|
||||
dispatch(changeDevice(0, "Firefox OS Flame"));
|
||||
dispatch(changeDevice(0, "Firefox OS Flame", "phones"));
|
||||
|
||||
viewport = getState().viewports[0];
|
||||
equal(viewport.device, "Firefox OS Flame",
|
||||
|
|
|
@ -89,6 +89,9 @@ exports.devices = {
|
|||
// Whether or not the device modal is open
|
||||
isModalOpen: PropTypes.bool,
|
||||
|
||||
// Viewport id that triggered the modal to open
|
||||
modalOpenedFromViewport: PropTypes.number,
|
||||
|
||||
// Device list state, possible values are exported above in an enum
|
||||
listState: PropTypes.oneOf(Object.keys(exports.deviceListState)),
|
||||
|
||||
|
@ -140,6 +143,9 @@ exports.viewport = {
|
|||
// The currently selected device applied to the viewport
|
||||
device: PropTypes.string,
|
||||
|
||||
// The currently selected device type applied to the viewport
|
||||
deviceType: PropTypes.string,
|
||||
|
||||
// The width of the viewport
|
||||
width: PropTypes.number,
|
||||
|
||||
|
|
|
@ -78,99 +78,6 @@ var CommandUtils = {
|
|||
gcliInit.releaseSystem(target);
|
||||
},
|
||||
|
||||
/**
|
||||
* Read a toolbarSpec from preferences
|
||||
* @param pref The name of the preference to read
|
||||
*/
|
||||
getCommandbarSpec: function (pref) {
|
||||
let value = prefBranch.getComplexValue(pref, Ci.nsISupportsString).data;
|
||||
return JSON.parse(value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a list of props for React components that manage the state of the buttons.
|
||||
*
|
||||
* @param {Array} toolbarSpec - An array of strings each of which is a GCLI command.
|
||||
* @param {Object} target
|
||||
* @param {Object} document - Used to listen to unload event of the window.
|
||||
* @param {Requisition} requisition
|
||||
* @param {Function} createButtonState - A function that provides a common interface
|
||||
* to create a button for the toolbox.
|
||||
*
|
||||
* @return {Array} List of ToolboxButton objects..
|
||||
*
|
||||
* Warning: this method uses the unload event of the window that owns the
|
||||
* buttons that are of type checkbox. this means that we don't properly
|
||||
* unregister event handlers until the window is destroyed.
|
||||
*/
|
||||
createCommandButtons: function (toolbarSpec, target, document, requisition,
|
||||
createButtonState) {
|
||||
return util.promiseEach(toolbarSpec, typed => {
|
||||
// Ask GCLI to parse the typed string (doesn't execute it)
|
||||
return requisition.update(typed).then(() => {
|
||||
// Ignore invalid commands
|
||||
let command = requisition.commandAssignment.value;
|
||||
if (command == null) {
|
||||
throw new Error("No command '" + typed + "'");
|
||||
}
|
||||
if (!command.buttonId) {
|
||||
throw new Error("Attempting to add a button to the toolbar, and the command " +
|
||||
"did not have an id.");
|
||||
}
|
||||
// Create the ToolboxButton.
|
||||
let button = createButtonState({
|
||||
id: command.buttonId,
|
||||
className: command.buttonClass,
|
||||
description: command.tooltipText || command.description,
|
||||
onClick: requisition.updateExec.bind(requisition, typed)
|
||||
});
|
||||
|
||||
// Allow the command button to be toggleable.
|
||||
if (command.state) {
|
||||
/**
|
||||
* The onChange event should be called with an event object that
|
||||
* contains a target property which specifies which target the event
|
||||
* applies to. For legacy reasons the event object can also contain
|
||||
* a tab property.
|
||||
*/
|
||||
const onChange = (eventName, ev) => {
|
||||
if (ev.target == target || ev.tab == target.tab) {
|
||||
let updateChecked = (checked) => {
|
||||
// This will emit a ToolboxButton update event.
|
||||
button.isChecked = checked;
|
||||
};
|
||||
|
||||
// isChecked would normally be synchronous. An annoying quirk
|
||||
// of the 'csscoverage toggle' command forces us to accept a
|
||||
// promise here, but doing Promise.resolve(reply).then(...) here
|
||||
// makes this async for everyone, which breaks some tests so we
|
||||
// treat non-promise replies separately to keep then synchronous.
|
||||
let reply = command.state.isChecked(target);
|
||||
if (typeof reply.then == "function") {
|
||||
reply.then(updateChecked, console.error);
|
||||
} else {
|
||||
updateChecked(reply);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
command.state.onChange(target, onChange);
|
||||
onChange("", { target: target });
|
||||
|
||||
document.defaultView.addEventListener("unload", function (event) {
|
||||
if (command.state.offChange) {
|
||||
command.state.offChange(target, onChange);
|
||||
}
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
requisition.clear();
|
||||
|
||||
return button;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* A helper function to create the environment object that is passed to
|
||||
* GCLI commands.
|
||||
|
|
|
@ -4,12 +4,16 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { Task } = require("devtools/shared/task");
|
||||
const { getJSON } = require("devtools/client/shared/getjson");
|
||||
|
||||
const DEVICES_URL = "devtools.devices.url";
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const L10N = new LocalizationHelper("devtools/client/locales/device.properties");
|
||||
|
||||
loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
|
||||
|
||||
const DEVICES_URL = "devtools.devices.url";
|
||||
const LOCAL_DEVICES = "devtools.devices.local";
|
||||
|
||||
/* This is a catalog of common web-enabled devices and their properties,
|
||||
* intended for (mobile) device emulation.
|
||||
*
|
||||
|
@ -20,11 +24,14 @@ const L10N = new LocalizationHelper("devtools/client/locales/device.properties")
|
|||
* - pixelRatio: ratio from viewport to physical screen pixels.
|
||||
* - userAgent: UA string of the device's browser.
|
||||
* - touch: whether it has a touch screen.
|
||||
* - firefoxOS: whether Firefox OS is supported.
|
||||
* - os: default OS, such as "ios", "fxos", "android".
|
||||
*
|
||||
* The device types are:
|
||||
* ["phones", "tablets", "laptops", "televisions", "consoles", "watches"].
|
||||
*
|
||||
* To propose new devices for the shared catalog, check out the repo at
|
||||
* https://github.com/mozilla/simulated-devices and file a pull request.
|
||||
*
|
||||
* You can easily add more devices to this catalog from your own code (e.g. an
|
||||
* addon) like so:
|
||||
*
|
||||
|
@ -33,21 +40,38 @@ const L10N = new LocalizationHelper("devtools/client/locales/device.properties")
|
|||
*/
|
||||
|
||||
// Local devices catalog that addons can add to.
|
||||
let localDevices = {};
|
||||
let localDevices;
|
||||
let localDevicesLoaded = false;
|
||||
|
||||
// Load local devices from storage.
|
||||
let loadLocalDevices = Task.async(function* () {
|
||||
if (localDevicesLoaded) {
|
||||
return;
|
||||
}
|
||||
let devicesJSON = yield asyncStorage.getItem(LOCAL_DEVICES);
|
||||
if (!devicesJSON) {
|
||||
devicesJSON = "{}";
|
||||
}
|
||||
localDevices = JSON.parse(devicesJSON);
|
||||
localDevicesLoaded = true;
|
||||
});
|
||||
|
||||
// Add a device to the local catalog.
|
||||
function addDevice(device, type = "phones") {
|
||||
let addDevice = Task.async(function* (device, type = "phones") {
|
||||
yield loadLocalDevices();
|
||||
let list = localDevices[type];
|
||||
if (!list) {
|
||||
list = localDevices[type] = [];
|
||||
}
|
||||
list.push(device);
|
||||
}
|
||||
yield asyncStorage.setItem(LOCAL_DEVICES, JSON.stringify(localDevices));
|
||||
});
|
||||
exports.addDevice = addDevice;
|
||||
|
||||
// Remove a device from the local catalog.
|
||||
// returns `true` if the device is removed, `false` otherwise.
|
||||
function removeDevice(device, type = "phones") {
|
||||
let removeDevice = Task.async(function* (device, type = "phones") {
|
||||
yield loadLocalDevices();
|
||||
let list = localDevices[type];
|
||||
if (!list) {
|
||||
return false;
|
||||
|
@ -60,25 +84,26 @@ function removeDevice(device, type = "phones") {
|
|||
}
|
||||
|
||||
list.splice(index, 1);
|
||||
yield asyncStorage.setItem(LOCAL_DEVICES, JSON.stringify(localDevices));
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
exports.removeDevice = removeDevice;
|
||||
|
||||
// Get the complete devices catalog.
|
||||
function getDevices() {
|
||||
let getDevices = Task.async(function* () {
|
||||
// Fetch common devices from Mozilla's CDN.
|
||||
return getJSON(DEVICES_URL).then(devices => {
|
||||
for (let type in localDevices) {
|
||||
if (!devices[type]) {
|
||||
devices.TYPES.push(type);
|
||||
devices[type] = [];
|
||||
}
|
||||
devices[type] = localDevices[type].concat(devices[type]);
|
||||
let devices = yield getJSON(DEVICES_URL);
|
||||
yield loadLocalDevices();
|
||||
for (let type in localDevices) {
|
||||
if (!devices[type]) {
|
||||
devices.TYPES.push(type);
|
||||
devices[type] = [];
|
||||
}
|
||||
return devices;
|
||||
});
|
||||
}
|
||||
devices[type] = localDevices[type].concat(devices[type]);
|
||||
}
|
||||
return devices;
|
||||
});
|
||||
exports.getDevices = getDevices;
|
||||
|
||||
// Get the localized string for a device type.
|
||||
|
|
|
@ -542,8 +542,8 @@ KeyframeEffectReadOnly::ComposeStyle(
|
|||
|
||||
// Bug 1329878 - Stylo: Implement accumulate and addition on Servo
|
||||
// AnimationValue.
|
||||
RawServoAnimationValue* servoFromValue = segment->mServoFromValue;
|
||||
RawServoAnimationValue* servoToValue = segment->mServoToValue;
|
||||
RawServoAnimationValue* servoFromValue = segment->mFromValue.mServo;
|
||||
RawServoAnimationValue* servoToValue = segment->mToValue.mServo;
|
||||
|
||||
// For unsupported or non-animatable animation types, we get nullptrs.
|
||||
if (!servoFromValue || !servoToValue) {
|
||||
|
@ -599,11 +599,11 @@ KeyframeEffectReadOnly::ComposeStyle(
|
|||
|
||||
StyleAnimationValue fromValue =
|
||||
CompositeValue(prop.mProperty, aStyleRule.mGecko,
|
||||
segment->mFromValue,
|
||||
segment->mFromValue.mGecko,
|
||||
segment->mFromComposite);
|
||||
StyleAnimationValue toValue =
|
||||
CompositeValue(prop.mProperty, aStyleRule.mGecko,
|
||||
segment->mToValue,
|
||||
segment->mToValue.mGecko,
|
||||
segment->mToComposite);
|
||||
|
||||
// Iteration composition for accumulate
|
||||
|
@ -614,9 +614,9 @@ KeyframeEffectReadOnly::ComposeStyle(
|
|||
prop.mSegments.LastElement();
|
||||
// FIXME: Bug 1293492: Add a utility function to calculate both of
|
||||
// below StyleAnimationValues.
|
||||
StyleAnimationValue lastValue = lastSegment.mToValue.IsNull()
|
||||
StyleAnimationValue lastValue = lastSegment.mToValue.mGecko.IsNull()
|
||||
? GetUnderlyingStyle(prop.mProperty, aStyleRule.mGecko)
|
||||
: lastSegment.mToValue;
|
||||
: lastSegment.mToValue.mGecko;
|
||||
fromValue =
|
||||
StyleAnimationValue::Accumulate(prop.mProperty,
|
||||
lastValue,
|
||||
|
@ -1015,10 +1015,10 @@ DumpAnimationProperties(nsTArray<AnimationProperty>& aAnimationProperties)
|
|||
for (auto& s : p.mSegments) {
|
||||
nsString fromValue, toValue;
|
||||
Unused << StyleAnimationValue::UncomputeValue(p.mProperty,
|
||||
s.mFromValue,
|
||||
s.mFromValue.mGecko,
|
||||
fromValue);
|
||||
Unused << StyleAnimationValue::UncomputeValue(p.mProperty,
|
||||
s.mToValue,
|
||||
s.mToValue.mGecko,
|
||||
toValue);
|
||||
printf(" %f..%f: %s..%s\n", s.mFromKey, s.mToKey,
|
||||
NS_ConvertUTF16toUTF8(fromValue).get(),
|
||||
|
@ -1135,7 +1135,7 @@ KeyframeEffectReadOnly::GetProperties(
|
|||
|
||||
binding_detail::FastAnimationPropertyValueDetails fromValue;
|
||||
CreatePropertyValue(property.mProperty, segment.mFromKey,
|
||||
segment.mTimingFunction, segment.mFromValue,
|
||||
segment.mTimingFunction, segment.mFromValue.mGecko,
|
||||
segment.mFromComposite, fromValue);
|
||||
// We don't apply timing functions for zero-length segments, so
|
||||
// don't return one here.
|
||||
|
@ -1152,10 +1152,11 @@ KeyframeEffectReadOnly::GetProperties(
|
|||
// a) this is the last segment, or
|
||||
// b) the next segment's from-value differs.
|
||||
if (segmentIdx == segmentLen - 1 ||
|
||||
property.mSegments[segmentIdx + 1].mFromValue != segment.mToValue) {
|
||||
property.mSegments[segmentIdx + 1].mFromValue.mGecko !=
|
||||
segment.mToValue.mGecko) {
|
||||
binding_detail::FastAnimationPropertyValueDetails toValue;
|
||||
CreatePropertyValue(property.mProperty, segment.mToKey,
|
||||
Nothing(), segment.mToValue,
|
||||
Nothing(), segment.mToValue.mGecko,
|
||||
segment.mToComposite, toValue);
|
||||
// It doesn't really make sense to have a timing function on the
|
||||
// last property value or before a sudden jump so we just drop the
|
||||
|
@ -1621,11 +1622,13 @@ KeyframeEffectReadOnly::CalculateCumulativeChangeHint(
|
|||
}
|
||||
RefPtr<nsStyleContext> fromContext =
|
||||
CreateStyleContextForAnimationValue(property.mProperty,
|
||||
segment.mFromValue, aStyleContext);
|
||||
segment.mFromValue.mGecko,
|
||||
aStyleContext);
|
||||
|
||||
RefPtr<nsStyleContext> toContext =
|
||||
CreateStyleContextForAnimationValue(property.mProperty,
|
||||
segment.mToValue, aStyleContext);
|
||||
segment.mToValue.mGecko,
|
||||
aStyleContext);
|
||||
|
||||
uint32_t equalStructs = 0;
|
||||
uint32_t samePointerStructs = 0;
|
||||
|
|
|
@ -60,9 +60,7 @@ struct AnimationPropertySegment
|
|||
float mFromKey, mToKey;
|
||||
// NOTE: In the case that no keyframe for 0 or 1 offset is specified
|
||||
// the unit of mFromValue or mToValue is eUnit_Null.
|
||||
StyleAnimationValue mFromValue, mToValue;
|
||||
// FIXME add a deep == impl for RawServoAnimationValue
|
||||
RefPtr<RawServoAnimationValue> mServoFromValue, mServoToValue;
|
||||
AnimationValue mFromValue, mToValue;
|
||||
|
||||
Maybe<ComputedTimingFunction> mTimingFunction;
|
||||
dom::CompositeOperation mFromComposite = dom::CompositeOperation::Replace;
|
||||
|
|
|
@ -271,8 +271,7 @@ struct AdditionalProperty
|
|||
struct KeyframeValueEntry
|
||||
{
|
||||
nsCSSPropertyID mProperty;
|
||||
StyleAnimationValue mValue;
|
||||
RefPtr<RawServoAnimationValue> mServoValue;
|
||||
AnimationValue mValue;
|
||||
|
||||
float mOffset;
|
||||
Maybe<ComputedTimingFunction> mTimingFunction;
|
||||
|
@ -704,7 +703,6 @@ KeyframeUtils::GetAnimationPropertiesFromKeyframes(
|
|||
entry->mOffset = frame.mComputedOffset;
|
||||
entry->mProperty = value.mProperty;
|
||||
entry->mValue = value.mValue;
|
||||
entry->mServoValue = value.mServoValue;
|
||||
entry->mTimingFunction = frame.mTimingFunction;
|
||||
entry->mComposite =
|
||||
frame.mComposite ? frame.mComposite.value() : aEffectComposite;
|
||||
|
@ -1404,8 +1402,6 @@ BuildSegmentsFromValueEntries(nsTArray<KeyframeValueEntry>& aEntries,
|
|||
segment->mToKey = aEntries[j].mOffset;
|
||||
segment->mFromValue = aEntries[i].mValue;
|
||||
segment->mToValue = aEntries[j].mValue;
|
||||
segment->mServoFromValue = aEntries[i].mServoValue;
|
||||
segment->mServoToValue = aEntries[j].mServoValue;
|
||||
segment->mTimingFunction = aEntries[i].mTimingFunction;
|
||||
segment->mFromComposite = aEntries[i].mComposite;
|
||||
segment->mToComposite = aEntries[j].mComposite;
|
||||
|
@ -1786,8 +1782,8 @@ GetCumulativeDistances(const nsTArray<ComputedKeyframeValues>& aValues,
|
|||
double componentDistance = 0.0;
|
||||
if (StyleAnimationValue::ComputeDistance(
|
||||
prop,
|
||||
prevPacedValues[propIdx].mValue,
|
||||
pacedValues[propIdx].mValue,
|
||||
prevPacedValues[propIdx].mValue.mGecko,
|
||||
pacedValues[propIdx].mValue.mGecko,
|
||||
aStyleContext,
|
||||
componentDistance)) {
|
||||
dist += componentDistance * componentDistance;
|
||||
|
@ -1800,8 +1796,8 @@ GetCumulativeDistances(const nsTArray<ComputedKeyframeValues>& aValues,
|
|||
// no distance between the previous paced value and this value.
|
||||
Unused <<
|
||||
StyleAnimationValue::ComputeDistance(aPacedProperty,
|
||||
prevPacedValues[0].mValue,
|
||||
pacedValues[0].mValue,
|
||||
prevPacedValues[0].mValue.mGecko,
|
||||
pacedValues[0].mValue.mGecko,
|
||||
aStyleContext,
|
||||
dist);
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ support-files =
|
|||
file_use_counter_outer.html
|
||||
file_use_counter_svg_getElementById.svg
|
||||
file_use_counter_svg_currentScale.svg
|
||||
file_use_counter_svg_background.html
|
||||
file_use_counter_svg_list_style_image.html
|
||||
file_use_counter_svg_fill_pattern_definition.svg
|
||||
file_use_counter_svg_fill_pattern.svg
|
||||
file_use_counter_svg_fill_pattern_internal.svg
|
||||
|
|
|
@ -71,12 +71,6 @@ add_task(function* () {
|
|||
// data: URLs don't correctly propagate to their referring document yet.
|
||||
//yield check_use_counter_direct("file_use_counter_svg_fill_pattern_data.svg",
|
||||
// "PROPERTY_FILL_OPACITY");
|
||||
|
||||
// Check that use counters are incremented by SVGs loaded as CSS images in
|
||||
// pages loaded in iframes. Again, SVG images in CSS aren't permitted to
|
||||
// execute script, so we need to use properties here.
|
||||
yield check_use_counter_iframe("file_use_counter_svg_list_style_image.html",
|
||||
"PROPERTY_FILL");
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=968923
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 968923</title>
|
||||
<style>
|
||||
/* Use a query string to work around imagelib caching.
|
||||
Otherwise, we won't get use counters for this file. */
|
||||
body { background-image: url('file_use_counter_svg_getElementById.svg?asbackground=1') }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=968923">Mozilla Bug 968923</a>
|
||||
<img id="display" />
|
||||
<iframe id="content" src="about:blank">
|
||||
|
||||
</iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -1,22 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=968923
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 968923</title>
|
||||
<style>
|
||||
/* Use a query string to work around imagelib caching.
|
||||
Otherwise, we won't get use counters for this file. */
|
||||
ul { list-style-image: url('file_use_counter_svg_currentScale.svg?asliststyleimage=1') }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=968923">Mozilla Bug 968923</a>
|
||||
<ul>
|
||||
<li>Some text</li>
|
||||
<li>Some other text</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -810,9 +810,9 @@ MediaFormatReader::DemuxerProxy::NotifyDataArrived()
|
|||
static const char*
|
||||
TrackTypeToStr(TrackInfo::TrackType aTrack)
|
||||
{
|
||||
MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack ||
|
||||
aTrack == TrackInfo::kVideoTrack ||
|
||||
aTrack == TrackInfo::kTextTrack);
|
||||
MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack
|
||||
|| aTrack == TrackInfo::kVideoTrack
|
||||
|| aTrack == TrackInfo::kTextTrack);
|
||||
switch (aTrack) {
|
||||
case TrackInfo::kAudioTrack:
|
||||
return "Audio";
|
||||
|
@ -1307,9 +1307,9 @@ MediaFormatReader::ShouldSkip(bool aSkipToNextKeyframe,
|
|||
if (NS_FAILED(rv)) {
|
||||
return aSkipToNextKeyframe;
|
||||
}
|
||||
return (nextKeyframe < aTimeThreshold ||
|
||||
(mVideo.mTimeThreshold
|
||||
&& mVideo.mTimeThreshold.ref().EndTime() < aTimeThreshold))
|
||||
return (nextKeyframe < aTimeThreshold
|
||||
|| (mVideo.mTimeThreshold
|
||||
&& mVideo.mTimeThreshold.ref().EndTime() < aTimeThreshold))
|
||||
&& nextKeyframe.ToMicroseconds() >= 0
|
||||
&& !nextKeyframe.IsInfinite();
|
||||
}
|
||||
|
@ -1772,8 +1772,8 @@ MediaFormatReader::HandleDemuxedSamples(
|
|||
if (info && decoder.mLastStreamSourceID != info->GetID()) {
|
||||
bool supportRecycling = MediaPrefs::MediaDecoderCheckRecycling()
|
||||
&& decoder.mDecoder->SupportDecoderRecycling();
|
||||
if (decoder.mNextStreamSourceID.isNothing() ||
|
||||
decoder.mNextStreamSourceID.ref() != info->GetID()) {
|
||||
if (decoder.mNextStreamSourceID.isNothing()
|
||||
|| decoder.mNextStreamSourceID.ref() != info->GetID()) {
|
||||
if (!supportRecycling) {
|
||||
LOG("%s stream id has changed from:%d to:%d, draining decoder.",
|
||||
TrackTypeToStr(aTrack), decoder.mLastStreamSourceID,
|
||||
|
@ -1891,22 +1891,28 @@ MediaFormatReader::DrainDecoder(TrackType aTrack)
|
|||
return;
|
||||
}
|
||||
decoder.mNeedDraining = false;
|
||||
if (!decoder.mDecoder ||
|
||||
decoder.mNumSamplesInput == decoder.mNumSamplesOutput) {
|
||||
decoder.mDraining = true;
|
||||
if (!decoder.mDecoder
|
||||
|| decoder.mNumSamplesInput == decoder.mNumSamplesOutput) {
|
||||
// No frames to drain.
|
||||
LOGV("Draining %s with nothing to drain", TrackTypeToStr(aTrack));
|
||||
NotifyDrainComplete(aTrack);
|
||||
return;
|
||||
}
|
||||
decoder.mDraining = true;
|
||||
RefPtr<MediaFormatReader> self = this;
|
||||
decoder.mDecoder->Drain()
|
||||
->Then(mTaskQueue, __func__,
|
||||
[self, this, aTrack, &decoder]
|
||||
(const MediaDataDecoder::DecodedData& aResults) {
|
||||
decoder.mDrainRequest.Complete();
|
||||
NotifyNewOutput(aTrack, aResults);
|
||||
NotifyDrainComplete(aTrack);
|
||||
if (aResults.IsEmpty()) {
|
||||
NotifyDrainComplete(aTrack);
|
||||
} else {
|
||||
NotifyNewOutput(aTrack, aResults);
|
||||
// Let's see if we have any more data available to drain.
|
||||
decoder.mNeedDraining = true;
|
||||
decoder.mDraining = false;
|
||||
}
|
||||
},
|
||||
[self, this, aTrack, &decoder](const MediaResult& aError) {
|
||||
decoder.mDrainRequest.Complete();
|
||||
|
@ -2027,15 +2033,13 @@ MediaFormatReader::Update(TrackType aTrack)
|
|||
decoder.RejectPromise(decoder.mError.ref(), __func__);
|
||||
return;
|
||||
} else if (decoder.mDrainComplete) {
|
||||
bool wasDraining = decoder.mDraining;
|
||||
decoder.mDrainComplete = false;
|
||||
decoder.mDraining = false;
|
||||
if (decoder.mDemuxEOS) {
|
||||
LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack));
|
||||
decoder.RejectPromise(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
|
||||
} else if (decoder.mWaitingForData) {
|
||||
if (wasDraining && decoder.mLastSampleTime &&
|
||||
!decoder.mNextStreamSourceID) {
|
||||
if (decoder.mLastSampleTime && !decoder.mNextStreamSourceID) {
|
||||
// We have completed draining the decoder following WaitingForData.
|
||||
// Set up the internal seek machinery to be able to resume from the
|
||||
// last sample decoded.
|
||||
|
@ -2090,7 +2094,7 @@ MediaFormatReader::Update(TrackType aTrack)
|
|||
media::TimeUnit nextKeyframe;
|
||||
if (aTrack == TrackType::kVideoTrack && !decoder.HasInternalSeekPending()
|
||||
&& NS_SUCCEEDED(
|
||||
decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe))) {
|
||||
decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe))) {
|
||||
if (needsNewDecoder) {
|
||||
ShutdownDecoder(aTrack);
|
||||
}
|
||||
|
@ -2153,8 +2157,8 @@ MediaFormatReader::ReturnOutput(MediaData* aData, TrackType aTrack)
|
|||
if (aTrack == TrackInfo::kAudioTrack) {
|
||||
AudioData* audioData = static_cast<AudioData*>(aData);
|
||||
|
||||
if (audioData->mChannels != mInfo.mAudio.mChannels ||
|
||||
audioData->mRate != mInfo.mAudio.mRate) {
|
||||
if (audioData->mChannels != mInfo.mAudio.mChannels
|
||||
|| audioData->mRate != mInfo.mAudio.mRate) {
|
||||
LOG("change of audio format (rate:%d->%d). "
|
||||
"This is an unsupported configuration",
|
||||
mInfo.mAudio.mRate, audioData->mRate);
|
||||
|
@ -2489,8 +2493,8 @@ MediaFormatReader::OnSeekFailed(TrackType aTrack, const MediaResult& aError)
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (nextSeekTime.isNothing() ||
|
||||
nextSeekTime.ref() > mFallbackSeekTime.ref()) {
|
||||
if (nextSeekTime.isNothing()
|
||||
|| nextSeekTime.ref() > mFallbackSeekTime.ref()) {
|
||||
nextSeekTime = Some(mFallbackSeekTime.ref());
|
||||
LOG("Unable to seek audio to video seek time. A/V sync may be broken");
|
||||
} else {
|
||||
|
@ -2742,8 +2746,8 @@ MediaFormatReader::UpdateBuffered()
|
|||
intervals = mVideo.mTimeRanges;
|
||||
}
|
||||
|
||||
if (!intervals.Length() ||
|
||||
intervals.GetStart() == media::TimeUnit::FromMicroseconds(0)) {
|
||||
if (!intervals.Length()
|
||||
|| intervals.GetStart() == media::TimeUnit::FromMicroseconds(0)) {
|
||||
// IntervalSet already starts at 0 or is empty, nothing to shift.
|
||||
mBuffered = intervals;
|
||||
} else {
|
||||
|
|
|
@ -251,8 +251,11 @@ public:
|
|||
// it drops the input samples. The decoder may be holding onto samples
|
||||
// that are required to decode samples that it expects to get in future.
|
||||
// This is called when the demuxer reaches end of stream.
|
||||
// This function is asynchronous. The MediaDataDecoder shall resolve the
|
||||
// pending DecodePromise will all drained samples.
|
||||
// This function is asynchronous.
|
||||
// The MediaDataDecoder shall resolve the pending DecodePromise with drained
|
||||
// samples. Drain will be called multiple times until the resolved
|
||||
// DecodePromise is empty which indicates that there are no more samples to
|
||||
// drain.
|
||||
virtual RefPtr<DecodePromise> Drain() = 0;
|
||||
|
||||
// Causes all samples in the decoding pipeline to be discarded. When this
|
||||
|
|
|
@ -11,15 +11,15 @@
|
|||
|
||||
#define MAX_CHANNELS 16
|
||||
|
||||
namespace mozilla
|
||||
{
|
||||
namespace mozilla {
|
||||
|
||||
FFmpegAudioDecoder<LIBAV_VER>::FFmpegAudioDecoder(FFmpegLibWrapper* aLib,
|
||||
TaskQueue* aTaskQueue, const AudioInfo& aConfig)
|
||||
: FFmpegDataDecoder(aLib, aTaskQueue, GetCodecId(aConfig.mMimeType))
|
||||
{
|
||||
MOZ_COUNT_CTOR(FFmpegAudioDecoder);
|
||||
// Use a new MediaByteBuffer as the object will be modified during initialization.
|
||||
// Use a new MediaByteBuffer as the object will be modified during
|
||||
// initialization.
|
||||
if (aConfig.mCodecSpecificConfig && aConfig.mCodecSpecificConfig->Length()) {
|
||||
mExtraData = new MediaByteBuffer;
|
||||
mExtraData->AppendElements(*aConfig.mCodecSpecificConfig);
|
||||
|
@ -31,8 +31,10 @@ FFmpegAudioDecoder<LIBAV_VER>::Init()
|
|||
{
|
||||
nsresult rv = InitDecoder();
|
||||
|
||||
return rv == NS_OK ? InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__)
|
||||
: InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
|
||||
return rv == NS_OK
|
||||
? InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__)
|
||||
: InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
|
||||
__func__);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -44,7 +46,8 @@ FFmpegAudioDecoder<LIBAV_VER>::InitCodecContext()
|
|||
// isn't implemented.
|
||||
mCodecContext->thread_count = 1;
|
||||
// FFmpeg takes this as a suggestion for what format to use for audio samples.
|
||||
// LibAV 0.8 produces rubbish float interleaved samples, request 16 bits audio.
|
||||
// LibAV 0.8 produces rubbish float interleaved samples, request 16 bits
|
||||
// audio.
|
||||
mCodecContext->request_sample_fmt =
|
||||
(mLib->mVersion == 53) ? AV_SAMPLE_FMT_S16 : AV_SAMPLE_FMT_FLT;
|
||||
}
|
||||
|
|
|
@ -7,11 +7,10 @@
|
|||
#ifndef __FFmpegAACDecoder_h__
|
||||
#define __FFmpegAACDecoder_h__
|
||||
|
||||
#include "FFmpegLibWrapper.h"
|
||||
#include "FFmpegDataDecoder.h"
|
||||
#include "FFmpegLibWrapper.h"
|
||||
|
||||
namespace mozilla
|
||||
{
|
||||
namespace mozilla {
|
||||
|
||||
template <int V> class FFmpegAudioDecoder
|
||||
{
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
* 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 "mozilla/SyncRunnable.h"
|
||||
#include "mozilla/TaskQueue.h"
|
||||
|
||||
#include <string.h>
|
||||
#ifdef __GNUC__
|
||||
|
@ -14,10 +12,10 @@
|
|||
|
||||
#include "FFmpegLog.h"
|
||||
#include "FFmpegDataDecoder.h"
|
||||
#include "mozilla/TaskQueue.h"
|
||||
#include "prsystem.h"
|
||||
|
||||
namespace mozilla
|
||||
{
|
||||
namespace mozilla {
|
||||
|
||||
StaticMutex FFmpegDataDecoder<LIBAV_VER>::sMonitor;
|
||||
|
||||
|
|
|
@ -12,8 +12,7 @@
|
|||
#include "mozilla/StaticMutex.h"
|
||||
#include "FFmpegLibs.h"
|
||||
|
||||
namespace mozilla
|
||||
{
|
||||
namespace mozilla {
|
||||
|
||||
template <int V>
|
||||
class FFmpegDataDecoder : public MediaDataDecoder
|
||||
|
@ -42,7 +41,7 @@ protected:
|
|||
// Flush and Drain operation, always run
|
||||
virtual RefPtr<FlushPromise> ProcessFlush();
|
||||
virtual void ProcessShutdown();
|
||||
virtual void InitCodecContext() {}
|
||||
virtual void InitCodecContext() { }
|
||||
AVFrame* PrepareFrame();
|
||||
nsresult InitDecoder();
|
||||
|
||||
|
|
|
@ -12,8 +12,7 @@
|
|||
#include "FFmpegAudioDecoder.h"
|
||||
#include "FFmpegVideoDecoder.h"
|
||||
|
||||
namespace mozilla
|
||||
{
|
||||
namespace mozilla {
|
||||
|
||||
template <int V>
|
||||
class FFmpegDecoderModule : public PlatformDecoderModule
|
||||
|
@ -27,8 +26,8 @@ public:
|
|||
return pdm.forget();
|
||||
}
|
||||
|
||||
explicit FFmpegDecoderModule(FFmpegLibWrapper* aLib) : mLib(aLib) {}
|
||||
virtual ~FFmpegDecoderModule() {}
|
||||
explicit FFmpegDecoderModule(FFmpegLibWrapper* aLib) : mLib(aLib) { }
|
||||
virtual ~FFmpegDecoderModule() { }
|
||||
|
||||
already_AddRefed<MediaDataDecoder>
|
||||
CreateVideoDecoder(const CreateDecoderParams& aParams) override
|
||||
|
@ -73,9 +72,9 @@ public:
|
|||
ConversionRequired
|
||||
DecoderNeedsConversion(const TrackInfo& aConfig) const override
|
||||
{
|
||||
if (aConfig.IsVideo() &&
|
||||
(aConfig.mMimeType.EqualsLiteral("video/avc") ||
|
||||
aConfig.mMimeType.EqualsLiteral("video/mp4"))) {
|
||||
if (aConfig.IsVideo()
|
||||
&& (aConfig.mMimeType.EqualsLiteral("video/avc")
|
||||
|| aConfig.mMimeType.EqualsLiteral("video/mp4"))) {
|
||||
return ConversionRequired::kNeedAVCC;
|
||||
} else {
|
||||
return ConversionRequired::kNeedNone;
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
#include "FFmpegLog.h"
|
||||
#include "prlink.h"
|
||||
|
||||
namespace mozilla
|
||||
{
|
||||
namespace mozilla {
|
||||
|
||||
FFmpegRuntimeLinker::LinkStatus FFmpegRuntimeLinker::sLinkStatus =
|
||||
LinkStatus_INIT;
|
||||
|
@ -59,7 +58,8 @@ FFmpegRuntimeLinker::Init()
|
|||
PRLibSpec lspec;
|
||||
lspec.type = PR_LibSpec_Pathname;
|
||||
lspec.value.pathname = lib;
|
||||
sLibAV.mAVCodecLib = PR_LoadLibraryWithFlags(lspec, PR_LD_NOW | PR_LD_LOCAL);
|
||||
sLibAV.mAVCodecLib =
|
||||
PR_LoadLibraryWithFlags(lspec, PR_LD_NOW | PR_LD_LOCAL);
|
||||
if (sLibAV.mAVCodecLib) {
|
||||
sLibAV.mAVUtilLib = sLibAV.mAVCodecLib;
|
||||
switch (sLibAV.Link()) {
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
|
||||
#include "PlatformDecoderModule.h"
|
||||
|
||||
namespace mozilla
|
||||
{
|
||||
namespace mozilla {
|
||||
|
||||
class FFmpegRuntimeLinker
|
||||
{
|
||||
|
|
|
@ -4,18 +4,12 @@
|
|||
* 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 "mozilla/TaskQueue.h"
|
||||
|
||||
#include "nsThreadUtils.h"
|
||||
#include "ImageContainer.h"
|
||||
|
||||
#include "MediaInfo.h"
|
||||
#include "VPXDecoder.h"
|
||||
#include "MP4Decoder.h"
|
||||
|
||||
#include "FFmpegVideoDecoder.h"
|
||||
#include "FFmpegLog.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "ImageContainer.h"
|
||||
#include "MediaInfo.h"
|
||||
#include "MP4Decoder.h"
|
||||
#include "VPXDecoder.h"
|
||||
|
||||
#include "libavutil/pixfmt.h"
|
||||
#if LIBAVCODEC_VERSION_MAJOR < 54
|
||||
|
@ -25,12 +19,15 @@
|
|||
#define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P
|
||||
#define AV_PIX_FMT_NONE PIX_FMT_NONE
|
||||
#endif
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "mozilla/TaskQueue.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
|
||||
typedef mozilla::layers::Image Image;
|
||||
typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
|
||||
|
||||
namespace mozilla
|
||||
{
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* FFmpeg calls back to this function with a list of pixel formats it supports.
|
||||
|
@ -71,7 +68,8 @@ FFmpegVideoDecoder<LIBAV_VER>::PtsCorrectionContext::PtsCorrectionContext()
|
|||
}
|
||||
|
||||
int64_t
|
||||
FFmpegVideoDecoder<LIBAV_VER>::PtsCorrectionContext::GuessCorrectPts(int64_t aPts, int64_t aDts)
|
||||
FFmpegVideoDecoder<LIBAV_VER>::PtsCorrectionContext::GuessCorrectPts(
|
||||
int64_t aPts, int64_t aDts)
|
||||
{
|
||||
int64_t pts = AV_NOPTS_VALUE;
|
||||
|
||||
|
@ -83,8 +81,8 @@ FFmpegVideoDecoder<LIBAV_VER>::PtsCorrectionContext::GuessCorrectPts(int64_t aPt
|
|||
mNumFaultyPts += aPts <= mLastPts;
|
||||
mLastPts = aPts;
|
||||
}
|
||||
if ((mNumFaultyPts <= mNumFaultyDts || aDts == int64_t(AV_NOPTS_VALUE)) &&
|
||||
aPts != int64_t(AV_NOPTS_VALUE)) {
|
||||
if ((mNumFaultyPts <= mNumFaultyDts || aDts == int64_t(AV_NOPTS_VALUE))
|
||||
&& aPts != int64_t(AV_NOPTS_VALUE)) {
|
||||
pts = aPts;
|
||||
} else {
|
||||
pts = aDts;
|
||||
|
@ -101,8 +99,9 @@ FFmpegVideoDecoder<LIBAV_VER>::PtsCorrectionContext::Reset()
|
|||
mLastDts = INT64_MIN;
|
||||
}
|
||||
|
||||
FFmpegVideoDecoder<LIBAV_VER>::FFmpegVideoDecoder(FFmpegLibWrapper* aLib,
|
||||
TaskQueue* aTaskQueue, const VideoInfo& aConfig, ImageContainer* aImageContainer)
|
||||
FFmpegVideoDecoder<LIBAV_VER>::FFmpegVideoDecoder(
|
||||
FFmpegLibWrapper* aLib, TaskQueue* aTaskQueue, const VideoInfo& aConfig,
|
||||
ImageContainer* aImageContainer)
|
||||
: FFmpegDataDecoder(aLib, aTaskQueue, GetCodecId(aConfig.mMimeType))
|
||||
, mImageContainer(aImageContainer)
|
||||
, mInfo(aConfig)
|
||||
|
@ -110,7 +109,8 @@ FFmpegVideoDecoder<LIBAV_VER>::FFmpegVideoDecoder(FFmpegLibWrapper* aLib,
|
|||
, mLastInputDts(INT64_MIN)
|
||||
{
|
||||
MOZ_COUNT_CTOR(FFmpegVideoDecoder);
|
||||
// Use a new MediaByteBuffer as the object will be modified during initialization.
|
||||
// Use a new MediaByteBuffer as the object will be modified during
|
||||
// initialization.
|
||||
mExtraData = new MediaByteBuffer;
|
||||
mExtraData->AppendElements(*aConfig.mExtraData);
|
||||
}
|
||||
|
@ -187,10 +187,9 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample, bool* aGotFrame,
|
|||
while (inputSize) {
|
||||
uint8_t* data;
|
||||
int size;
|
||||
int len = mLib->av_parser_parse2(mCodecParser, mCodecContext, &data, &size,
|
||||
inputData, inputSize,
|
||||
aSample->mTime, aSample->mTimecode,
|
||||
aSample->mOffset);
|
||||
int len = mLib->av_parser_parse2(
|
||||
mCodecParser, mCodecContext, &data, &size, inputData, inputSize,
|
||||
aSample->mTime, aSample->mTimecode, aSample->mOffset);
|
||||
if (size_t(len) > inputSize) {
|
||||
return NS_ERROR_DOM_MEDIA_DECODE_ERR;
|
||||
}
|
||||
|
@ -281,8 +280,9 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample,
|
|||
// against the map becoming extremely big.
|
||||
mDurationMap.Clear();
|
||||
}
|
||||
FFMPEG_LOG("Got one frame output with pts=%lld dts=%lld duration=%lld opaque=%lld",
|
||||
pts, mFrame->pkt_dts, duration, mCodecContext->reordered_opaque);
|
||||
FFMPEG_LOG(
|
||||
"Got one frame output with pts=%lld dts=%lld duration=%lld opaque=%lld",
|
||||
pts, mFrame->pkt_dts, duration, mCodecContext->reordered_opaque);
|
||||
|
||||
VideoData::YCbCrBuffer b;
|
||||
b.mPlanes[0].mData = mFrame->data[0];
|
||||
|
|
|
@ -48,8 +48,10 @@ private:
|
|||
RefPtr<DecodePromise> ProcessDecode(MediaRawData* aSample) override;
|
||||
RefPtr<DecodePromise> ProcessDrain() override;
|
||||
RefPtr<FlushPromise> ProcessFlush() override;
|
||||
MediaResult DoDecode(MediaRawData* aSample, bool* aGotFrame, DecodedData& aResults);
|
||||
MediaResult DoDecode(MediaRawData* aSample, uint8_t* aData, int aSize, bool* aGotFrame, DecodedData& aResults);
|
||||
MediaResult DoDecode(MediaRawData* aSample, bool* aGotFrame,
|
||||
DecodedData& aResults);
|
||||
MediaResult DoDecode(MediaRawData* aSample, uint8_t* aData, int aSize,
|
||||
bool* aGotFrame, DecodedData& aResults);
|
||||
void OutputDelayedFrames();
|
||||
|
||||
/**
|
||||
|
@ -67,7 +69,8 @@ private:
|
|||
// Parser used for VP8 and VP9 decoding.
|
||||
AVCodecParserContext* mCodecParser;
|
||||
|
||||
class PtsCorrectionContext {
|
||||
class PtsCorrectionContext
|
||||
{
|
||||
public:
|
||||
PtsCorrectionContext();
|
||||
int64_t GuessCorrectPts(int64_t aPts, int64_t aDts);
|
||||
|
@ -77,14 +80,15 @@ private:
|
|||
private:
|
||||
int64_t mNumFaultyPts; /// Number of incorrect PTS values so far
|
||||
int64_t mNumFaultyDts; /// Number of incorrect DTS values so far
|
||||
int64_t mLastPts; /// PTS of the last frame
|
||||
int64_t mLastDts; /// DTS of the last frame
|
||||
int64_t mLastPts; /// PTS of the last frame
|
||||
int64_t mLastDts; /// DTS of the last frame
|
||||
};
|
||||
|
||||
PtsCorrectionContext mPtsContext;
|
||||
int64_t mLastInputDts;
|
||||
|
||||
class DurationMap {
|
||||
class DurationMap
|
||||
{
|
||||
public:
|
||||
typedef Pair<int64_t, int64_t> DurationElement;
|
||||
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
// soundtouch happens to be always included in lgpllibs
|
||||
#include "soundtouch/SoundTouch.h"
|
||||
|
||||
namespace mozilla
|
||||
{
|
||||
namespace mozilla {
|
||||
|
||||
template <int V> class FFmpegDecoderModule
|
||||
{
|
||||
|
|
|
@ -98,25 +98,45 @@ Push.prototype = {
|
|||
this.createPromise((resolve, reject) => {
|
||||
let callback = new PushSubscriptionCallback(this, resolve, reject);
|
||||
|
||||
if (!options || !options.applicationServerKey) {
|
||||
if (!options || options.applicationServerKey === null) {
|
||||
PushService.subscribe(this._scope, this._principal, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
let appServerKey = options.applicationServerKey;
|
||||
let keyView = new this._window.Uint8Array(ArrayBuffer.isView(appServerKey) ?
|
||||
appServerKey.buffer : appServerKey);
|
||||
let keyView = this._normalizeAppServerKey(options.applicationServerKey);
|
||||
if (keyView.byteLength === 0) {
|
||||
callback._rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
|
||||
return;
|
||||
}
|
||||
PushService.subscribeWithKey(this._scope, this._principal,
|
||||
appServerKey.length, appServerKey,
|
||||
keyView.byteLength, keyView,
|
||||
callback);
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_normalizeAppServerKey: function(appServerKey) {
|
||||
let key;
|
||||
if (typeof appServerKey == "string") {
|
||||
try {
|
||||
key = Cu.cloneInto(ChromeUtils.base64URLDecode(appServerKey, {
|
||||
padding: "reject",
|
||||
}), this._window);
|
||||
} catch (e) {
|
||||
throw new this._window.DOMException(
|
||||
"String contains an invalid character",
|
||||
"InvalidCharacterError"
|
||||
);
|
||||
}
|
||||
} else if (this._window.ArrayBuffer.isView(appServerKey)) {
|
||||
key = appServerKey.buffer;
|
||||
} else {
|
||||
// `appServerKey` is an array buffer.
|
||||
key = appServerKey;
|
||||
}
|
||||
return new this._window.Uint8Array(key);
|
||||
},
|
||||
|
||||
getSubscription: function() {
|
||||
console.debug("getSubscription()", this._scope);
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "mozilla/dom/PushManager.h"
|
||||
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
@ -580,11 +581,10 @@ PushManager::PerformSubscriptionActionFromWorker(SubscriptionAction aAction,
|
|||
|
||||
nsTArray<uint8_t> appServerKey;
|
||||
if (!aOptions.mApplicationServerKey.IsNull()) {
|
||||
const OwningArrayBufferViewOrArrayBuffer& bufferSource =
|
||||
aOptions.mApplicationServerKey.Value();
|
||||
if (!PushUtil::CopyBufferSourceToArray(bufferSource, appServerKey) ||
|
||||
appServerKey.IsEmpty()) {
|
||||
p->MaybeReject(NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
|
||||
nsresult rv = NormalizeAppServerKey(aOptions.mApplicationServerKey.Value(),
|
||||
appServerKey);
|
||||
if (NS_FAILED(rv)) {
|
||||
p->MaybeReject(rv);
|
||||
return p.forget();
|
||||
}
|
||||
}
|
||||
|
@ -596,5 +596,38 @@ PushManager::PerformSubscriptionActionFromWorker(SubscriptionAction aAction,
|
|||
return p.forget();
|
||||
}
|
||||
|
||||
nsresult
|
||||
PushManager::NormalizeAppServerKey(const OwningArrayBufferViewOrArrayBufferOrString& aSource,
|
||||
nsTArray<uint8_t>& aAppServerKey)
|
||||
{
|
||||
if (aSource.IsString()) {
|
||||
NS_ConvertUTF16toUTF8 base64Key(aSource.GetAsString());
|
||||
FallibleTArray<uint8_t> decodedKey;
|
||||
nsresult rv = Base64URLDecode(base64Key,
|
||||
Base64URLDecodePaddingPolicy::Reject,
|
||||
decodedKey);
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
|
||||
}
|
||||
aAppServerKey = decodedKey;
|
||||
} else if (aSource.IsArrayBuffer()) {
|
||||
if (!PushUtil::CopyArrayBufferToArray(aSource.GetAsArrayBuffer(),
|
||||
aAppServerKey)) {
|
||||
return NS_ERROR_DOM_PUSH_INVALID_KEY_ERR;
|
||||
}
|
||||
} else if (aSource.IsArrayBufferView()) {
|
||||
if (!PushUtil::CopyArrayBufferViewToArray(aSource.GetAsArrayBufferView(),
|
||||
aAppServerKey)) {
|
||||
return NS_ERROR_DOM_PUSH_INVALID_KEY_ERR;
|
||||
}
|
||||
} else {
|
||||
MOZ_CRASH("Uninitialized union: expected string, buffer, or view");
|
||||
}
|
||||
if (aAppServerKey.IsEmpty()) {
|
||||
return NS_ERROR_DOM_PUSH_INVALID_KEY_ERR;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -47,6 +47,7 @@ namespace workers {
|
|||
class WorkerPrivate;
|
||||
}
|
||||
|
||||
class OwningArrayBufferViewOrArrayBufferOrString;
|
||||
class Promise;
|
||||
class PushManagerImpl;
|
||||
struct PushSubscriptionOptionsInit;
|
||||
|
@ -104,6 +105,10 @@ public:
|
|||
private:
|
||||
~PushManager();
|
||||
|
||||
nsresult
|
||||
NormalizeAppServerKey(const OwningArrayBufferViewOrArrayBufferOrString& aSource,
|
||||
nsTArray<uint8_t>& aAppServerKey);
|
||||
|
||||
// The following are only set and accessed on the main thread.
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
RefPtr<PushManagerImpl> mImpl;
|
||||
|
|
|
@ -60,34 +60,6 @@ http://creativecommons.org/licenses/publicdomain/
|
|||
pushSubscription = yield registration.pushManager.subscribe();
|
||||
});
|
||||
|
||||
function base64UrlDecode(s) {
|
||||
s = s.replace(/-/g, '+').replace(/_/g, '/');
|
||||
|
||||
// Replace padding if it was stripped by the sender.
|
||||
// See http://tools.ietf.org/html/rfc4648#section-4
|
||||
switch (s.length % 4) {
|
||||
case 0:
|
||||
break; // No pad chars in this case
|
||||
case 2:
|
||||
s += '==';
|
||||
break; // Two pad chars
|
||||
case 3:
|
||||
s += '=';
|
||||
break; // One pad char
|
||||
default:
|
||||
throw new Error('Illegal base64url string!');
|
||||
}
|
||||
|
||||
// With correct padding restored, apply the standard base64 decoder
|
||||
var decoded = atob(s);
|
||||
|
||||
var array = new Uint8Array(new ArrayBuffer(decoded.length));
|
||||
for (var i = 0; i < decoded.length; i++) {
|
||||
array[i] = decoded.charCodeAt(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
add_task(function* compareJSONSubscription() {
|
||||
var json = pushSubscription.toJSON();
|
||||
is(json.endpoint, pushSubscription.endpoint, "Wrong endpoint");
|
||||
|
|
|
@ -195,8 +195,107 @@ http://creativecommons.org/licenses/publicdomain/
|
|||
}
|
||||
});
|
||||
|
||||
add_task(function* validKeyBuffer() {
|
||||
var key = yield generateKey();
|
||||
var pushSubscription = yield registration.pushManager.subscribe({
|
||||
applicationServerKey: key.buffer,
|
||||
});
|
||||
is(pushSubscription.endpoint, "https://example.com/push/3",
|
||||
"Wrong endpoint for subscription created with key buffer");
|
||||
var subscriptionKey = pushSubscription.options.applicationServerKey;
|
||||
isDeeply(new Uint8Array(subscriptionKey), key,
|
||||
"App server key getter should match given key");
|
||||
});
|
||||
|
||||
add_task(function* validKeyBufferInWorker() {
|
||||
var key = yield generateKey();
|
||||
var data = yield sendRequestToWorker({
|
||||
type: "subscribeWithKey",
|
||||
key: key.buffer,
|
||||
});
|
||||
is(data.endpoint, "https://example.com/push/4",
|
||||
"Wrong endpoint for subscription with key buffer created in worker");
|
||||
isDeeply(new Uint8Array(data.key), key,
|
||||
"App server key getter should match given key for subscription created in worker");
|
||||
});
|
||||
|
||||
add_task(function* validKeyString() {
|
||||
var base64Key = "BOp8kf30nj6mKFFSPw_w3JAMS99Bac8zneMJ6B6lmKixUO5XTf4AtdPgYUgWke-XE25JHdcooyLgJML1R57jhKY";
|
||||
var key = base64UrlDecode(base64Key);
|
||||
var pushSubscription = yield registration.pushManager.subscribe({
|
||||
applicationServerKey: base64Key,
|
||||
});
|
||||
is(pushSubscription.endpoint, "https://example.com/push/5",
|
||||
"Wrong endpoint for subscription created with Base64-encoded key");
|
||||
isDeeply(new Uint8Array(pushSubscription.options.applicationServerKey), key,
|
||||
"App server key getter should match Base64-decoded key");
|
||||
});
|
||||
|
||||
add_task(function* validKeyStringInWorker() {
|
||||
var base64Key = "BOp8kf30nj6mKFFSPw_w3JAMS99Bac8zneMJ6B6lmKixUO5XTf4AtdPgYUgWke-XE25JHdcooyLgJML1R57jhKY";
|
||||
var key = base64UrlDecode(base64Key);
|
||||
var data = yield sendRequestToWorker({
|
||||
type: "subscribeWithKey",
|
||||
key: base64Key,
|
||||
});
|
||||
is(data.endpoint, "https://example.com/push/6",
|
||||
"Wrong endpoint for subscription created with Base64-encoded key in worker");
|
||||
isDeeply(new Uint8Array(data.key), key,
|
||||
"App server key getter should match decoded key for subscription created in worker");
|
||||
});
|
||||
|
||||
add_task(function* invalidKeyString() {
|
||||
try {
|
||||
yield registration.pushManager.subscribe({
|
||||
applicationServerKey: "!@#$^&*",
|
||||
});
|
||||
ok(false, "Should reject for invalid Base64-encoded keys");
|
||||
} catch (error) {
|
||||
ok(error instanceof DOMException,
|
||||
"Wrong exception type for invalid Base64-encoded key");
|
||||
is(error.name, "InvalidCharacterError",
|
||||
"Wrong exception name for invalid Base64-encoded key");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* invalidKeyStringInWorker() {
|
||||
var errorInfo = yield sendRequestToWorker({
|
||||
type: "subscribeWithKey",
|
||||
key: "!@#$^&*",
|
||||
});
|
||||
ok(errorInfo.isDOMException,
|
||||
"Wrong exception type in worker for invalid Base64-encoded key");
|
||||
is(errorInfo.name, "InvalidCharacterError",
|
||||
"Wrong exception name in worker for invalid Base64-encoded key");
|
||||
});
|
||||
|
||||
add_task(function* emptyKeyString() {
|
||||
try {
|
||||
yield registration.pushManager.subscribe({
|
||||
applicationServerKey: "",
|
||||
});
|
||||
ok(false, "Should reject for empty key strings");
|
||||
} catch (error) {
|
||||
ok(error instanceof DOMException,
|
||||
"Wrong exception type for empty key string");
|
||||
is(error.name, "InvalidAccessError",
|
||||
"Wrong exception name for empty key string");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* emptyKeyStringInWorker() {
|
||||
var errorInfo = yield sendRequestToWorker({
|
||||
type: "subscribeWithKey",
|
||||
key: "",
|
||||
});
|
||||
ok(errorInfo.isDOMException,
|
||||
"Wrong exception type in worker for empty key string");
|
||||
is(errorInfo.name, "InvalidAccessError",
|
||||
"Wrong exception name in worker for empty key string");
|
||||
});
|
||||
|
||||
add_task(function* unsubscribe() {
|
||||
is(subscriptions, 2, "Wrong subscription count");
|
||||
is(subscriptions, 6, "Wrong subscription count");
|
||||
controlledFrame.remove();
|
||||
});
|
||||
|
||||
|
|
|
@ -243,3 +243,31 @@ function waitForActive(swr) {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
function base64UrlDecode(s) {
|
||||
s = s.replace(/-/g, '+').replace(/_/g, '/');
|
||||
|
||||
// Replace padding if it was stripped by the sender.
|
||||
// See http://tools.ietf.org/html/rfc4648#section-4
|
||||
switch (s.length % 4) {
|
||||
case 0:
|
||||
break; // No pad chars in this case
|
||||
case 2:
|
||||
s += '==';
|
||||
break; // Two pad chars
|
||||
case 3:
|
||||
s += '=';
|
||||
break; // One pad char
|
||||
default:
|
||||
throw new Error('Illegal base64url string!');
|
||||
}
|
||||
|
||||
// With correct padding restored, apply the standard base64 decoder
|
||||
var decoded = atob(s);
|
||||
|
||||
var array = new Uint8Array(new ArrayBuffer(decoded.length));
|
||||
for (var i = 0; i < decoded.length; i++) {
|
||||
array[i] = decoded.charCodeAt(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
|
|
@ -297,7 +297,7 @@ var Observer = {
|
|||
},
|
||||
get_current_test: function(uri) {
|
||||
for (let item in test_servers) {
|
||||
let re = RegExp('https?://'+test_servers[item].host);
|
||||
let re = RegExp('https?://'+test_servers[item].host+'.*\/browser/dom/security/test/hsts/file_testserver.sjs');
|
||||
if (re.test(uri)) {
|
||||
return test_servers[item];
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
dictionary PushSubscriptionOptionsInit {
|
||||
// boolean userVisibleOnly = false;
|
||||
BufferSource? applicationServerKey = null;
|
||||
(BufferSource or DOMString)? applicationServerKey = null;
|
||||
};
|
||||
|
||||
// The main thread JS implementation. Please see comments in
|
||||
|
|
|
@ -113,7 +113,10 @@ ImageOps::DecodeToSurface(nsIInputStream* aInputStream,
|
|||
if (NS_FAILED(rv)) {
|
||||
return nullptr;
|
||||
}
|
||||
sourceBuffer->Complete(NS_OK);
|
||||
// Make sure our sourceBuffer is marked as complete.
|
||||
if (!sourceBuffer->IsComplete()) {
|
||||
sourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
// Create a decoder.
|
||||
DecoderType decoderType =
|
||||
|
|
|
@ -429,30 +429,41 @@ AccessibleCaretManager::UpdateCaretsForSelectionMode(UpdateCaretsHint aHint)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
AccessibleCaretManager::UpdateCaretsForOverlappingTilt()
|
||||
{
|
||||
if (mFirstCaret->IsVisuallyVisible() && mSecondCaret->IsVisuallyVisible()) {
|
||||
if (mFirstCaret->Intersects(*mSecondCaret)) {
|
||||
if (mFirstCaret->LogicalPosition().x <=
|
||||
mSecondCaret->LogicalPosition().x) {
|
||||
mFirstCaret->SetAppearance(Appearance::Left);
|
||||
mSecondCaret->SetAppearance(Appearance::Right);
|
||||
} else {
|
||||
mFirstCaret->SetAppearance(Appearance::Right);
|
||||
mSecondCaret->SetAppearance(Appearance::Left);
|
||||
}
|
||||
} else {
|
||||
mFirstCaret->SetAppearance(Appearance::Normal);
|
||||
mSecondCaret->SetAppearance(Appearance::Normal);
|
||||
}
|
||||
if (!mFirstCaret->IsVisuallyVisible() || !mSecondCaret->IsVisuallyVisible()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mFirstCaret->Intersects(*mSecondCaret)) {
|
||||
mFirstCaret->SetAppearance(Appearance::Normal);
|
||||
mSecondCaret->SetAppearance(Appearance::Normal);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mFirstCaret->LogicalPosition().x <=
|
||||
mSecondCaret->LogicalPosition().x) {
|
||||
mFirstCaret->SetAppearance(Appearance::Left);
|
||||
mSecondCaret->SetAppearance(Appearance::Right);
|
||||
} else {
|
||||
mFirstCaret->SetAppearance(Appearance::Right);
|
||||
mSecondCaret->SetAppearance(Appearance::Left);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
AccessibleCaretManager::UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame,
|
||||
nsIFrame* aEndFrame)
|
||||
{
|
||||
// When a short LTR word in RTL environment is selected, the two carets
|
||||
// tilted inward might be overlapped. Make them tilt outward.
|
||||
if (UpdateCaretsForOverlappingTilt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFirstCaret->IsVisuallyVisible()) {
|
||||
auto startFrameWritingMode = aStartFrame->GetWritingMode();
|
||||
mFirstCaret->SetAppearance(startFrameWritingMode.IsBidiLTR() ?
|
||||
|
|
|
@ -226,7 +226,8 @@ protected:
|
|||
nsIFrame* aEndFrame) const;
|
||||
|
||||
// Check if the two carets is overlapping to become tilt.
|
||||
virtual void UpdateCaretsForOverlappingTilt();
|
||||
// @return true if the two carets become tilt; false, otherwise.
|
||||
virtual bool UpdateCaretsForOverlappingTilt();
|
||||
|
||||
// Make the two carets always tilt.
|
||||
virtual void UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame,
|
||||
|
|
|
@ -92,7 +92,7 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
virtual void UpdateCaretsForOverlappingTilt() override {}
|
||||
virtual bool UpdateCaretsForOverlappingTilt() override { return true; }
|
||||
|
||||
virtual void UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame,
|
||||
nsIFrame* aEndFrame)
|
||||
|
|
|
@ -551,7 +551,7 @@ GetSuitableScale(float aMaxScale, float aMinScale,
|
|||
|
||||
static inline void
|
||||
UpdateMinMaxScale(const nsIFrame* aFrame,
|
||||
const StyleAnimationValue& aValue,
|
||||
const AnimationValue& aValue,
|
||||
gfxSize& aMinScale,
|
||||
gfxSize& aMaxScale)
|
||||
{
|
||||
|
@ -591,7 +591,9 @@ GetMinAndMaxScaleForAnimationProperty(const nsIFrame* aFrame,
|
|||
StyleAnimationValue baseStyle =
|
||||
EffectCompositor::GetBaseStyle(prop.mProperty, aFrame);
|
||||
MOZ_ASSERT(!baseStyle.IsNull(), "The base value should be set");
|
||||
UpdateMinMaxScale(aFrame, baseStyle, aMinScale, aMaxScale);
|
||||
// FIXME: Bug 1311257: We need to get the baseStyle for
|
||||
// RawServoAnimationValue.
|
||||
UpdateMinMaxScale(aFrame, { baseStyle, nullptr }, aMinScale, aMaxScale);
|
||||
}
|
||||
|
||||
for (const AnimationPropertySegment& segment : prop.mSegments) {
|
||||
|
|
|
@ -159,7 +159,7 @@ MakeCSSAngle(const nsCSSValue& aValue)
|
|||
return CSSAngle(aValue.GetAngleValue(), aValue.GetUnit());
|
||||
}
|
||||
|
||||
static void AddTransformFunctions(nsCSSValueList* aList,
|
||||
static void AddTransformFunctions(const nsCSSValueList* aList,
|
||||
nsStyleContext* aContext,
|
||||
nsPresContext* aPresContext,
|
||||
TransformReferenceBox& aRefBox,
|
||||
|
@ -401,6 +401,20 @@ static void AddTransformFunctions(nsCSSValueList* aList,
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
AddTransformFunctions(const nsCSSValueSharedList* aList,
|
||||
const nsIFrame* aFrame,
|
||||
TransformReferenceBox& aRefBox,
|
||||
layers::Animatable& aAnimatable)
|
||||
{
|
||||
MOZ_ASSERT(aList->mHead);
|
||||
AddTransformFunctions(aList->mHead,
|
||||
aFrame->StyleContext(),
|
||||
aFrame->PresContext(),
|
||||
aRefBox,
|
||||
aAnimatable.get_ArrayOfTransformFunction());
|
||||
}
|
||||
|
||||
static TimingFunction
|
||||
ToTimingFunction(const Maybe<ComputedTimingFunction>& aCTF)
|
||||
{
|
||||
|
@ -420,7 +434,7 @@ ToTimingFunction(const Maybe<ComputedTimingFunction>& aCTF)
|
|||
|
||||
static void
|
||||
SetAnimatable(nsCSSPropertyID aProperty,
|
||||
const StyleAnimationValue& aAnimationValue,
|
||||
const AnimationValue& aAnimationValue,
|
||||
nsIFrame* aFrame,
|
||||
TransformReferenceBox& aRefBox,
|
||||
layers::Animatable& aAnimatable)
|
||||
|
@ -434,17 +448,19 @@ SetAnimatable(nsCSSPropertyID aProperty,
|
|||
|
||||
switch (aProperty) {
|
||||
case eCSSProperty_opacity:
|
||||
aAnimatable = aAnimationValue.GetFloatValue();
|
||||
aAnimatable = aAnimationValue.GetOpacity();
|
||||
break;
|
||||
case eCSSProperty_transform: {
|
||||
aAnimatable = InfallibleTArray<TransformFunction>();
|
||||
nsCSSValueSharedList* list =
|
||||
aAnimationValue.GetCSSValueSharedListValue();
|
||||
AddTransformFunctions(list->mHead,
|
||||
aFrame->StyleContext(),
|
||||
aFrame->PresContext(),
|
||||
aRefBox,
|
||||
aAnimatable.get_ArrayOfTransformFunction());
|
||||
if (aAnimationValue.mServo) {
|
||||
RefPtr<nsCSSValueSharedList> list;
|
||||
Servo_AnimationValues_GetTransform(aAnimationValue.mServo, &list);
|
||||
AddTransformFunctions(list, aFrame, aRefBox, aAnimatable);
|
||||
} else {
|
||||
nsCSSValueSharedList* list =
|
||||
aAnimationValue.mGecko.GetCSSValueSharedListValue();
|
||||
AddTransformFunctions(list, aFrame, aRefBox, aAnimatable);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -465,7 +481,9 @@ SetBaseAnimationStyle(nsCSSPropertyID aProperty,
|
|||
MOZ_ASSERT(!baseValue.IsNull(),
|
||||
"The base value should be already there");
|
||||
|
||||
SetAnimatable(aProperty, baseValue, aFrame, aRefBox, aBaseStyle);
|
||||
// FIXME: Bug 1311257: We need to get the baseValue for
|
||||
// RawServoAnimationValue.
|
||||
SetAnimatable(aProperty, { baseValue, nullptr }, aFrame, aRefBox, aBaseStyle);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
@ -66,3 +66,6 @@ fuzzy-if(d2d,1,10000) == sepia-one.html sepia-one-ref.html
|
|||
fuzzy-if(d2d,1,10000) == sepia-over-one.html sepia-over-one-ref.html
|
||||
fuzzy-if(d2d,1,10000) == sepia-percent.html sepia-percent-ref.html
|
||||
== sepia-zero.html sepia-zero-ref.html
|
||||
|
||||
fuzzy(2,125000) == scale-filtered-content-01.html scale-filtered-content-01-ref.html
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Filters: Filtered content should be rendered in device space</title>
|
||||
<style type="text/css">
|
||||
#filtered {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background-color: lime;
|
||||
opacity: 0.5;
|
||||
transform: translate(10px) scale(5);
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
.inner {
|
||||
position: absolute;
|
||||
background-color: blue;
|
||||
width: 10px;
|
||||
height:10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>You should see clear blue rects.</p>
|
||||
<div id="filtered">
|
||||
<div class="inner" style="left:10px; top:10px;"></div>
|
||||
<div class="inner" style="left:50px; top:20px;"></div>
|
||||
<div class="inner" style="left:80px; top:30px;"></div>
|
||||
</div>
|
||||
</body>
|
|
@ -0,0 +1,33 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Filters: Filtered content should be rendered in device space</title>
|
||||
<style type="text/css">
|
||||
#filtered {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background-color: lime;
|
||||
filter: opacity(50%);
|
||||
transform: translate(10px) scale(5);
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
.inner {
|
||||
position: absolute;
|
||||
background-color: blue;
|
||||
width: 10px;
|
||||
height:10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>You should see clear blue rects.</p>
|
||||
<div id="filtered">
|
||||
<div class="inner" style="left:10px; top:10px;"></div>
|
||||
<div class="inner" style="left:50px; top:20px;"></div>
|
||||
<div class="inner" style="left:80px; top:30px;"></div>
|
||||
</div>
|
||||
</body>
|
|
@ -127,6 +127,11 @@ SERVO_BINDING_FUNC(Servo_AnimationValues_Interpolate,
|
|||
SERVO_BINDING_FUNC(Servo_AnimationValues_Uncompute,
|
||||
RawServoDeclarationBlockStrong,
|
||||
RawServoAnimationValueBorrowedListBorrowed value)
|
||||
SERVO_BINDING_FUNC(Servo_AnimationValues_GetOpacity, float,
|
||||
RawServoAnimationValueBorrowed value)
|
||||
SERVO_BINDING_FUNC(Servo_AnimationValues_GetTransform, void,
|
||||
RawServoAnimationValueBorrowed value,
|
||||
RefPtr<nsCSSValueSharedList>* list)
|
||||
|
||||
// Style attribute
|
||||
SERVO_BINDING_FUNC(Servo_ParseStyleAttribute, RawServoDeclarationBlockStrong,
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
#include "mozilla/StyleSetHandleInlines.h"
|
||||
#include "mozilla/Tuple.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsStyleTransformMatrix.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsCOMArray.h"
|
||||
#include "nsIStyleRule.h"
|
||||
|
@ -3458,7 +3457,7 @@ ComputeValuesFromStyleContext(
|
|||
PropertyStyleAnimationValuePair* pair = aValues.AppendElement();
|
||||
pair->mProperty = *p;
|
||||
if (!StyleAnimationValue::ExtractComputedValue(*p, aStyleContext,
|
||||
pair->mValue)) {
|
||||
pair->mValue.mGecko)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -3468,7 +3467,7 @@ ComputeValuesFromStyleContext(
|
|||
PropertyStyleAnimationValuePair* pair = aValues.AppendElement();
|
||||
pair->mProperty = aProperty;
|
||||
return StyleAnimationValue::ExtractComputedValue(aProperty, aStyleContext,
|
||||
pair->mValue);
|
||||
pair->mValue.mGecko);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -3583,7 +3582,7 @@ StyleAnimationValue::ComputeValue(nsCSSPropertyID aProperty,
|
|||
MOZ_ASSERT(values.Length() == 1);
|
||||
MOZ_ASSERT(values[0].mProperty == aProperty);
|
||||
|
||||
aComputedValue = values[0].mValue;
|
||||
aComputedValue = values[0].mValue.mGecko;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -4815,29 +4814,10 @@ StyleAnimationValue::ExtractComputedValue(nsCSSPropertyID aProperty,
|
|||
gfxSize
|
||||
StyleAnimationValue::GetScaleValue(const nsIFrame* aForFrame) const
|
||||
{
|
||||
MOZ_ASSERT(aForFrame);
|
||||
MOZ_ASSERT(GetUnit() == StyleAnimationValue::eUnit_Transform);
|
||||
|
||||
nsCSSValueSharedList* list = GetCSSValueSharedListValue();
|
||||
MOZ_ASSERT(list->mHead);
|
||||
|
||||
RuleNodeCacheConditions dontCare;
|
||||
bool dontCareBool;
|
||||
nsStyleTransformMatrix::TransformReferenceBox refBox(aForFrame);
|
||||
Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(
|
||||
list->mHead,
|
||||
aForFrame->StyleContext(),
|
||||
aForFrame->PresContext(), dontCare, refBox,
|
||||
aForFrame->PresContext()->AppUnitsPerDevPixel(),
|
||||
&dontCareBool);
|
||||
|
||||
Matrix transform2d;
|
||||
bool canDraw2D = transform.CanDraw2D(&transform2d);
|
||||
if (!canDraw2D) {
|
||||
return gfxSize();
|
||||
}
|
||||
|
||||
return ThebesMatrix(transform2d).ScaleFactors(true);
|
||||
return nsStyleTransformMatrix::GetScaleValue(list, aForFrame);
|
||||
}
|
||||
|
||||
StyleAnimationValue::StyleAnimationValue(int32_t aInt, Unit aUnit,
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include "nsCSSProps.h"
|
||||
#include "nsCSSValue.h"
|
||||
#include "nsStyleCoord.h"
|
||||
#include "nsStyleTransformMatrix.h"
|
||||
#include "ServoBindings.h"
|
||||
|
||||
class nsIFrame;
|
||||
class nsStyleContext;
|
||||
|
@ -582,11 +584,39 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
struct AnimationValue
|
||||
{
|
||||
StyleAnimationValue mGecko;
|
||||
RefPtr<RawServoAnimationValue> mServo;
|
||||
|
||||
bool operator==(const AnimationValue& aOther) const {
|
||||
// FIXME: Bug 1337229: add a deep == impl for RawServoAnimationValue.
|
||||
return mGecko == aOther.mGecko && mServo == aOther.mServo;
|
||||
}
|
||||
|
||||
bool IsNull() const { return mGecko.IsNull() && !mServo; }
|
||||
|
||||
float GetOpacity() const {
|
||||
return mServo ? Servo_AnimationValues_GetOpacity(mServo)
|
||||
: mGecko.GetFloatValue();
|
||||
}
|
||||
|
||||
// Returns the scale for mGecko or mServo, which are calculated with
|
||||
// reference to aFrame.
|
||||
gfxSize GetScaleValue(const nsIFrame* aFrame) const {
|
||||
if (mServo) {
|
||||
RefPtr<nsCSSValueSharedList> list;
|
||||
Servo_AnimationValues_GetTransform(mServo, &list);
|
||||
return nsStyleTransformMatrix::GetScaleValue(list, aFrame);
|
||||
}
|
||||
return mGecko.GetScaleValue(aFrame);
|
||||
}
|
||||
};
|
||||
|
||||
struct PropertyStyleAnimationValuePair
|
||||
{
|
||||
nsCSSPropertyID mProperty;
|
||||
StyleAnimationValue mValue;
|
||||
RefPtr<RawServoAnimationValue> mServoValue;
|
||||
AnimationValue mValue;
|
||||
};
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -1259,4 +1259,29 @@ CSSValueArrayTo3DMatrix(nsCSSValue::Array* aArray)
|
|||
return m;
|
||||
}
|
||||
|
||||
gfxSize
|
||||
GetScaleValue(const nsCSSValueSharedList* aList,
|
||||
const nsIFrame* aForFrame)
|
||||
{
|
||||
MOZ_ASSERT(aList && aList->mHead);
|
||||
MOZ_ASSERT(aForFrame);
|
||||
|
||||
RuleNodeCacheConditions dontCare;
|
||||
bool dontCareBool;
|
||||
TransformReferenceBox refBox(aForFrame);
|
||||
Matrix4x4 transform = ReadTransforms(
|
||||
aList->mHead,
|
||||
aForFrame->StyleContext(),
|
||||
aForFrame->PresContext(), dontCare, refBox,
|
||||
aForFrame->PresContext()->AppUnitsPerDevPixel(),
|
||||
&dontCareBool);
|
||||
Matrix transform2d;
|
||||
bool canDraw2D = transform.CanDraw2D(&transform2d);
|
||||
if (!canDraw2D) {
|
||||
return gfxSize();
|
||||
}
|
||||
|
||||
return ThebesMatrix(transform2d).ScaleFactors(true);
|
||||
}
|
||||
|
||||
} // namespace nsStyleTransformMatrix
|
||||
|
|
|
@ -230,6 +230,9 @@ namespace nsStyleTransformMatrix {
|
|||
|
||||
mozilla::gfx::Matrix CSSValueArrayTo2DMatrix(nsCSSValue::Array* aArray);
|
||||
mozilla::gfx::Matrix4x4 CSSValueArrayTo3DMatrix(nsCSSValue::Array* aArray);
|
||||
|
||||
gfxSize GetScaleValue(const nsCSSValueSharedList* aList,
|
||||
const nsIFrame* aForFrame);
|
||||
} // namespace nsStyleTransformMatrix
|
||||
|
||||
#endif
|
||||
|
|
|
@ -120,7 +120,7 @@ ElementPropertyTransition::UpdateStartValueFromReplacedTransition()
|
|||
StyleAnimationValue::UncomputeValue(mProperties[0].mProperty,
|
||||
startValue,
|
||||
cssValue);
|
||||
mProperties[0].mSegments[0].mFromValue = Move(startValue);
|
||||
mProperties[0].mSegments[0].mFromValue.mGecko = Move(startValue);
|
||||
MOZ_ASSERT(uncomputeResult, "UncomputeValue should not fail");
|
||||
MOZ_ASSERT(mKeyframes.Length() == 2,
|
||||
"Transitions should have exactly two animation keyframes");
|
||||
|
@ -989,8 +989,8 @@ nsTransitionManager::ConsiderInitiatingTransition(
|
|||
oldPT->GetAnimation()->PlaybackRate(),
|
||||
oldPT->SpecifiedTiming(),
|
||||
segment.mTimingFunction,
|
||||
segment.mFromValue,
|
||||
segment.mToValue
|
||||
segment.mFromValue.mGecko,
|
||||
segment.mToValue.mGecko
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ struct ElementPropertyTransition : public dom::KeyframeEffectReadOnly
|
|||
NS_WARNING("Failed to generate transition property values");
|
||||
return StyleAnimationValue();
|
||||
}
|
||||
return mProperties[0].mSegments[0].mToValue;
|
||||
return mProperties[0].mSegments[0].mToValue.mGecko;
|
||||
}
|
||||
|
||||
// This is the start value to be used for a check for whether a
|
||||
|
|
|
@ -119,7 +119,7 @@ nsDisplaySVGGeometry::Paint(nsDisplayListBuilder* aBuilder,
|
|||
gfxPoint devPixelOffset =
|
||||
nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
|
||||
|
||||
gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) *
|
||||
gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
|
||||
gfxMatrix::Translation(devPixelOffset);
|
||||
DrawResult result =
|
||||
static_cast<SVGGeometryFrame*>(mFrame)->PaintSVG(*aCtx->ThebesContext(), tm);
|
||||
|
|
|
@ -3115,7 +3115,7 @@ nsDisplaySVGText::Paint(nsDisplayListBuilder* aBuilder,
|
|||
gfxPoint devPixelOffset =
|
||||
nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
|
||||
|
||||
gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) *
|
||||
gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
|
||||
gfxMatrix::Translation(devPixelOffset);
|
||||
|
||||
gfxContext* ctx = aCtx->ThebesContext();
|
||||
|
|
|
@ -39,10 +39,10 @@ nsFilterInstance::GetFilterDescription(nsIContent* aFilteredElement,
|
|||
const gfxRect& aBBox,
|
||||
nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages)
|
||||
{
|
||||
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
||||
gfxMatrix identity;
|
||||
nsFilterInstance instance(nullptr, aFilteredElement, aMetrics,
|
||||
aFilterChain, aFilterInputIsTainted, nullptr,
|
||||
unused, nullptr, nullptr, nullptr, &aBBox);
|
||||
identity, nullptr, nullptr, nullptr, &aBBox);
|
||||
if (!instance.IsInitialized()) {
|
||||
return FilterDescription();
|
||||
}
|
||||
|
@ -70,9 +70,10 @@ nsFilterInstance::PaintFilteredFrame(nsIFrame *aFilteredFrame,
|
|||
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
||||
// Hardcode InputIsTainted to true because we don't want JS to be able to
|
||||
// read the rendered contents of aFilteredFrame.
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
||||
filterChain, /* InputIsTainted */ true, aPaintCallback,
|
||||
aTransform, aDirtyArea, nullptr, nullptr, nullptr);
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
|
||||
*metrics, filterChain, /* InputIsTainted */ true,
|
||||
aPaintCallback, aTransform, aDirtyArea, nullptr,
|
||||
nullptr, nullptr);
|
||||
if (!instance.IsInitialized()) {
|
||||
return DrawResult::BAD_IMAGE;
|
||||
}
|
||||
|
@ -88,14 +89,14 @@ nsFilterInstance::GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
|
|||
return nsRegion();
|
||||
}
|
||||
|
||||
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
||||
gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
|
||||
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
||||
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
||||
// Hardcode InputIsTainted to true because we don't want JS to be able to
|
||||
// read the rendered contents of aFilteredFrame.
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
||||
filterChain, /* InputIsTainted */ true, nullptr, unused,
|
||||
nullptr, &aPreFilterDirtyRegion);
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
|
||||
*metrics, filterChain, /* InputIsTainted */ true,
|
||||
nullptr, tm, nullptr, &aPreFilterDirtyRegion);
|
||||
if (!instance.IsInitialized()) {
|
||||
return nsRegion();
|
||||
}
|
||||
|
@ -110,14 +111,14 @@ nsRegion
|
|||
nsFilterInstance::GetPreFilterNeededArea(nsIFrame *aFilteredFrame,
|
||||
const nsRegion& aPostFilterDirtyRegion)
|
||||
{
|
||||
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
||||
gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
|
||||
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
||||
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
||||
// Hardcode InputIsTainted to true because we don't want JS to be able to
|
||||
// read the rendered contents of aFilteredFrame.
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
||||
filterChain, /* InputIsTainted */ true, nullptr, unused,
|
||||
&aPostFilterDirtyRegion);
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
|
||||
*metrics, filterChain, /* InputIsTainted */ true,
|
||||
nullptr, tm, &aPostFilterDirtyRegion);
|
||||
if (!instance.IsInitialized()) {
|
||||
return nsRect();
|
||||
}
|
||||
|
@ -143,15 +144,15 @@ nsFilterInstance::GetPostFilterBounds(nsIFrame *aFilteredFrame,
|
|||
preFilterRegionPtr = &preFilterRegion;
|
||||
}
|
||||
|
||||
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
||||
gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
|
||||
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
||||
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
||||
// Hardcode InputIsTainted to true because we don't want JS to be able to
|
||||
// read the rendered contents of aFilteredFrame.
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
||||
filterChain, /* InputIsTainted */ true, nullptr, unused,
|
||||
nullptr, preFilterRegionPtr, aPreFilterBounds,
|
||||
aOverrideBBox);
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
|
||||
*metrics, filterChain, /* InputIsTainted */ true,
|
||||
nullptr, tm, nullptr, preFilterRegionPtr,
|
||||
aPreFilterBounds, aOverrideBBox);
|
||||
if (!instance.IsInitialized()) {
|
||||
return nsRect();
|
||||
}
|
||||
|
@ -203,11 +204,6 @@ nsFilterInstance::nsFilterInstance(nsIFrame *aTargetFrame,
|
|||
0.0f, mFilterSpaceToUserSpaceScale.height,
|
||||
0.0f, 0.0f);
|
||||
|
||||
// Only used (so only set) when we paint:
|
||||
if (mPaintCallback) {
|
||||
mFilterSpaceToDeviceSpaceTransform = filterToUserSpace * mPaintTransform;
|
||||
}
|
||||
|
||||
mFilterSpaceToFrameSpaceInCSSPxTransform =
|
||||
filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
|
||||
// mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
|
||||
|
@ -243,22 +239,19 @@ nsFilterInstance::ComputeUserSpaceToFilterSpaceScale()
|
|||
{
|
||||
gfxMatrix canvasTransform;
|
||||
if (mTargetFrame) {
|
||||
canvasTransform = nsSVGUtils::GetCanvasTM(mTargetFrame);
|
||||
if (canvasTransform.IsSingular()) {
|
||||
mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors(true);
|
||||
if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
|
||||
mUserSpaceToFilterSpaceScale.height <= 0.0f) {
|
||||
// Nothing should be rendered.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
} else {
|
||||
mUserSpaceToFilterSpaceScale = gfxSize(1.0, 1.0);
|
||||
}
|
||||
|
||||
mUserSpaceToFilterSpaceScale = canvasTransform.ScaleFactors(true);
|
||||
if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
|
||||
mUserSpaceToFilterSpaceScale.height <= 0.0f) {
|
||||
// Nothing should be rendered.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mFilterSpaceToUserSpaceScale = gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
|
||||
1.0f / mUserSpaceToFilterSpaceScale.height);
|
||||
mFilterSpaceToUserSpaceScale =
|
||||
gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
|
||||
1.0f / mUserSpaceToFilterSpaceScale.height);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -362,8 +355,7 @@ nsFilterInstance::ComputeNeededBoxes()
|
|||
}
|
||||
|
||||
DrawResult
|
||||
nsFilterInstance::BuildSourcePaint(SourceInfo *aSource,
|
||||
DrawTarget* aTargetDT)
|
||||
nsFilterInstance::BuildSourcePaint(SourceInfo *aSource)
|
||||
{
|
||||
MOZ_ASSERT(mTargetFrame);
|
||||
nsIntRect neededRect = aSource->mNeededBounds;
|
||||
|
@ -375,32 +367,22 @@ nsFilterInstance::BuildSourcePaint(SourceInfo *aSource,
|
|||
return DrawResult::TEMPORARY_ERROR;
|
||||
}
|
||||
|
||||
gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform();
|
||||
DebugOnly<bool> invertible = deviceToFilterSpace.Invert();
|
||||
MOZ_ASSERT(invertible,
|
||||
"The returning matix of GetFilterSpaceToDeviceSpaceTransform must"
|
||||
"be an invertible matrix(not a singular one), since we already"
|
||||
"checked it and early return if it's not from the caller side"
|
||||
"(nsFilterInstance::Render)");
|
||||
RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
|
||||
MOZ_ASSERT(ctx); // already checked the draw target above
|
||||
gfxContextAutoSaveRestore saver(ctx);
|
||||
|
||||
if (!mPaintTransform.IsSingular()) {
|
||||
RefPtr<gfxContext> gfx = gfxContext::CreateOrNull(offscreenDT);
|
||||
MOZ_ASSERT(gfx); // already checked the draw target above
|
||||
gfx->Save();
|
||||
gfx->Multiply(mPaintTransform *
|
||||
deviceToFilterSpace *
|
||||
gfxMatrix::Translation(-neededRect.TopLeft()));
|
||||
GeneralPattern pattern;
|
||||
if (aSource == &mFillPaint) {
|
||||
nsSVGUtils::MakeFillPatternFor(mTargetFrame, gfx, &pattern);
|
||||
} else if (aSource == &mStrokePaint) {
|
||||
nsSVGUtils::MakeStrokePatternFor(mTargetFrame, gfx, &pattern);
|
||||
}
|
||||
if (pattern.GetPattern()) {
|
||||
offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))),
|
||||
pattern);
|
||||
}
|
||||
gfx->Restore();
|
||||
ctx->SetMatrix(mPaintTransform *
|
||||
gfxMatrix::Translation(-neededRect.TopLeft()));
|
||||
GeneralPattern pattern;
|
||||
if (aSource == &mFillPaint) {
|
||||
nsSVGUtils::MakeFillPatternFor(mTargetFrame, ctx, &pattern);
|
||||
} else if (aSource == &mStrokePaint) {
|
||||
nsSVGUtils::MakeStrokePatternFor(mTargetFrame, ctx, &pattern);
|
||||
}
|
||||
|
||||
if (pattern.GetPattern()) {
|
||||
offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))),
|
||||
pattern);
|
||||
}
|
||||
|
||||
aSource->mSourceSurface = offscreenDT->Snapshot();
|
||||
|
@ -410,17 +392,17 @@ nsFilterInstance::BuildSourcePaint(SourceInfo *aSource,
|
|||
}
|
||||
|
||||
DrawResult
|
||||
nsFilterInstance::BuildSourcePaints(DrawTarget* aTargetDT)
|
||||
nsFilterInstance::BuildSourcePaints()
|
||||
{
|
||||
if (!mFillPaint.mNeededBounds.IsEmpty()) {
|
||||
DrawResult result = BuildSourcePaint(&mFillPaint, aTargetDT);
|
||||
DrawResult result = BuildSourcePaint(&mFillPaint);
|
||||
if (result != DrawResult::SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mStrokePaint.mNeededBounds.IsEmpty()) {
|
||||
DrawResult result = BuildSourcePaint(&mStrokePaint, aTargetDT);
|
||||
DrawResult result = BuildSourcePaint(&mStrokePaint);
|
||||
if (result != DrawResult::SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
@ -430,7 +412,7 @@ nsFilterInstance::BuildSourcePaints(DrawTarget* aTargetDT)
|
|||
}
|
||||
|
||||
DrawResult
|
||||
nsFilterInstance::BuildSourceImage(DrawTarget* aTargetDT)
|
||||
nsFilterInstance::BuildSourceImage()
|
||||
{
|
||||
MOZ_ASSERT(mTargetFrame);
|
||||
|
||||
|
@ -464,19 +446,13 @@ nsFilterInstance::BuildSourceImage(DrawTarget* aTargetDT)
|
|||
// space to device space and back again). However, that would make the
|
||||
// code more complex while being hard to get right without introducing
|
||||
// subtle bugs, and in practice it probably makes no real difference.)
|
||||
gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform();
|
||||
DebugOnly<bool> invertible = deviceToFilterSpace.Invert();
|
||||
MOZ_ASSERT(invertible,
|
||||
"The returning matix of GetFilterSpaceToDeviceSpaceTransform must"
|
||||
"be an invertible matrix(not a singular one), since we already"
|
||||
"checked it and early return if it's not from the caller side"
|
||||
"(nsFilterInstance::Render)");
|
||||
|
||||
RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
|
||||
MOZ_ASSERT(ctx); // already checked the draw target above
|
||||
ctx->SetMatrix(
|
||||
ctx->CurrentMatrix().Translate(-neededRect.TopLeft()).
|
||||
PreMultiply(deviceToFilterSpace));
|
||||
gfxMatrix devPxToCssPxTM = nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
|
||||
DebugOnly<bool> invertible = devPxToCssPxTM.Invert();
|
||||
MOZ_ASSERT(invertible);
|
||||
ctx->SetMatrix(devPxToCssPxTM * mPaintTransform *
|
||||
gfxMatrix::Translation(-neededRect.TopLeft()));
|
||||
|
||||
DrawResult result =
|
||||
mPaintCallback->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty);
|
||||
|
@ -499,23 +475,27 @@ nsFilterInstance::Render(DrawTarget* aDrawTarget)
|
|||
|
||||
nsIntRect filterRect =
|
||||
mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
|
||||
gfxMatrix ctm = GetFilterSpaceToDeviceSpaceTransform();
|
||||
|
||||
if (filterRect.IsEmpty() || ctm.IsSingular()) {
|
||||
if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
|
||||
return DrawResult::SUCCESS;
|
||||
}
|
||||
|
||||
AutoRestoreTransform autoRestoreTransform(aDrawTarget);
|
||||
Matrix newTM = ToMatrix(ctm).PreTranslate(filterRect.x, filterRect.y) *
|
||||
aDrawTarget->GetTransform();
|
||||
aDrawTarget->SetTransform(newTM);
|
||||
gfxMatrix filterSpaceToUserSpace = mPaintTransform;
|
||||
DebugOnly<bool> invertible = filterSpaceToUserSpace.Invert();
|
||||
MOZ_ASSERT(invertible);
|
||||
filterSpaceToUserSpace *= nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
|
||||
|
||||
aDrawTarget->SetTransform(ToMatrix(filterSpaceToUserSpace) *
|
||||
aDrawTarget->GetTransform() *
|
||||
Matrix::Translation(filterRect.TopLeft()));
|
||||
|
||||
ComputeNeededBoxes();
|
||||
DrawResult result = BuildSourceImage(aDrawTarget);
|
||||
|
||||
DrawResult result = BuildSourceImage();
|
||||
if (result != DrawResult::SUCCESS){
|
||||
return result;
|
||||
}
|
||||
result = BuildSourcePaints(aDrawTarget);
|
||||
result = BuildSourcePaints();
|
||||
if (result != DrawResult::SUCCESS){
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -119,6 +119,7 @@ public:
|
|||
const gfxRect *aOverrideBBox = nullptr,
|
||||
const nsRect *aPreFilterBounds = nullptr);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @param aTargetFrame The frame of the filtered element under consideration,
|
||||
* may be null.
|
||||
|
@ -201,15 +202,6 @@ public:
|
|||
*/
|
||||
nsRect ComputeSourceNeededRect();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the transform from filter space to outer-<svg> device space.
|
||||
*/
|
||||
gfxMatrix GetFilterSpaceToDeviceSpaceTransform() const {
|
||||
return mFilterSpaceToDeviceSpaceTransform;
|
||||
}
|
||||
|
||||
private:
|
||||
struct SourceInfo {
|
||||
// Specifies which parts of the source need to be rendered.
|
||||
// Set by ComputeNeededBoxes().
|
||||
|
@ -228,21 +220,20 @@ private:
|
|||
* Creates a SourceSurface for either the FillPaint or StrokePaint graph
|
||||
* nodes
|
||||
*/
|
||||
DrawResult BuildSourcePaint(SourceInfo *aPrimitive,
|
||||
DrawTarget* aTargetDT);
|
||||
DrawResult BuildSourcePaint(SourceInfo *aPrimitive);
|
||||
|
||||
/**
|
||||
* Creates a SourceSurface for either the FillPaint and StrokePaint graph
|
||||
* nodes, fills its contents and assigns it to mFillPaint.mSourceSurface and
|
||||
* mStrokePaint.mSourceSurface respectively.
|
||||
*/
|
||||
DrawResult BuildSourcePaints(DrawTarget* aTargetDT);
|
||||
DrawResult BuildSourcePaints();
|
||||
|
||||
/**
|
||||
* Creates the SourceSurface for the SourceGraphic graph node, paints its
|
||||
* contents, and assigns it to mSourceGraphic.mSourceSurface.
|
||||
*/
|
||||
DrawResult BuildSourceImage(DrawTarget* aTargetDT);
|
||||
DrawResult BuildSourceImage();
|
||||
|
||||
/**
|
||||
* Build the list of FilterPrimitiveDescriptions that describes the filter's
|
||||
|
@ -339,11 +330,6 @@ private:
|
|||
*/
|
||||
nsIntRect mTargetBBoxInFilterSpace;
|
||||
|
||||
/**
|
||||
* The transform from filter space to outer-<svg> device space.
|
||||
*/
|
||||
gfxMatrix mFilterSpaceToDeviceSpaceTransform;
|
||||
|
||||
/**
|
||||
* Transform rects between filter space and frame space in CSS pixels.
|
||||
*/
|
||||
|
|
|
@ -403,32 +403,30 @@ class RegularFramePaintCallback : public nsSVGFilterPaintCallback
|
|||
public:
|
||||
RegularFramePaintCallback(nsDisplayListBuilder* aBuilder,
|
||||
LayerManager* aManager,
|
||||
const nsPoint& aOffset)
|
||||
const gfxPoint& aUserSpaceToFrameSpaceOffset)
|
||||
: mBuilder(aBuilder), mLayerManager(aManager),
|
||||
mOffset(aOffset) {}
|
||||
mUserSpaceToFrameSpaceOffset(aUserSpaceToFrameSpaceOffset) {}
|
||||
|
||||
virtual DrawResult Paint(gfxContext& aContext, nsIFrame *aTarget,
|
||||
const gfxMatrix& aTransform,
|
||||
const nsIntRect* aDirtyRect) override
|
||||
{
|
||||
BasicLayerManager* basic = mLayerManager->AsBasicLayerManager();
|
||||
RefPtr<gfxContext> oldCtx = basic->GetTarget();
|
||||
basic->SetTarget(&aContext);
|
||||
|
||||
gfxPoint devPixelOffset =
|
||||
nsLayoutUtils::PointToGfxPoint(-mOffset,
|
||||
aTarget->PresContext()->AppUnitsPerDevPixel());
|
||||
|
||||
gfxContextMatrixAutoSaveRestore autoSR(&aContext);
|
||||
aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffset));
|
||||
aContext.SetMatrix(aContext.CurrentMatrix().Translate(-mUserSpaceToFrameSpaceOffset));
|
||||
|
||||
mLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, mBuilder);
|
||||
basic->SetTarget(oldCtx);
|
||||
return DrawResult::SUCCESS;
|
||||
}
|
||||
|
||||
private:
|
||||
nsDisplayListBuilder* mBuilder;
|
||||
LayerManager* mLayerManager;
|
||||
nsPoint mOffset;
|
||||
gfxPoint mUserSpaceToFrameSpaceOffset;
|
||||
};
|
||||
|
||||
typedef nsSVGIntegrationUtils::PaintFramesParams PaintFramesParams;
|
||||
|
@ -449,7 +447,7 @@ PaintMaskSurface(const PaintFramesParams& aParams,
|
|||
|
||||
const nsStyleSVGReset *svgReset = aSC->StyleSVGReset();
|
||||
gfxMatrix cssPxToDevPxMatrix =
|
||||
nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
|
||||
nsSVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
|
||||
|
||||
nsPresContext* presContext = aParams.frame->PresContext();
|
||||
gfxPoint devPixelOffsetToUserSpace =
|
||||
|
@ -548,7 +546,7 @@ CreateAndPaintMaskSurface(const PaintFramesParams& aParams,
|
|||
// Optimization for single SVG mask.
|
||||
if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
|
||||
gfxMatrix cssPxToDevPxMatrix =
|
||||
nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
|
||||
nsSVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
|
||||
paintResult.opacityApplied = true;
|
||||
nsSVGMaskFrame::MaskParams params(&ctx, aParams.frame, cssPxToDevPxMatrix,
|
||||
aOpacity, &paintResult.maskTransform,
|
||||
|
@ -653,29 +651,36 @@ ValidateSVGFrame(nsIFrame* aFrame)
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup transform matrix of a gfx context by a specific frame. Depend on
|
||||
* aClipCtx, this function may clip that context by the visual overflow area
|
||||
* of aFrame.
|
||||
*
|
||||
* @param aFrame is the target frame.
|
||||
* @param aOffsetToBoundingBox returns the offset between the reference frame
|
||||
* and the bounding box of aFrame.
|
||||
* @oaram aOffsetToUserSpace returns the offset between the reference frame and
|
||||
* the user space coordinate of aFrame.
|
||||
*/
|
||||
static void
|
||||
SetupContextMatrix(nsIFrame* aFrame, const PaintFramesParams& aParams,
|
||||
nsPoint& aOffsetToBoundingBox, nsPoint& aOffsetToUserSpace)
|
||||
struct EffectOffsets {
|
||||
// The offset between the reference frame and the bounding box of the
|
||||
// target frame in app unit.
|
||||
nsPoint offsetToBoundingBox;
|
||||
// The offset between the reference frame and the bounding box of the
|
||||
// target frame in device unit.
|
||||
gfxPoint offsetToBoundingBoxInDevPx;
|
||||
// The offset between the reference frame and the bounding box of the
|
||||
// target frame in app unit.
|
||||
nsPoint offsetToUserSpace;
|
||||
// The offset between the reference frame and the bounding box of the
|
||||
// target frame in device unit.
|
||||
gfxPoint offsetToUserSpaceInDevPx;
|
||||
};
|
||||
|
||||
EffectOffsets
|
||||
ComputeEffectOffset(nsIFrame* aFrame, const PaintFramesParams& aParams)
|
||||
{
|
||||
aOffsetToBoundingBox = aParams.builder->ToReferenceFrame(aFrame) -
|
||||
nsSVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
|
||||
EffectOffsets result;
|
||||
|
||||
result.offsetToBoundingBox =
|
||||
aParams.builder->ToReferenceFrame(aFrame) -
|
||||
nsSVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
|
||||
if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
|
||||
/* Snap the offset if the reference frame is not a SVG frame,
|
||||
* since other frames will be snapped to pixel when rendering. */
|
||||
aOffsetToBoundingBox = nsPoint(
|
||||
aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.x),
|
||||
aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.y));
|
||||
result.offsetToBoundingBox =
|
||||
nsPoint(
|
||||
aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(result.offsetToBoundingBox.x),
|
||||
aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(result.offsetToBoundingBox.y));
|
||||
}
|
||||
|
||||
// After applying only "aOffsetToBoundingBox", aParams.ctx would have its
|
||||
|
@ -689,25 +694,43 @@ SetupContextMatrix(nsIFrame* aFrame, const PaintFramesParams& aParams,
|
|||
// if we want aParams.ctx to be in user space, we first need to subtract the
|
||||
// frame's position so that SVG painting can later add it again and the
|
||||
// frame is painted in the right place.
|
||||
|
||||
gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
|
||||
nsPoint toUserSpace =
|
||||
nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
|
||||
nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
|
||||
|
||||
aOffsetToUserSpace = aOffsetToBoundingBox - toUserSpace;
|
||||
result.offsetToUserSpace = result.offsetToBoundingBox - toUserSpace;
|
||||
|
||||
#ifdef DEBUG
|
||||
bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
|
||||
NS_ASSERTION(hasSVGLayout || aOffsetToBoundingBox == aOffsetToUserSpace,
|
||||
NS_ASSERTION(hasSVGLayout ||
|
||||
result.offsetToBoundingBox == result.offsetToUserSpace,
|
||||
"For non-SVG frames there shouldn't be any additional offset");
|
||||
#endif
|
||||
|
||||
gfxPoint devPixelOffsetToUserSpace =
|
||||
nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace,
|
||||
result.offsetToUserSpaceInDevPx =
|
||||
nsLayoutUtils::PointToGfxPoint(result.offsetToUserSpace,
|
||||
aFrame->PresContext()->AppUnitsPerDevPixel());
|
||||
gfxContext& context = aParams.ctx;
|
||||
context.SetMatrix(context.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
|
||||
result.offsetToBoundingBoxInDevPx =
|
||||
nsLayoutUtils::PointToGfxPoint(result.offsetToBoundingBox,
|
||||
aFrame->PresContext()->AppUnitsPerDevPixel());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup transform matrix of a gfx context by a specific frame. Move the
|
||||
* origin of aParams.ctx to the user space of aFrame.
|
||||
*/
|
||||
static EffectOffsets
|
||||
MoveContextOriginToUserSpace(nsIFrame* aFrame, const PaintFramesParams& aParams)
|
||||
{
|
||||
EffectOffsets offset = ComputeEffectOffset(aFrame, aParams);
|
||||
|
||||
aParams.ctx.SetMatrix(
|
||||
aParams.ctx.CurrentMatrix().Translate(offset.offsetToUserSpaceInDevPx));
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -798,16 +821,13 @@ nsSVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams)
|
|||
}
|
||||
|
||||
gfxContextMatrixAutoSaveRestore matSR;
|
||||
nsPoint offsetToBoundingBox;
|
||||
nsPoint offsetToUserSpace;
|
||||
|
||||
// Paint clip-path-basic-shape onto ctx
|
||||
gfxContextAutoSaveRestore basicShapeSR;
|
||||
if (maskUsage.shouldApplyBasicShape) {
|
||||
matSR.SetContext(&ctx);
|
||||
|
||||
SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
|
||||
offsetToUserSpace);
|
||||
MoveContextOriginToUserSpace(firstFrame, aParams);
|
||||
|
||||
basicShapeSR.SetContext(&ctx);
|
||||
nsCSSClipPathInstance::ApplyBasicShapeClip(ctx, frame);
|
||||
|
@ -826,12 +846,12 @@ nsSVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams)
|
|||
matSR.Restore();
|
||||
matSR.SetContext(&ctx);
|
||||
|
||||
SetupContextMatrix(frame, aParams, offsetToBoundingBox,
|
||||
offsetToUserSpace);
|
||||
EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
|
||||
result = PaintMaskSurface(aParams, maskTarget,
|
||||
shouldPushOpacity ? 1.0 : maskUsage.opacity,
|
||||
firstFrame->StyleContext(), maskFrames,
|
||||
ctx.CurrentMatrix(), offsetToUserSpace);
|
||||
ctx.CurrentMatrix(),
|
||||
offsets.offsetToUserSpace);
|
||||
if (result != DrawResult::SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
@ -842,10 +862,9 @@ nsSVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams)
|
|||
matSR.Restore();
|
||||
matSR.SetContext(&ctx);
|
||||
|
||||
SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
|
||||
offsetToUserSpace);
|
||||
MoveContextOriginToUserSpace(firstFrame, aParams);
|
||||
Matrix clipMaskTransform;
|
||||
gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame);
|
||||
gfxMatrix cssPxToDevPxMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
|
||||
|
||||
nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame();
|
||||
RefPtr<SourceSurface> maskSurface =
|
||||
|
@ -905,12 +924,9 @@ nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams)
|
|||
|
||||
nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame();
|
||||
|
||||
gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame);
|
||||
gfxMatrix cssPxToDevPxMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
|
||||
nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
|
||||
|
||||
nsPoint offsetToBoundingBox;
|
||||
nsPoint offsetToUserSpace;
|
||||
|
||||
bool shouldGenerateMask = (maskUsage.opacity != 1.0f ||
|
||||
maskUsage.shouldGenerateClipMaskLayer ||
|
||||
maskUsage.shouldGenerateMaskLayer);
|
||||
|
@ -931,12 +947,11 @@ nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams)
|
|||
// For css-mask, we want to generate a mask for each continuation frame,
|
||||
// so we setup context matrix by the position of the current frame,
|
||||
// instead of the first continuation frame.
|
||||
SetupContextMatrix(frame, aParams, offsetToBoundingBox,
|
||||
offsetToUserSpace);
|
||||
EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
|
||||
MaskPaintResult paintResult =
|
||||
CreateAndPaintMaskSurface(aParams, maskUsage.opacity,
|
||||
firstFrame->StyleContext(),
|
||||
maskFrames, offsetToUserSpace);
|
||||
maskFrames, offsets.offsetToUserSpace);
|
||||
|
||||
if (paintResult.transparentBlackMask) {
|
||||
return paintResult.result;
|
||||
|
@ -956,8 +971,7 @@ nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams)
|
|||
matSR.Restore();
|
||||
matSR.SetContext(&context);
|
||||
|
||||
SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
|
||||
offsetToUserSpace);
|
||||
MoveContextOriginToUserSpace(firstFrame, aParams);
|
||||
Matrix clipMaskTransform;
|
||||
DrawResult clipMaskResult;
|
||||
RefPtr<SourceSurface> clipMaskSurface;
|
||||
|
@ -984,8 +998,7 @@ nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams)
|
|||
MOZ_ASSERT(maskUsage.opacity != 1.0f);
|
||||
|
||||
matSR.SetContext(&context);
|
||||
SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
|
||||
offsetToUserSpace);
|
||||
MoveContextOriginToUserSpace(firstFrame, aParams);
|
||||
shouldPushMask = true;
|
||||
}
|
||||
|
||||
|
@ -1011,8 +1024,7 @@ nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams)
|
|||
if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
|
||||
gfxContextMatrixAutoSaveRestore matSR(&context);
|
||||
|
||||
SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
|
||||
offsetToUserSpace);
|
||||
MoveContextOriginToUserSpace(firstFrame, aParams);
|
||||
|
||||
MOZ_ASSERT(!maskUsage.shouldApplyClipPath ||
|
||||
!maskUsage.shouldApplyBasicShape);
|
||||
|
@ -1098,12 +1110,9 @@ nsSVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams)
|
|||
}
|
||||
|
||||
gfxContext& context = aParams.ctx;
|
||||
nsPoint offsetToBoundingBox;
|
||||
nsPoint offsetToUserSpace;
|
||||
|
||||
gfxContextAutoSaveRestore autoSR(&context);
|
||||
SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
|
||||
offsetToUserSpace);
|
||||
EffectOffsets offsets = MoveContextOriginToUserSpace(firstFrame, aParams);
|
||||
|
||||
if (opacity != 1.0f) {
|
||||
context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity,
|
||||
|
@ -1112,9 +1121,14 @@ nsSVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams)
|
|||
|
||||
/* Paint the child and apply filters */
|
||||
RegularFramePaintCallback callback(aParams.builder, aParams.layerManager,
|
||||
offsetToUserSpace);
|
||||
nsRegion dirtyRegion = aParams.dirtyRect - offsetToBoundingBox;
|
||||
gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(frame);
|
||||
offsets.offsetToUserSpaceInDevPx);
|
||||
nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
|
||||
gfxSize scaleFactors = context.CurrentMatrix().ScaleFactors(true);
|
||||
gfxMatrix scaleMatrix(scaleFactors.width, 0.0f,
|
||||
0.0f, scaleFactors.height,
|
||||
0.0f, 0.0f);
|
||||
gfxMatrix tm =
|
||||
scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
|
||||
DrawResult result =
|
||||
nsFilterInstance::PaintFilteredFrame(frame, context.GetDrawTarget(),
|
||||
tm, &callback, &dirtyRegion);
|
||||
|
@ -1126,18 +1140,6 @@ nsSVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams)
|
|||
return result;
|
||||
}
|
||||
|
||||
gfxMatrix
|
||||
nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame)
|
||||
{
|
||||
int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel();
|
||||
float devPxPerCSSPx =
|
||||
1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel);
|
||||
|
||||
return gfxMatrix(devPxPerCSSPx, 0.0,
|
||||
0.0, devPxPerCSSPx,
|
||||
0.0, 0.0);
|
||||
}
|
||||
|
||||
class PaintFrameCallback : public gfxDrawingCallback {
|
||||
public:
|
||||
PaintFrameCallback(nsIFrame* aFrame,
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче