Merge autoland to central, a=merge

MozReview-Commit-ID: 47exp5fFJe1
This commit is contained in:
Wes Kocher 2017-02-08 15:48:05 -08:00
Родитель 166c51d181 2880f6ee5a
Коммит 008683d974
319 изменённых файлов: 7373 добавлений и 4407 удалений

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

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

3
config/external/ffi/moz.build поставляемый
Просмотреть файл

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

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше