--- a/accessible/xpcom/xpcAccessibleValue.cpp
+++ b/accessible/xpcom/xpcAccessibleValue.cpp
@@ -71,17 +71,17 @@ xpcAccessibleValue::GetCurrentValue(doub
if (Intl().IsAccessible() && Intl().AsAccessible()->IsDefunct())
return NS_ERROR_FAILURE;
double value;
if (Intl().IsAccessible()) {
value = Intl().AsAccessible()->CurValue();
} else {
- value = Intl().AsProxy()->MinValue();
+ value = Intl().AsProxy()->CurValue();
}
if (!IsNaN(value))
*aValue = value;
return NS_OK;
}
@@ -110,17 +110,17 @@ xpcAccessibleValue::GetMinimumIncrement(
*aValue = 0;
if (Intl().IsNull())
return NS_ERROR_FAILURE;
if (Intl().IsAccessible() && Intl().AsAccessible()->IsDefunct())
return NS_ERROR_FAILURE;
- double value = Intl().AsAccessible()->Step();
+ double value;
if (Intl().IsAccessible()) {
value = Intl().AsAccessible()->Step();
} else {
value = Intl().AsProxy()->Step();
}
if (!IsNaN(value))
*aValue = value;
--- a/b2g/app/nsBrowserApp.cpp
+++ b/b2g/app/nsBrowserApp.cpp
@@ -45,17 +45,17 @@
#include "mozilla/WindowsDllBlocklist.h"
static void Output(const char *fmt, ... )
{
va_list ap;
va_start(ap, fmt);
#if defined(XP_WIN) && !MOZ_WINCONSOLE
- char16_t msg[2048];
+ wchar_t msg[2048];
_vsnwprintf(msg, sizeof(msg)/sizeof(msg[0]), NS_ConvertUTF8toUTF16(fmt).get(), ap);
MessageBoxW(nullptr, msg, L"XULRunner", MB_OK | MB_ICONERROR);
#else
vfprintf(stderr, fmt, ap);
#endif
va_end(ap);
}
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6642,16 +6642,24 @@ var gIdentityHandler = {
get _identityIcon () {
delete this._identityIcon;
return this._identityIcon = document.getElementById("identity-icon");
},
get _permissionList () {
delete this._permissionList;
return this._permissionList = document.getElementById("identity-popup-permission-list");
},
+ get _permissionAnchors () {
+ delete this._permissionAnchors;
+ let permissionAnchors = {};
+ for (let anchor of document.getElementById("blocked-permissions-container").children) {
+ permissionAnchors[anchor.getAttribute("data-permission-id")] = anchor;
+ }
+ return this._permissionAnchors = permissionAnchors;
+ },
/**
* Handler for mouseclicks on the "More Information" button in the
* "identity-popup" panel.
*/
handleMoreInfoClick : function(event) {
displaySecurityInfo();
event.stopPropagation();
@@ -6899,17 +6907,42 @@ var gIdentityHandler = {
}
if (this._isCertUserOverridden) {
this._identityBox.classList.add("certUserOverridden");
// Cert is trusted because of a security exception, verifier is a special string.
tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
}
- if (SitePermissions.hasGrantedPermissions(this._uri)) {
+ let permissionAnchors = this._permissionAnchors;
+
+ // hide all permission icons
+ for (let icon of Object.values(permissionAnchors)) {
+ icon.removeAttribute("showing");
+ }
+
+ // keeps track if we should show an indicator that there are active permissions
+ let hasGrantedPermissions = false;
+
+ // show permission icons
+ for (let permission of SitePermissions.getAllByURI(this._uri)) {
+ if (permission.state === SitePermissions.BLOCK) {
+
+ let icon = permissionAnchors[permission.id];
+ if (icon) {
+ icon.setAttribute("showing", "true");
+ }
+
+ } else if (permission.state === SitePermissions.ALLOW ||
+ permission.state === SitePermissions.SESSION) {
+ hasGrantedPermissions = true;
+ }
+ }
+
+ if (hasGrantedPermissions) {
this._identityBox.classList.add("grantedPermissions");
}
// Push the appropriate strings out to the UI
this._identityBox.tooltipText = tooltip;
this._identityIcon.tooltipText = gNavigatorBundle.getString("identity.icon.tooltip");
this._identityIconLabel.value = icon_label;
this._identityIconCountryLabel.value = icon_country_label;
@@ -7219,17 +7252,17 @@ var gIdentityHandler = {
},
updateSitePermissions: function () {
while (this._permissionList.hasChildNodes())
this._permissionList.removeChild(this._permissionList.lastChild);
let uri = gBrowser.currentURI;
- for (let permission of SitePermissions.getPermissionsByURI(uri)) {
+ for (let permission of SitePermissions.getPermissionDetailsByURI(uri)) {
let item = this._createPermissionItem(permission);
this._permissionList.appendChild(item);
}
},
setPermission: function (aPermission, aState) {
if (aState == SitePermissions.getDefault(aPermission))
SitePermissions.remove(gBrowser.currentURI, aPermission);
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -720,16 +720,32 @@
align="center"
aria-label="&urlbar.viewSiteInfo.label;"
onclick="gIdentityHandler.handleIdentityButtonEvent(event);"
onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);"
ondragstart="gIdentityHandler.onDragStart(event);">
<image id="identity-icon"
consumeanchor="identity-box"
onclick="PageProxyClickHandler(event);"/>
+ <box id="blocked-permissions-container" align="center">
+ <image data-permission-id="geo" class="notification-anchor-icon geo-icon blocked" role="button"
+ aria-label="&urlbar.geolocationNotificationAnchor.label;"/>
+ <image data-permission-id="desktop-notification" class="notification-anchor-icon desktop-notification-icon blocked" role="button"
+ aria-label="&urlbar.webNotsNotificationAnchor3.label;"/>
+ <image data-permission-id="camera" class="notification-anchor-icon camera-icon blocked" role="button"
+ aria-label="&urlbar.webRTCShareDevicesNotificationAnchor.label;"/>
+ <image data-permission-id="indexedDB" class="notification-anchor-icon indexedDB-icon blocked" role="button"
+ aria-label="&urlbar.indexedDBNotificationAnchor.label;"/>
+ <image data-permission-id="microphone" class="notification-anchor-icon microphone-icon blocked" role="button"
+ aria-label="&urlbar.webRTCShareMicrophoneNotificationAnchor.label;"/>
+ <image data-permission-id="screen" class="notification-anchor-icon screen-icon blocked" role="button"
+ aria-label="&urlbar.webRTCShareScreenNotificationAnchor.label;"/>
+ <image data-permission-id="pointerLock" class="notification-anchor-icon pointerLock-icon blocked" role="button"
+ aria-label="&urlbar.pointerLockNotificationAnchor.label;"/>
+ </box>
<box id="notification-popup-box"
hidden="true"
tooltiptext=""
onmouseover="document.getElementById('identity-icon').classList.add('no-hover');"
onmouseout="document.getElementById('identity-icon').classList.remove('no-hover');"
align="center">
<image id="default-notification-icon" class="notification-anchor-icon" role="button"
aria-label="&urlbar.defaultNotificationAnchor.label;"/>
--- a/browser/base/content/test/general/browser_permissions.js
+++ b/browser/base/content/test/general/browser_permissions.js
@@ -6,16 +6,17 @@ var {classes: Cc, interfaces: Ci, utils:
const PERMISSIONS_PAGE = "http://example.com/browser/browser/base/content/test/general/permissions.html";
var {SitePermissions} = Cu.import("resource:///modules/SitePermissions.jsm", {});
registerCleanupFunction(function() {
SitePermissions.remove(gBrowser.currentURI, "install");
SitePermissions.remove(gBrowser.currentURI, "cookie");
SitePermissions.remove(gBrowser.currentURI, "geo");
SitePermissions.remove(gBrowser.currentURI, "camera");
+ SitePermissions.remove(gBrowser.currentURI, "microphone");
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
});
add_task(function* testMainViewVisible() {
let {gIdentityHandler} = gBrowser.ownerGlobal;
@@ -59,25 +60,53 @@ add_task(function* testMainViewVisible()
add_task(function* testIdentityIcon() {
let {gIdentityHandler} = gBrowser.ownerGlobal;
let tab = gBrowser.selectedTab = gBrowser.addTab();
yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
gIdentityHandler.setPermission("geo", SitePermissions.ALLOW);
ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
- "identity-box signals granted permssions");
+ "identity-box signals granted permissions");
gIdentityHandler.setPermission("geo", SitePermissions.getDefault("geo"));
ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
- "identity-box doesn't signal granted permssions");
+ "identity-box doesn't signal granted permissions");
gIdentityHandler.setPermission("camera", SitePermissions.BLOCK);
ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
- "identity-box doesn't signal granted permssions");
+ "identity-box doesn't signal granted permissions");
gIdentityHandler.setPermission("cookie", SitePermissions.SESSION);
ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
- "identity-box signals granted permssions");
+ "identity-box signals granted permissions");
});
+
+add_task(function* testPermissionIcons() {
+ let {gIdentityHandler} = gBrowser.ownerGlobal;
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+
+ gIdentityHandler.setPermission("camera", SitePermissions.ALLOW);
+ gIdentityHandler.setPermission("geo", SitePermissions.BLOCK);
+ gIdentityHandler.setPermission("microphone", SitePermissions.SESSION);
+
+ let geoIcon = gIdentityHandler._identityBox.querySelector("[data-permission-id='geo']");
+ ok(geoIcon.hasAttribute("showing"), "blocked permission icon is shown");
+ ok(geoIcon.classList.contains("blocked"),
+ "blocked permission icon is shown as blocked");
+
+ let cameraIcon = gIdentityHandler._identityBox.querySelector("[data-permission-id='camera']");
+ ok(!cameraIcon.hasAttribute("showing"),
+ "allowed permission icon is not shown");
+
+ let microphoneIcon = gIdentityHandler._identityBox.querySelector("[data-permission-id='microphone']");
+ ok(!microphoneIcon.hasAttribute("showing"),
+ "allowed permission icon is not shown");
+
+ gIdentityHandler.setPermission("geo", SitePermissions.getDefault("geo"));
+
+ ok(!geoIcon.hasAttribute("showing"),
+ "blocked permission icon is not shown after reset");
+});
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -124,28 +124,33 @@ These should match what Safari and other
fullscreenWarning.afterDomain.label): these two strings are used
respectively before and after the domain requiring fullscreen.
Localizers can use one of them, or both, to better adapt this
sentence to their language. -->
<!ENTITY fullscreenWarning.beforeDomain.label "">
<!ENTITY fullscreenWarning.afterDomain.label "is now full screen">
<!ENTITY fullscreenWarning.generic.label "This document is now full screen">
-<!ENTITY pointerlockWarning.beforeDomain.label "">
-<!ENTITY pointerlockWarning.afterDomain.label "has control of your pointer. Press Esc to take back control.">
-<!ENTITY pointerlockWarning.generic.label "This document has control of your pointer. Press Esc to take back control">
-
<!-- LOCALIZATION NOTE (exitDOMFullscreen.button,
exitDOMFullscreenMac.button): the "escape" button on PC keyboards
is uppercase, while on Mac keyboards it is lowercase -->
<!ENTITY exitDOMFullscreen.button "Exit Full Screen (Esc)">
<!ENTITY exitDOMFullscreenMac.button "Exit Full Screen (esc)">
<!ENTITY leaveDOMFullScreen.label "Exit Full Screen">
<!ENTITY leaveDOMFullScreen.accesskey "u">
+<!-- LOCALIZATION NOTE (pointerlockWarning.beforeDomain.label,
+ pointerlockWarning.afterDomain.label): these two strings are used
+ respectively before and after the domain requiring pointerlock.
+ Localizers can use one of them, or both, to better adapt this
+ sentence to their language. -->
+<!ENTITY pointerlockWarning.beforeDomain.label "">
+<!ENTITY pointerlockWarning.afterDomain.label "has control of your pointer. Press Esc to take back control.">
+<!ENTITY pointerlockWarning.generic.label "This document has control of your pointer. Press Esc to take back control.">
+
<!ENTITY closeWindow.label "Close Window">
<!ENTITY closeWindow.accesskey "d">
<!ENTITY bookmarksMenu.label "Bookmarks">
<!ENTITY bookmarksMenu.accesskey "B">
<!ENTITY bookmarkThisPageCmd.label "Bookmark This Page">
<!ENTITY editThisBookmarkCmd.label "Edit This Bookmark">
<!ENTITY bookmarkThisPageCmd.commandkey "d">
--- a/browser/modules/ReaderParent.jsm
+++ b/browser/modules/ReaderParent.jsm
@@ -40,17 +40,20 @@ var ReaderParent = {
case "Reader:ArticleGet":
this._getArticle(message.data.url, message.target).then((article) => {
// Make sure the target browser is still alive before trying to send data back.
if (message.target.messageManager) {
message.target.messageManager.sendAsyncMessage("Reader:ArticleData", { article: article });
}
}, e => {
if (e && e.newURL) {
- message.target.loadURI("about:reader?url=" + encodeURIComponent(e.newURL));
+ // Make sure the target browser is still alive before trying to send data back.
+ if (message.target.messageManager) {
+ message.target.messageManager.sendAsyncMessage("Reader:ArticleData", { newURL: e.newURL });
+ }
}
});
break;
case "Reader:FaviconRequest": {
if (message.target.messageManager) {
let faviconUrl = PlacesUtils.promiseFaviconLinkUrl(message.data.url);
faviconUrl.then(function onResolution(favicon) {
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -11,73 +11,72 @@ var gStringBundle =
this.SitePermissions = {
UNKNOWN: Services.perms.UNKNOWN_ACTION,
ALLOW: Services.perms.ALLOW_ACTION,
BLOCK: Services.perms.DENY_ACTION,
SESSION: Components.interfaces.nsICookiePermission.ACCESS_SESSION,
+ /* Returns all custom permissions for a given URI, the return
+ * type is a list of objects with the keys:
+ * - id: the permissionId of the permission
+ * - state: a constant representing the current permission state
+ * (e.g. SitePermissions.ALLOW)
+ *
+ * To receive a more detailed, albeit less performant listing see
+ * SitePermissions.getPermissionDetailsByURI().
+ */
+ getAllByURI: function (aURI) {
+ let result = [];
+ if (!this.isSupportedURI(aURI)) {
+ return result;
+ }
+
+ let permissions = Services.perms.getAllForURI(aURI);
+ while (permissions.hasMoreElements()) {
+ let permission = permissions.getNext();
+
+ // filter out unknown permissions
+ if (gPermissionObject[permission.type]) {
+ result.push({
+ id: permission.type,
+ state: permission.capability,
+ });
+ }
+ }
+
+ return result;
+ },
+
/* Returns a list of objects representing all permissions that are currently
* set for the given URI. Each object contains the following keys:
* - id: the permissionID of the permission
* - label: the localized label
* - state: a constant representing the current permission state
* (e.g. SitePermissions.ALLOW)
* - availableStates: an array of all available states for that permission,
* represented as objects with the keys:
* - id: the state constant
* - label: the translated label of that state
*/
- getPermissionsByURI: function (aURI) {
- if (!this.isSupportedURI(aURI)) {
- return [];
- }
-
+ getPermissionDetailsByURI: function (aURI) {
let permissions = [];
- for (let permission of kPermissionIDs) {
- let state = this.get(aURI, permission);
- if (state === this.UNKNOWN) {
- continue;
- }
+ for (let {state, id} of this.getAllByURI(aURI)) {
+ let availableStates = this.getAvailableStates(id).map( state => {
+ return { id: state, label: this.getStateLabel(id, state) };
+ });
+ let label = this.getPermissionLabel(id);
- let availableStates = this.getAvailableStates(permission).map( state => {
- return { id: state, label: this.getStateLabel(permission, state) };
- });
- let label = this.getPermissionLabel(permission);
-
- permissions.push({
- id: permission,
- label: label,
- state: state,
- availableStates: availableStates,
- });
+ permissions.push({id, label, state, availableStates});
}
return permissions;
},
- /* Returns a boolean indicating whether there are any granted
- * (meaning allowed or session-allowed) permissions for the given URI.
- * Will return false for invalid URIs (such as file:// URLs).
- */
- hasGrantedPermissions: function (aURI) {
- if (!this.isSupportedURI(aURI)) {
- return false;
- }
-
- for (let permission of kPermissionIDs) {
- let state = this.get(aURI, permission);
- if (state === this.ALLOW || state === this.SESSION) {
- return true;
- }
- }
- return false;
- },
-
/* Checks whether a UI for managing permissions should be exposed for a given
* URI. This excludes file URIs, for instance, as they don't have a host,
* even though nsIPermissionManager can still handle them.
*/
isSupportedURI: function (aURI) {
return aURI.schemeIs("http") || aURI.schemeIs("https");
},
--- a/browser/modules/test/xpcshell/test_SitePermissions.js
+++ b/browser/modules/test/xpcshell/test_SitePermissions.js
@@ -8,84 +8,79 @@ Components.utils.import("resource://gre/
add_task(function* testPermissionsListing() {
Assert.deepEqual(SitePermissions.listPermissions().sort(),
["camera","cookie","desktop-notification","geo","image",
"indexedDB","install","microphone","popup"],
"Correct list of all permissions");
});
-add_task(function* testHasGrantedPermissions() {
- // check that it returns false on an invalid URI
- // like a file URI, which doesn't support site permissions
- let wrongURI = Services.io.newURI("file:///example.js", null, null)
- Assert.equal(SitePermissions.hasGrantedPermissions(wrongURI), false);
-
- let uri = Services.io.newURI("https://example.com", null, null)
- Assert.equal(SitePermissions.hasGrantedPermissions(uri), false);
-
- // check that ALLOW states return true
- SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
- Assert.equal(SitePermissions.hasGrantedPermissions(uri), true);
-
- // removing the ALLOW state should revert to false
- SitePermissions.remove(uri, "camera");
- Assert.equal(SitePermissions.hasGrantedPermissions(uri), false);
-
- // check that SESSION states return true
- SitePermissions.set(uri, "microphone", SitePermissions.SESSION);
- Assert.equal(SitePermissions.hasGrantedPermissions(uri), true);
-
- // removing the SESSION state should revert to false
- SitePermissions.remove(uri, "microphone");
- Assert.equal(SitePermissions.hasGrantedPermissions(uri), false);
-
- // check that a combination of ALLOW and BLOCK states returns true
- SitePermissions.set(uri, "geo", SitePermissions.ALLOW);
- Assert.equal(SitePermissions.hasGrantedPermissions(uri), true);
-
- // check that a combination of SESSION and BLOCK states returns true
- SitePermissions.set(uri, "geo", SitePermissions.SESSION);
- Assert.equal(SitePermissions.hasGrantedPermissions(uri), true);
-
- // check that only BLOCK states will not return true
- SitePermissions.remove(uri, "geo");
- Assert.equal(SitePermissions.hasGrantedPermissions(uri), false);
-
-});
-
-add_task(function* testGetPermissionsByURI() {
+add_task(function* testGetAllByURI() {
// check that it returns an empty array on an invalid URI
// like a file URI, which doesn't support site permissions
let wrongURI = Services.io.newURI("file:///example.js", null, null)
- Assert.deepEqual(SitePermissions.getPermissionsByURI(wrongURI), []);
+ Assert.deepEqual(SitePermissions.getAllByURI(wrongURI), []);
+
+ let uri = Services.io.newURI("https://example.com", null, null)
+ Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
+
+ SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
+ Assert.deepEqual(SitePermissions.getAllByURI(uri), [
+ { id: "camera", state: SitePermissions.ALLOW }
+ ]);
+
+ SitePermissions.set(uri, "microphone", SitePermissions.SESSION);
+ SitePermissions.set(uri, "desktop-notification", SitePermissions.BLOCK);
+
+ Assert.deepEqual(SitePermissions.getAllByURI(uri), [
+ { id: "camera", state: SitePermissions.ALLOW },
+ { id: "microphone", state: SitePermissions.SESSION },
+ { id: "desktop-notification", state: SitePermissions.BLOCK }
+ ]);
+
+ SitePermissions.remove(uri, "microphone");
+ Assert.deepEqual(SitePermissions.getAllByURI(uri), [
+ { id: "camera", state: SitePermissions.ALLOW },
+ { id: "desktop-notification", state: SitePermissions.BLOCK }
+ ]);
+
+ SitePermissions.remove(uri, "camera");
+ SitePermissions.remove(uri, "desktop-notification");
+ Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
+});
+
+add_task(function* testGetPermissionDetailsByURI() {
+ // check that it returns an empty array on an invalid URI
+ // like a file URI, which doesn't support site permissions
+ let wrongURI = Services.io.newURI("file:///example.js", null, null)
+ Assert.deepEqual(SitePermissions.getPermissionDetailsByURI(wrongURI), []);
let uri = Services.io.newURI("https://example.com", null, null)
SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
SitePermissions.set(uri, "cookie", SitePermissions.SESSION);
SitePermissions.set(uri, "popup", SitePermissions.BLOCK);
- let permissions = SitePermissions.getPermissionsByURI(uri);
+ let permissions = SitePermissions.getPermissionDetailsByURI(uri);
let camera = permissions.find(({id}) => id === "camera");
Assert.deepEqual(camera, {
id: "camera",
label: "Use the Camera",
state: SitePermissions.ALLOW,
availableStates: [
{ id: SitePermissions.UNKNOWN, label: "Always Ask" },
{ id: SitePermissions.ALLOW, label: "Allow" },
{ id: SitePermissions.BLOCK, label: "Block" },
]
});
// check that removed permissions (State.UNKNOWN) are skipped
SitePermissions.remove(uri, "camera");
- permissions = SitePermissions.getPermissionsByURI(uri);
+ permissions = SitePermissions.getPermissionDetailsByURI(uri);
camera = permissions.find(({id}) => id === "camera");
Assert.equal(camera, undefined);
// check that different available state values are represented
let cookie = permissions.find(({id}) => id === "cookie");
Assert.deepEqual(cookie, {
--- a/browser/themes/linux/searchbar.css
+++ b/browser/themes/linux/searchbar.css
@@ -206,18 +206,18 @@ menuitem[cmd="cmd_clearhistory"][disable
display: -moz-box;
margin-inline-end: 0;
width: 16px;
height: 16px;
}
.addengine-item {
-moz-appearance: none;
- background-color: Menu;
- color: MenuText;
+ background-color: transparent;
+ color: inherit;
border: none;
height: 32px;
margin: 0;
padding: 0 10px;
}
.addengine-item > .button-box {
-moz-box-pack: start;
--- a/devtools/client/animationinspector/components/animation-timeline.js
+++ b/devtools/client/animationinspector/components/animation-timeline.js
@@ -68,17 +68,17 @@ AnimationsTimeline.prototype = {
parent: containerEl,
attributes: {
"class": "animation-timeline"
}
});
let scrubberContainer = createNode({
parent: this.rootWrapperEl,
- attributes: {"class": "scrubber-wrapper track-container"}
+ attributes: {"class": "scrubber-wrapper"}
});
this.scrubberEl = createNode({
parent: scrubberContainer,
attributes: {
"class": "scrubber"
}
});
@@ -87,25 +87,40 @@ AnimationsTimeline.prototype = {
parent: this.scrubberEl,
attributes: {
"class": "scrubber-handle"
}
});
this.scrubberHandleEl.addEventListener("mousedown",
this.onScrubberMouseDown);
+ this.headerWrapper = createNode({
+ parent: this.rootWrapperEl,
+ attributes: {
+ "class": "header-wrapper"
+ }
+ });
+
this.timeHeaderEl = createNode({
- parent: this.rootWrapperEl,
+ parent: this.headerWrapper,
attributes: {
"class": "time-header track-container"
}
});
+
this.timeHeaderEl.addEventListener("mousedown",
this.onScrubberMouseDown);
+ this.timeTickEl = createNode({
+ parent: this.rootWrapperEl,
+ attributes: {
+ "class": "time-body track-container"
+ }
+ });
+
this.animationsEl = createNode({
parent: this.rootWrapperEl,
nodeType: "ul",
attributes: {
"class": "animations"
}
});
@@ -449,24 +464,39 @@ AnimationsTimeline.prototype = {
let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
let minTimeInterval = TIME_GRADUATION_MIN_SPACING *
animationDuration / width;
let intervalLength = findOptimalTimeInterval(minTimeInterval);
let intervalWidth = intervalLength * width / animationDuration;
// And the time graduation header.
this.timeHeaderEl.innerHTML = "";
+ this.timeTickEl.innerHTML = "";
for (let i = 0; i <= width / intervalWidth; i++) {
let pos = 100 * i * intervalWidth / width;
+ // This element is the header of time tick for displaying animation
+ // duration time.
createNode({
parent: this.timeHeaderEl,
nodeType: "span",
attributes: {
- "class": "time-tick",
+ "class": "header-item",
"style": `left:${pos}%`
},
textContent: TimeScale.formatTime(TimeScale.distanceToRelativeTime(pos))
});
+
+ // This element is displayed as a vertical line separator corresponding
+ // the header of time tick for indicating time slice for animation
+ // iterations.
+ createNode({
+ parent: this.timeTickEl,
+ nodeType: "span",
+ attributes: {
+ "class": "time-tick",
+ "style": `left:${pos}%`
+ }
+ });
}
}
};
--- a/devtools/client/animationinspector/test/browser_animation_timeline_header.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_header.js
@@ -11,33 +11,39 @@ requestLongerTimeout(2);
const {findOptimalTimeInterval, TimeScale} = require("devtools/client/animationinspector/utils");
// Should be kept in sync with TIME_GRADUATION_MIN_SPACING in
// animation-timeline.js
const TIME_GRADUATION_MIN_SPACING = 40;
add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
+
+ // System scrollbar is enabled by default on our testing envionment and it
+ // would shrink width of inspector and affect number of time-ticks causing
+ // unexpected results. So, we set it wider to avoid this kind of edge case.
+ yield pushPref("devtools.toolsidebar-width.inspector", 350);
+
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let headerEl = timeline.timeHeaderEl;
info("Find out how many time graduations should there be");
let width = headerEl.offsetWidth;
let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
let minTimeInterval = TIME_GRADUATION_MIN_SPACING * animationDuration / width;
// Note that findOptimalTimeInterval is tested separately in xpcshell test
// test_findOptimalTimeInterval.js, so we assume that it works here.
let interval = findOptimalTimeInterval(minTimeInterval);
let nb = Math.ceil(animationDuration / interval);
- is(headerEl.querySelectorAll(".time-tick").length, nb,
+ is(headerEl.querySelectorAll(".header-item").length, nb,
"The expected number of time ticks were found");
info("Make sure graduations are evenly distributed and show the right times");
[...headerEl.querySelectorAll(".time-tick")].forEach((tick, i) => {
let left = parseFloat(tick.style.left);
let expectedPos = i * interval * 100 / animationDuration;
is(Math.round(left), Math.round(expectedPos),
`Graduation ${i} is positioned correctly`);
--- a/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
@@ -12,17 +12,17 @@ add_task(function* () {
yield addTab(URL_ROOT + "doc_simple_animation.html");
let {panel} = yield openAnimationInspector();
let timeline = panel.animationsTimelineComponent;
let el = timeline.rootWrapperEl;
ok(el.querySelector(".time-header"),
"The header element is in the DOM of the timeline");
- ok(el.querySelectorAll(".time-header .time-tick").length,
+ ok(el.querySelectorAll(".time-header .header-item").length,
"The header has some time graduations");
ok(el.querySelector(".animations"),
"The animations container is in the DOM of the timeline");
is(el.querySelectorAll(".animations .animation").length,
timeline.animations.length,
"The number of animations displayed matches the number of animations");
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -59,16 +59,17 @@ skip-if = true # Bug 1177463 - Temporari
[browser_toolbox_select_event.js]
skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
[browser_toolbox_selected_tool_unavailable.js]
[browser_toolbox_sidebar.js]
[browser_toolbox_sidebar_events.js]
[browser_toolbox_sidebar_existing_tabs.js]
[browser_toolbox_sidebar_overflow_menu.js]
[browser_toolbox_split_console.js]
+[browser_toolbox_target.js]
[browser_toolbox_tabsswitch_shortcuts.js]
[browser_toolbox_textbox_context_menu.js]
[browser_toolbox_theme_registration.js]
[browser_toolbox_toggle.js]
[browser_toolbox_tool_ready.js]
[browser_toolbox_tool_remote_reopen.js]
[browser_toolbox_transport_events.js]
[browser_toolbox_view_source_01.js]
--- a/devtools/client/framework/test/browser_target_from_url.js
+++ b/devtools/client/framework/test/browser_target_from_url.js
@@ -33,20 +33,20 @@ add_task(function* () {
assertIsTabTarget(target);
info("Test tab with chrome privileges");
target = yield targetFromURL(new URL("http://foo?type=tab&id=" + windowId + "&chrome"));
assertIsTabTarget(target, true);
info("Test invalid tab id");
try {
- yield targetFromURL(new URL("http://foo?type=tab&id=1"));
+ yield targetFromURL(new URL("http://foo?type=tab&id=10000"));
ok(false, "Shouldn't pass");
} catch (e) {
- is(e.message, "targetFromURL, tab with outerWindowID:'1' doesn't exist");
+ is(e.message, "targetFromURL, tab with outerWindowID:'10000' doesn't exist");
}
info("Test parent process");
target = yield targetFromURL(new URL("http://foo?type=process"));
let topWindow = Services.wm.getMostRecentWindow("navigator:browser");
is(target.url, topWindow.location.href);
is(target.isLocalTab, false);
is(target.chrome, true);
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/browser_toolbox_target.js
@@ -0,0 +1,60 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test about:devtools-toolbox?target which allows opening a toolbox in an
+// iframe while defining which document to debug by setting a `target`
+// attribute refering to the document to debug.
+
+add_task(function *() {
+ // iframe loads the document to debug
+ let iframe = document.createElement("browser");
+ iframe.setAttribute("type", "content");
+ document.documentElement.appendChild(iframe);
+
+ let onLoad = once(iframe, "load", true);
+ iframe.setAttribute("src", "data:text/html,document to debug");
+ yield onLoad;
+ is(iframe.contentWindow.document.body.innerHTML, "document to debug");
+
+ // toolbox loads the toolbox document
+ let toolboxIframe = document.createElement("iframe");
+ document.documentElement.appendChild(toolboxIframe);
+
+ // Important step to define which target to debug
+ toolboxIframe.target = iframe;
+
+ let onToolboxReady = gDevTools.once("toolbox-ready");
+
+ onLoad = once(toolboxIframe, "load", true);
+ toolboxIframe.setAttribute("src", "about:devtools-toolbox?target");
+ yield onLoad;
+
+ // Also wait for toolbox-ready, as toolbox document load isn't enough, there
+ // is plenty of asynchronous steps during toolbox load
+ info("Waiting for toolbox-ready");
+ let toolbox = yield onToolboxReady;
+
+ let onToolboxDestroyed = gDevTools.once("toolbox-destroyed");
+ let onTabActorDetached = once(toolbox.target.client, "tabDetached");
+
+ info("Removing the iframes");
+ toolboxIframe.remove();
+
+ // And wait for toolbox-destroyed as toolbox unload is also full of
+ // asynchronous operation that outlast unload event
+ info("Waiting for toolbox-destroyed");
+ yield onToolboxDestroyed;
+ info("Toolbox destroyed");
+
+ // Also wait for tabDetached. Toolbox destroys the Target which calls
+ // TabActor.detach(). But Target doesn't wait for detach's end to resolve.
+ // Whereas it is quite important as it is a significant part of toolbox
+ // cleanup. If we do not wait for it and starts removing debugged document,
+ // the actor is still considered as being attached and continues processing
+ // events.
+ yield onTabActorDetached;
+
+ iframe.remove();
+});
--- a/devtools/client/inspector/rules/models/element-style.js
+++ b/devtools/client/inspector/rules/models/element-style.js
@@ -1,30 +1,21 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const {Cc, Ci} = require("chrome");
const promise = require("promise");
const {Rule} = require("devtools/client/inspector/rules/models/rule");
const {promiseWarn} = require("devtools/client/inspector/shared/utils");
const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
-const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
-
-loader.lazyGetter(this, "PSEUDO_ELEMENTS", () => {
- return domUtils.getCSSPseudoElementNames();
-});
-
-XPCOMUtils.defineLazyGetter(this, "domUtils", function () {
- return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
-});
+const {getCssProperties} = require("devtools/shared/fronts/css-properties");
/**
* ElementStyle is responsible for the following:
* Keeps track of which properties are overridden.
* Maintains a list of Rule objects for a given element.
*
* @param {Element} element
* The element whose style we are viewing.
@@ -43,16 +34,17 @@ XPCOMUtils.defineLazyGetter(this, "domUt
function ElementStyle(element, ruleView, store, pageStyle,
showUserAgentStyles) {
this.element = element;
this.ruleView = ruleView;
this.store = store || {};
this.pageStyle = pageStyle;
this.showUserAgentStyles = showUserAgentStyles;
this.rules = [];
+ this.cssProperties = getCssProperties(this.ruleView.inspector.toolbox);
// We don't want to overwrite this.store.userProperties so we only create it
// if it doesn't already exist.
if (!("userProperties" in this.store)) {
this.store.userProperties = new UserProperties();
}
if (!("disabled" in this.store)) {
@@ -203,17 +195,17 @@ ElementStyle.prototype = {
return true;
},
/**
* Calls markOverridden with all supported pseudo elements
*/
markOverriddenAll: function () {
this.markOverridden();
- for (let pseudo of PSEUDO_ELEMENTS) {
+ for (let pseudo of this.cssProperties.pseudoElements) {
this.markOverridden(pseudo);
}
},
/**
* Mark the properties listed in this.rules for a given pseudo element
* with an overridden flag if an earlier property overrides it.
*
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -205,20 +205,16 @@ devtools.jar:
skin/shadereditor.css (themes/shadereditor.css)
skin/storage.css (themes/storage.css)
skin/splitview.css (themes/splitview.css)
skin/styleeditor.css (themes/styleeditor.css)
skin/webaudioeditor.css (themes/webaudioeditor.css)
skin/components-frame.css (themes/components-frame.css)
skin/components-h-split-box.css (themes/components-h-split-box.css)
skin/jit-optimizations.css (themes/jit-optimizations.css)
- skin/images/magnifying-glass.png (themes/images/magnifying-glass.png)
- skin/images/magnifying-glass@2x.png (themes/images/magnifying-glass@2x.png)
- skin/images/magnifying-glass-light.png (themes/images/magnifying-glass-light.png)
- skin/images/magnifying-glass-light@2x.png (themes/images/magnifying-glass-light@2x.png)
skin/images/filter.svg (themes/images/filter.svg)
skin/images/search.svg (themes/images/search.svg)
skin/images/itemToggle.svg (themes/images/itemToggle.svg)
skin/images/itemArrow-dark-rtl.svg (themes/images/itemArrow-dark-rtl.svg)
skin/images/itemArrow-dark-ltr.svg (themes/images/itemArrow-dark-ltr.svg)
skin/images/itemArrow-rtl.svg (themes/images/itemArrow-rtl.svg)
skin/images/itemArrow-ltr.svg (themes/images/itemArrow-ltr.svg)
skin/images/noise.png (themes/images/noise.png)
@@ -338,21 +334,19 @@ devtools.jar:
# Firebug Theme
skin/images/firebug/read-only.svg (themes/images/firebug/read-only.svg)
skin/images/firebug/spinner.png (themes/images/firebug/spinner.png)
skin/images/firebug/twisty-closed-firebug.svg (themes/images/firebug/twisty-closed-firebug.svg)
skin/images/firebug/twisty-open-firebug.svg (themes/images/firebug/twisty-open-firebug.svg)
skin/images/firebug/arrow-down.svg (themes/images/firebug/arrow-down.svg)
skin/images/firebug/arrow-up.svg (themes/images/firebug/arrow-up.svg)
skin/images/firebug/close.svg (themes/images/firebug/close.svg)
- skin/images/firebug/filter.svg (themes/images/firebug/filter.svg)
skin/images/firebug/pause.svg (themes/images/firebug/pause.svg)
skin/images/firebug/play.svg (themes/images/firebug/play.svg)
skin/images/firebug/rewind.svg (themes/images/firebug/rewind.svg)
- skin/images/firebug/timeline-filter.svg (themes/images/firebug/timeline-filter.svg)
skin/images/firebug/disable.svg (themes/images/firebug/disable.svg)
skin/images/firebug/breadcrumbs-divider.svg (themes/images/firebug/breadcrumbs-divider.svg)
skin/images/firebug/breakpoint.svg (themes/images/firebug/breakpoint.svg)
skin/images/firebug/tool-options.svg (themes/images/firebug/tool-options.svg)
skin/images/firebug/debugger-step-in.svg (themes/images/firebug/debugger-step-in.svg)
skin/images/firebug/debugger-step-out.svg (themes/images/firebug/debugger-step-out.svg)
skin/images/firebug/debugger-step-over.svg (themes/images/firebug/debugger-step-over.svg)
skin/images/firebug/pane-collapse.svg (themes/images/firebug/pane-collapse.svg)
--- a/devtools/client/jsonview/css/search-box.css
+++ b/devtools/client/jsonview/css/search-box.css
@@ -3,44 +3,22 @@
* 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/. */
/******************************************************************************/
/* Search Box */
.searchBox {
height: 18px;
- font-size: 12px;
- margin-top: 0;
- border: 1px solid rgb(170, 188, 207);
- width: 200px;
- position: fixed;
- right: 5px;
- background-image: url("search.svg");
+ font: message-box;
+ background-color: var(--theme-body-background);
+ background-image: url("chrome://devtools/skin/images/filter.svg");
background-repeat: no-repeat;
background-position: 2px center;
+ border: 1px solid var(--theme-splitter-color);
+ border-radius: 2px;
+ color: var(--theme-content-color1);
+ width: 200px;
+ margin-top: 0;
+ position: fixed;
+ right: 1px;
padding-left: 20px;
}
-
-/******************************************************************************/
-/* Light Theme & Dark Theme*/
-
-.theme-dark .searchBox,
-.theme-light .searchBox {
- border: 1px solid rgb(170, 170, 170);
- background-image: url("chrome://devtools/skin/images/magnifying-glass-light.png");
- background-position: 8px center;
- border-radius: 2px;
- padding-left: 25px;
- margin-top: 1px;
- height: 16px;
- font-style: italic;
-}
-
-/******************************************************************************/
-/* Dark Theme */
-
-.theme-dark .searchBox {
- background-color: rgba(24, 29, 32, 1);
- color: rgba(184, 200, 217, 1);
- border-color: var(--theme-splitter-color);
- background-image: url("chrome://devtools/skin/images/magnifying-glass.png");
-}
--- a/devtools/client/shared/components/reps/document.js
+++ b/devtools/client/shared/components/reps/document.js
@@ -6,19 +6,18 @@
"use strict";
// Make this available to both AMD and CJS environments
define(function (require, exports, module) {
// ReactJS
const React = require("devtools/client/shared/vendor/react");
// Reps
- const { createFactories, isGrip } = require("./rep-utils");
+ const { createFactories, isGrip, getFileName } = require("./rep-utils");
const { ObjectBox } = createFactories(require("./object-box"));
- const { getFileName } = require("./url");
// Shortcuts
const { span } = React.DOM;
/**
* Renders DOM document object.
*/
let Document = React.createClass({
--- a/devtools/client/shared/components/reps/function.js
+++ b/devtools/client/shared/components/reps/function.js
@@ -29,17 +29,17 @@ define(function (require, exports, modul
return this.props.objectLink({
object: grip
}, "function");
}
return "";
},
summarizeFunction: function (grip) {
- let name = grip.displayName || grip.name || "function";
+ let name = grip.userDisplayName || grip.displayName || grip.name || "function";
return cropString(name + "()", 100);
},
render: function () {
let grip = this.props.object;
return (
ObjectBox({className: "function"},
--- a/devtools/client/shared/components/reps/moz.build
+++ b/devtools/client/shared/components/reps/moz.build
@@ -26,11 +26,10 @@ DevToolsModules(
'regexp.js',
'rep-utils.js',
'rep.js',
'reps.css',
'string.js',
'stylesheet.js',
'text-node.js',
'undefined.js',
- 'url.js',
'window.js',
)
--- a/devtools/client/shared/components/reps/rep-utils.js
+++ b/devtools/client/shared/components/reps/rep-utils.js
@@ -1,8 +1,9 @@
+/* globals URLSearchParams */
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
@@ -68,14 +69,82 @@ define(function (require, exports, modul
if (text.length > limit) {
return text.substr(0, Math.ceil(halfLimit)) + alternativeText +
text.substr(text.length - Math.floor(halfLimit));
}
return text;
}
+ function parseURLParams(url) {
+ url = new URL(url);
+ return parseURLEncodedText(url.searchParams);
+ }
+
+ function parseURLEncodedText(text) {
+ let params = [];
+
+ // In case the text is empty just return the empty parameters
+ if (text == "") {
+ return params;
+ }
+
+ let searchParams = new URLSearchParams(text);
+ let entries = [...searchParams.entries()];
+ return entries.map(entry => {
+ return {
+ name: entry[0],
+ value: entry[1]
+ };
+ });
+ }
+
+ function getFileName(url) {
+ let split = splitURLBase(url);
+ return split.name;
+ }
+
+ function splitURLBase(url) {
+ if (!isDataURL(url)) {
+ return splitURLTrue(url);
+ }
+ return {};
+ }
+
+ function isDataURL(url) {
+ return (url && url.substr(0, 5) == "data:");
+ }
+
+ function splitURLTrue(url) {
+ const reSplitFile = /(.*?):\/{2,3}([^\/]*)(.*?)([^\/]*?)($|\?.*)/;
+ let m = reSplitFile.exec(url);
+
+ if (!m) {
+ return {
+ name: url,
+ path: url
+ };
+ } else if (m[4] == "" && m[5] == "") {
+ return {
+ protocol: m[1],
+ domain: m[2],
+ path: m[3],
+ name: m[3] != "/" ? m[3] : m[2]
+ };
+ }
+
+ return {
+ protocol: m[1],
+ domain: m[2],
+ path: m[2] + m[3],
+ name: m[4] + m[5]
+ };
+ }
+
// Exports from this module
exports.createFactories = createFactories;
exports.isGrip = isGrip;
exports.cropString = cropString;
exports.cropMultipleLines = cropMultipleLines;
+ exports.parseURLParams = parseURLParams;
+ exports.parseURLEncodedText = parseURLEncodedText;
+ exports.getFileName = getFileName;
});
--- a/devtools/client/shared/components/reps/stylesheet.js
+++ b/devtools/client/shared/components/reps/stylesheet.js
@@ -6,19 +6,18 @@
"use strict";
// Make this available to both AMD and CJS environments
define(function (require, exports, module) {
// ReactJS
const React = require("devtools/client/shared/vendor/react");
// Reps
- const { createFactories, isGrip } = require("./rep-utils");
+ const { createFactories, isGrip, getFileName } = require("./rep-utils");
const { ObjectBox } = createFactories(require("./object-box"));
- const { getFileName } = require("./url");
// Shortcuts
const DOM = React.DOM;
/**
* Renders a grip representing CSSStyleSheet
*/
let StyleSheet = React.createClass({
deleted file mode 100644
--- a/devtools/client/shared/components/reps/url.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-/* global URLSearchParams */
-
-"use strict";
-
-// Make this available to both AMD and CJS environments
-define(function (require, exports, module) {
- function parseURLParams(url) {
- url = new URL(url);
- return parseURLEncodedText(url.searchParams);
- }
-
- function parseURLEncodedText(text) {
- let params = [];
-
- // In case the text is empty just return the empty parameters
- if (text == "") {
- return params;
- }
-
- let searchParams = new URLSearchParams(text);
- let entries = [...searchParams.entries()];
- return entries.map(entry => {
- return {
- name: entry[0],
- value: entry[1]
- };
- });
- }
-
- function getFileName(url) {
- let split = splitURLBase(url);
- return split.name;
- }
-
- function splitURLBase(url) {
- if (!isDataURL(url)) {
- return splitURLTrue(url);
- }
- return {};
- }
-
- function isDataURL(url) {
- return (url && url.substr(0, 5) == "data:");
- }
-
- function splitURLTrue(url) {
- const reSplitFile = /(.*?):\/{2,3}([^\/]*)(.*?)([^\/]*?)($|\?.*)/;
- let m = reSplitFile.exec(url);
-
- if (!m) {
- return {
- name: url,
- path: url
- };
- } else if (m[4] == "" && m[5] == "") {
- return {
- protocol: m[1],
- domain: m[2],
- path: m[3],
- name: m[3] != "/" ? m[3] : m[2]
- };
- }
-
- return {
- protocol: m[1],
- domain: m[2],
- path: m[2] + m[3],
- name: m[4] + m[5]
- };
- }
-
- // Exports from this module
- exports.parseURLParams = parseURLParams;
- exports.parseURLEncodedText = parseURLEncodedText;
- exports.getFileName = getFileName;
-});
--- a/devtools/client/shared/components/test/mochitest/test_reps_function.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_function.html
@@ -47,16 +47,32 @@ window.onload = Task.async(function* ()
mode: undefined,
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
+ function testUserNamed() {
+ // Test declaration: `function testName{ let innerVar = "foo" }`
+ const testName = "testUserNamed";
+
+ const defaultOutput = `testUserName()`;
+
+ const modeTests = [
+ {
+ mode: undefined,
+ expectedOutput: defaultOutput,
+ }
+ ];
+
+ testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+ }
+
function testVarNamed() {
// Test declaration: `let testVarName = function() { }`
const testName = "testVarNamed";
const defaultOutput = `testVarName()`;
const modeTests = [
{
@@ -113,16 +129,33 @@ window.onload = Task.async(function* ()
"name": "testName",
"displayName": "testName",
"location": {
"url": "debugger eval code",
"line": 1
}
};
+ case "testUserNamed":
+ return {
+ "type": "object",
+ "class": "Function",
+ "actor": "server1.conn6.obj35",
+ "extensible": true,
+ "frozen": false,
+ "sealed": false,
+ "name": "testName",
+ "userDisplayName": "testUserName",
+ "displayName": "testName",
+ "location": {
+ "url": "debugger eval code",
+ "line": 1
+ }
+ };
+
case "testVarNamed":
return {
"type": "object",
"class": "Function",
"actor": "server1.conn7.obj41",
"extensible": true,
"frozen": false,
"sealed": false,
--- a/devtools/client/shared/css-angle.js
+++ b/devtools/client/shared/css-angle.js
@@ -5,16 +5,18 @@
"use strict";
const SPECIALVALUES = new Set([
"initial",
"inherit",
"unset"
]);
+const {getCSSLexer} = require("devtools/shared/css-lexer");
+
/**
* This module is used to convert between various angle units.
*
* Usage:
* let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
* let {angleUtils} = require("devtools/client/shared/css-angle");
* let angle = new angleUtils.CssAngle("180deg");
*
@@ -61,17 +63,22 @@ CssAngle.prototype = {
return this._angleUnit;
},
set angleUnit(unit) {
this._angleUnit = unit;
},
get valid() {
- return /^-?\d+\.?\d*(deg|rad|grad|turn)$/gi.test(this.authored);
+ let token = getCSSLexer(this.authored).nextToken();
+ if (!token) {
+ return false;
+ }
+ return (token.tokenType === "dimension"
+ && token.text.toLowerCase() in CssAngle.ANGLEUNIT);
},
get specialValue() {
return SPECIALVALUES.has(this.lowerCased) ? this.authored : null;
},
get deg() {
let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.js
@@ -151,17 +151,17 @@ OutputParser.prototype = {
let colorOK = function () {
return options.supportsColor ||
(options.expectFilter && parenDepth === 1 &&
outerMostFunctionTakesColor);
};
let angleOK = function (angle) {
- return /^-?\d+\.?\d*(deg|rad|grad|turn)$/gi.test(angle);
+ return (new angleUtils.CssAngle(angle)).valid;
};
while (true) {
let token = tokenStream.nextToken();
if (!token) {
break;
}
if (token.tokenType === "comment") {
--- a/devtools/client/shared/test/browser_css_angle.js
+++ b/devtools/client/shared/test/browser_css_angle.js
@@ -8,16 +8,17 @@ const TEST_URI = "data:text/html;charset
var {angleUtils} = require("devtools/client/shared/css-angle");
add_task(function* () {
yield addTab("about:blank");
let [host] = yield createHost("bottom", TEST_URI);
info("Starting the test");
testAngleUtils();
+ testAngleValidity();
host.destroy();
gBrowser.removeCurrentTab();
});
function testAngleUtils() {
let data = getTestData();
@@ -30,30 +31,77 @@ function testAngleUtils() {
is(angle.rad, rad, "color.rad === rad");
is(angle.grad, grad, "color.grad === grad");
is(angle.turn, turn, "color.turn === turn");
testToString(angle, deg, rad, grad, turn);
}
}
+function testAngleValidity() {
+ let data = getAngleValidityData();
+
+ for (let {angle, result} of data) {
+ let testAngle = new angleUtils.CssAngle(angle);
+
+ is(testAngle.valid, result, `Testing that "${angle}" is ${testAngle.valid ? " a valid" : "an invalid" } angle`);
+ }
+}
+
function testToString(angle, deg, rad, grad, turn) {
angle.angleUnit = angleUtils.CssAngle.ANGLEUNIT.deg;
is(angle.toString(), deg, "toString() with deg type");
angle.angleUnit = angleUtils.CssAngle.ANGLEUNIT.rad;
is(angle.toString(), rad, "toString() with rad type");
angle.angleUnit = angleUtils.CssAngle.ANGLEUNIT.grad;
is(angle.toString(), grad, "toString() with grad type");
angle.angleUnit = angleUtils.CssAngle.ANGLEUNIT.turn;
is(angle.toString(), turn, "toString() with turn type");
}
+function getAngleValidityData() {
+ return [{
+ angle: "0.2turn",
+ result: true
+ }, {
+ angle: "-0.2turn",
+ result: true
+ }, {
+ angle: "-.2turn",
+ result: true
+ }, {
+ angle: "1e02turn",
+ result: true
+ }, {
+ angle: "-2e2turn",
+ result: true
+ }, {
+ angle: ".2turn",
+ result: true
+ }, {
+ angle: "0.2aaturn",
+ result: false
+ }, {
+ angle: "2dega",
+ result: false
+ }, {
+ angle: "0.deg",
+ result: false
+ }, {
+ angle: ".deg",
+ result: false
+ }, {
+ angle: "..2turn",
+ result: false
+ }];
+}
+
function getTestData() {
return [{
authored: "0deg",
deg: "0deg",
rad: "0rad",
grad: "0grad",
turn: "0turn"
}, {
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -102,17 +102,18 @@ body {
[timeline] #timeline-toolbar {
display: flex;
}
/* The main animations container */
#players {
height: calc(100% - var(--toolbar-height));
- overflow: auto;
+ overflow-x: hidden;
+ overflow-y: auto;
}
[empty] #players {
display: none;
}
/* The error message, shown when an invalid/unanimated element is selected */
@@ -195,18 +196,16 @@ body {
#timeline-rate {
position: relative;
width: 4.5em;
}
/* Animation timeline component */
.animation-timeline {
- height: 100%;
- overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
}
/* Useful for positioning animations or keyframes in the timeline */
.animation-timeline .track-container {
position: absolute;
@@ -214,77 +213,104 @@ body {
left: var(--timeline-sidebar-width);
/* Leave the width of a marker right of a track so the 100% markers can be
selected easily */
right: var(--keyframes-marker-size);
height: var(--timeline-animation-height);
}
.animation-timeline .scrubber-wrapper {
- z-index: 2;
- pointer-events: none;
+ position: absolute;
+ left: var(--timeline-sidebar-width);
+ /* Leave the width of a marker right of a track so the 100% markers can be
+ selected easily */
+ right: var(--keyframes-marker-size);
height: 100%;
}
.animation-timeline .scrubber {
+ z-index: 5;
+ pointer-events: none;
position: absolute;
- height: 100%;
+ /* Make the scrubber as tall as the viewport minus the toolbar height and the
+ header-wrapper's borders */
+ height: calc(100vh - var(--toolbar-height) - 1px);
+ min-height: 100%;
width: 0;
border-right: 1px solid red;
box-sizing: border-box;
}
-.animation-timeline .scrubber::before {
- content: "";
- position: absolute;
- top: 0;
- width: 1px;
- right: -6px;
- border-top: 5px solid red;
- border-left: 5px solid transparent;
- border-right: 5px solid transparent;
-}
-
/* The scrubber handle is a transparent element displayed on top of the scrubber
line that allows users to drag it */
.animation-timeline .scrubber .scrubber-handle {
position: absolute;
height: 100%;
- top: 0;
/* Make it thick enough for easy dragging */
width: 6px;
- right: -3px;
+ right: -1.5px;
cursor: col-resize;
pointer-events: all;
}
+.animation-timeline .scrubber .scrubber-handle::before {
+ content: "";
+ position: sticky;
+ top: 0;
+ width: 1px;
+ border-top: 5px solid red;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+}
+
.animation-timeline .time-header {
- min-height: var(--toolbar-height);
+ min-height: var(--timeline-animation-height);
cursor: col-resize;
-moz-user-select: none;
}
-.animation-timeline .time-header .time-tick {
+.animation-timeline .time-header .header-item {
position: absolute;
+ height: 100%;
+ padding-top: 3px;
+ border-left: 0.5px solid var(--time-graduation-border-color);
+}
+
+.animation-timeline .header-wrapper {
+ position: sticky;
top: 0;
- height: 100vh;
- padding-top: 3px;
+ background-color: var(--theme-body-background);
+ border-bottom: 1px solid var(--time-graduation-border-color);
+ z-index: 3;
+ height: var(--timeline-animation-height);
+ overflow: hidden;
+}
+
+.animation-timeline .time-body {
+ height: 100%;
+}
+
+.animation-timeline .time-body .time-tick {
+ -moz-user-select: none;
+ position: absolute;
+ width: 0;
+ /* When scroll bar is shown, make it covers entire time-body */
+ height: 100%;
+ /* When scroll bar is hidden, make it as tall as the viewport minus the
+ timeline animation height and the header-wrapper's borders */
+ min-height: calc(100vh - var(--timeline-animation-height) - 1px);
border-left: 0.5px solid var(--time-graduation-border-color);
}
.animation-timeline .animations {
width: 100%;
height: 100%;
- overflow-y: auto;
- overflow-x: hidden;
- /* Leave some space for the header */
- margin-top: var(--timeline-animation-height);
padding: 0;
list-style-type: none;
- border-top: 1px solid var(--time-graduation-border-color);
+ margin-top: 0;
}
/* Animation block widgets */
.animation-timeline .animation {
margin: 2px 0;
height: var(--timeline-animation-height);
position: relative;
deleted file mode 100644
--- a/devtools/client/themes/images/firebug/filter.svg
+++ /dev/null
@@ -1,25 +0,0 @@
-<!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="12" viewBox="0 0 12 12">
- <defs>
- <linearGradient id="b">
- <stop offset="0" stop-color="#646464"/>
- <stop offset=".734" stop-color="#c8c8c8"/>
- <stop offset="1" stop-color="#b4b4b4"/>
- </linearGradient>
- <linearGradient id="a">
- <stop offset="0" stop-color="#787878"/>
- <stop offset=".764" stop-color="#dcdcdc"/>
- <stop offset="1" stop-color="#c8c8c8"/>
- </linearGradient>
- <linearGradient xlink:href="#a" id="e" x1="7.336" y1="8.379" x2="4.585" y2="8.379" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.991 0 0 1 .083 1040.346)"/>
- <linearGradient xlink:href="#b" id="f" x1="7.715" y1="7.526" x2="4.206" y2="7.526" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.991 0 0 1 .083 1040.346)"/>
- <linearGradient xlink:href="#b" id="d" x1="9.755" y1="1044.158" x2="2.188" y2="1044.158" gradientUnits="userSpaceOnUse" gradientTransform="scale(1.005 1)"/>
- <linearGradient xlink:href="#a" id="c" x1="9.597" y1="1042.528" x2="2.261" y2="1042.528" gradientUnits="userSpaceOnUse" gradientTransform="scale(1.005 1)"/>
- </defs>
- <g stroke-width=".4" stroke-linejoin="round">
- <path d="M.2 1041.536l3.975 5.244h3.65l3.975-5.242z" fill="url(#c)" stroke="url(#d)" transform="translate(0 -1040.362)"/>
- <path d="M7.8 1046.764H4.2v3.898h3.6z" fill="url(#e)" stroke="url(#f)" transform="translate(0 -1040.362)"/>
- </g>
-</svg>
deleted file mode 100644
--- a/devtools/client/themes/images/firebug/timeline-filter.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-<!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
- <path d="M2 2v3l5 4v6h2V9l5-4V2z" fill="#3bace5"/>
-</svg>
deleted file mode 100644
index e8c1841588a52bd11935724ef92112a58e2f3c7e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d784870c380da76e4897aad41c4c87cc848dd50e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/devtools/client/themes/performance.css
+++ b/devtools/client/themes/performance.css
@@ -5,35 +5,32 @@
/* CSS Variables specific to this panel that aren't defined by the themes */
.theme-dark {
--cell-border-color: rgba(255,255,255,0.15);
--cell-border-color-light: rgba(255,255,255,0.1);
--focus-cell-border-color: rgba(255,255,255,0.5);
--row-alt-background-color: rgba(86, 117, 185, 0.15);
--row-hover-background-color: rgba(86, 117, 185, 0.25);
- --filter-image: url(chrome://devtools/skin/images/filter.svg);
}
.theme-light {
--cell-border-color: rgba(0,0,0,0.15);
--cell-border-color-light: rgba(0,0,0,0.1);
--focus-cell-border-color: rgba(0,0,0,0.3);
--row-alt-background-color: rgba(76,158,217,0.1);
--row-hover-background-color: rgba(76,158,217,0.2);
- --filter-image: url(chrome://devtools/skin/images/filter.svg);
}
.theme-firebug {
--cell-border-color: rgba(0,0,0,0.15);
--cell-border-color-light: rgba(0,0,0,0.1);
--focus-cell-border-color: rgba(0,0,0,0.3);
--row-alt-background-color: rgba(76,158,217,0.1);
--row-hover-background-color: rgba(76,158,217,0.2);
- --filter-image: url(chrome://devtools/skin/images/firebug/timeline-filter.svg);
}
/**
* A generic class to hide elements, replacing the `element.hidden` attribute
* that we use to hide elements that can later be active
*/
.hidden {
display: none;
@@ -48,17 +45,17 @@
}
#performance-toolbar-controls-detail-views .toolbarbutton-text {
padding-inline-start: 4px;
padding-inline-end: 8px;
}
#filter-button {
- list-style-image: var(--filter-image);
+ list-style-image: url(images/filter.svg);
}
#performance-filter-menupopup > menuitem .menu-iconic-left::after {
content: "";
display: block;
width: 8px;
height: 8px;
margin: 0 8px;
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -1,26 +1,26 @@
/* 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/. */
/* CSS Variables specific to this panel that aren't defined by the themes */
.theme-light {
--rule-highlight-background-color: #ffee99;
- --rule-filter-icon: url(images/magnifying-glass-light.png);
+ --rule-filter-icon: url(images/filter.svg);
}
.theme-dark {
--rule-highlight-background-color: #594724;
- --rule-filter-icon: url(images/magnifying-glass.png);
+ --rule-filter-icon: url(images/filter.svg);
}
.theme-firebug {
--rule-highlight-background-color: #ffee99;
- --rule-filter-icon: url(images/magnifying-glass-light.png);
+ --rule-filter-icon: url(images/filter.svg);
--rule-property-name: darkgreen;
--rule-property-value: darkblue;
}
/* Rule View Tabpanel */
.theme-firebug .ruleview {
font-family: monospace;
--- a/devtools/client/themes/toolbars.css
+++ b/devtools/client/themes/toolbars.css
@@ -9,91 +9,39 @@
--toolbar-tab-hover-active: rgba(170, 170, 170, .4);
--searchbox-background-color: #ffee99;
--searchbox-border-color: #ffbf00;
--searcbox-no-match-background-color: #ffe5e5;
--searcbox-no-match-border-color: #e52e2e;
--magnifying-glass-image: url(images/search.svg);
--filter-image: url(images/filter.svg);
--tool-options-image: url(images/tool-options.svg);
- --close-button-image: url(chrome://devtools/skin/images/close.svg);
--icon-filter: invert(1);
- --dock-bottom-image: url(chrome://devtools/skin/images/dock-bottom.svg);
- --dock-side-image: url(chrome://devtools/skin/images/dock-side.svg);
- --dock-undock-image: url(chrome://devtools/skin/images/dock-undock.svg);
--toolbar-button-border-color: rgba(170, 170, 170, .5);
-
- /* Toolbox buttons */
- --command-paintflashing-image: url(images/command-paintflashing.svg);
- --command-screenshot-image: url(images/command-screenshot.svg);
- --command-responsive-image: url(images/command-responsivemode.svg);
- --command-scratchpad-image: url(images/command-scratchpad.svg);
- --command-pick-image: url(images/command-pick.svg);
- --command-frames-image: url(images/command-frames.svg);
- --command-splitconsole-image: url(images/command-console.svg);
- --command-noautohide-image: url(images/command-noautohide.svg);
- --command-eyedropper-image: url(images/command-eyedropper.svg);
- --command-rulers-image: url(images/command-rulers.svg);
- --command-measure-image: url(images/command-measure.svg);
}
.theme-dark {
--toolbar-tab-hover: hsla(206, 37%, 4%, .2);
--toolbar-tab-hover-active: hsla(206, 37%, 4%, .4);
--searchbox-background-color: #4d4222;
--searchbox-border-color: #d99f2b;
--searcbox-no-match-background-color: #402325;
--searcbox-no-match-border-color: #cc3d3d;
--magnifying-glass-image: url(images/search.svg);
--filter-image: url(images/filter.svg);
--tool-options-image: url(images/tool-options.svg);
- --close-button-image: url(chrome://devtools/skin/images/close.svg);
--icon-filter: none;
- --dock-bottom-image: url(chrome://devtools/skin/images/dock-bottom.svg);
- --dock-side-image: url(chrome://devtools/skin/images/dock-side.svg);
- --dock-undock-image: url(chrome://devtools/skin/images/dock-undock.svg);
--toolbar-button-border-color: rgba(0, 0, 0, .4);
-
- /* Toolbox buttons */
- --command-paintflashing-image: url(images/command-paintflashing.svg);
- --command-screenshot-image: url(images/command-screenshot.svg);
- --command-responsive-image: url(images/command-responsivemode.svg);
- --command-scratchpad-image: url(images/command-scratchpad.svg);
- --command-pick-image: url(images/command-pick.svg);
- --command-frames-image: url(images/command-frames.svg);
- --command-splitconsole-image: url(images/command-console.svg);
- --command-noautohide-image: url(images/command-noautohide.svg);
- --command-eyedropper-image: url(images/command-eyedropper.svg);
- --command-rulers-image: url(images/command-rulers.svg);
- --command-measure-image: url(images/command-measure.svg);
}
.theme-firebug {
- --magnifying-glass-image: url(images/firebug/filter.svg);
- --magnifying-glass-image-2x: url(images/firebug/filter.svg);
+ --magnifying-glass-image: url(images/search.svg);
--tool-options-image: url(images/firebug/tool-options.svg);
- --close-button-image: url(chrome://devtools/skin/images/firebug/close.svg);
--icon-filter: invert(1);
- --dock-bottom-image: url(chrome://devtools/skin/images/firebug/dock-bottom.svg);
- --dock-side-image: url(chrome://devtools/skin/images/firebug/dock-side.svg);
- --dock-undock-image: url(chrome://devtools/skin/images/firebug/dock-undock.svg);
--toolbar-button-border-color: rgba(170, 170, 170, .5);
-
- /* Toolbox buttons */
- --command-paintflashing-image: url(images/firebug/command-paintflashing.svg);
- --command-screenshot-image: url(images/firebug/command-screenshot.svg);
- --command-responsive-image: url(images/firebug/command-responsivemode.svg);
- --command-scratchpad-image: url(images/firebug/command-scratchpad.svg);
- --command-pick-image: url(images/firebug/command-pick.svg);
- --command-frames-image: url(images/firebug/command-frames.svg);
- --command-splitconsole-image: url(images/firebug/command-console.svg);
- --command-noautohide-image: url(images/firebug/command-noautohide.svg);
- --command-eyedropper-image: url(images/firebug/command-eyedropper.svg);
- --command-rulers-image: url(images/firebug/command-rulers.svg);
- --command-measure-image: url(images/firebug/command-measure.svg);
}
/* Toolbars */
.devtools-toolbar,
.devtools-sidebar-tabs tabs {
-moz-appearance: none;
padding: 0;
@@ -122,16 +70,25 @@
.devtools-toolbar checkbox .checkbox-label-box {
border: none !important; /* overrides .checkbox-label-box from checkbox.css */
}
.devtools-toolbar checkbox .checkbox-label-box .checkbox-label {
margin: 0 6px !important; /* overrides .checkbox-label from checkbox.css */
padding: 0;
}
+.devtools-separator {
+ margin: 0 2px;
+ width: 2px;
+ background-image: linear-gradient(transparent 15%, var(--theme-splitter-color) 15%, var(--theme-splitter-color) 85%, transparent 85%);
+ background-size: 1px 100%;
+ background-repeat: no-repeat;
+ background-position: 0, 1px, 2px;
+}
+
/* Toolbar buttons */
.devtools-menulist,
.devtools-toolbarbutton,
.devtools-button {
-moz-appearance: none;
background: transparent;
min-height: 18px;
@@ -540,22 +497,16 @@
margin-bottom: 0;
}
.devtools-searchinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear:hover,
.devtools-filterinput > .textbox-input-box > .textbox-search-icons > .textbox-search-clear:hover {
-moz-image-region: rect(0, 32px, 16px, 16px);
}
-/* Close button */
-
-#toolbox-close::before {
- background-image: var(--close-button-image);
-}
-
/* In-tools sidebar */
.devtools-sidebar-tabs {
-moz-appearance: none;
margin: 0;
}
.devtools-sidebar-tabs > tabpanels {
-moz-appearance: none;
@@ -639,291 +590,16 @@
}
.devtools-sidebar-tabs tabs > tab[selected],
.devtools-sidebar-tabs tabs > tab[selected]:hover:active {
color: var(--theme-selection-color);
background: var(--theme-selection-background);
}
-/* Toolbox - moved from toolbox.css.
- * Rules that apply to the global toolbox like command buttons,
- * devtools tabs, docking buttons, etc. */
-
-#toolbox-controls > button,
-#toolbox-dock-buttons > button {
- -moz-appearance: none;
- -moz-user-focus: normal;
- border: none;
- margin: 0 4px;
- min-width: 16px;
- width: 16px;
-}
-
-/* Save space in Firebug theme */
-.theme-firebug #toolbox-controls button {
- margin-inline-start: 0 !important;
- min-width: 12px;
- margin: 0 1px;
-}
-
-#toolbox-dock-bottom::before {
- background-image: var(--dock-bottom-image);
-}
-
-#toolbox-dock-side::before {
- background-image: var(--dock-side-image);
-}
-
-#toolbox-dock-window::before {
- background-image: var(--dock-undock-image);
-}
-
-#toolbox-dock-bottom-minimize {
- /* Bug 1177463 - The minimize button is currently hidden until we agree on
- the UI for it, and until bug 1173849 is fixed too. */
- display: none;
-}
-
-#toolbox-dock-bottom-minimize::before {
- background-image: url("chrome://devtools/skin/images/dock-bottom-minimize@2x.png");
-}
-
-#toolbox-dock-bottom-minimize.minimized::before {
- background-image: url("chrome://devtools/skin/images/dock-bottom-maximize@2x.png");
-}
-
-#toolbox-dock-window,
-#toolbox-dock-bottom,
-#toolbox-dock-side {
- opacity: 0.8;
-}
-
-#toolbox-dock-window:hover,
-#toolbox-dock-bottom:hover,
-#toolbox-dock-side:hover {
- opacity: 1;
-}
-
-.devtools-separator {
- margin: 0 2px;
- width: 2px;
- background-image: linear-gradient(transparent 15%, var(--theme-splitter-color) 15%, var(--theme-splitter-color) 85%, transparent 85%);
- background-size: 1px 100%;
- background-repeat: no-repeat;
- background-position: 0, 1px, 2px;
-}
-
-#toolbox-buttons:empty + .devtools-separator,
-.devtools-separator[invisible] {
- visibility: hidden;
-}
-
-#toolbox-controls-separator {
- margin: 0;
-}
-
-/* Command buttons */
-
-.command-button {
- padding: 0;
- margin: 0;
- position: relative;
- -moz-user-focus: normal;
-}
-
-.command-button::before {
- opacity: 0.7;
-}
-
-.command-button:hover {
- background-color: var(--toolbar-tab-hover);
-}
-
-.command-button:hover:active,
-.command-button[checked=true]:not(:hover) {
- background-color: var(--toolbar-tab-hover-active)
-}
-
-.command-button:hover::before {
- opacity: 0.85;
-}
-
-.command-button:hover:active::before,
-.command-button[checked=true]::before,
-.command-button[open=true]::before {
- opacity: 1;
-}
-
-/* Tabs */
-
-.devtools-tabbar {
- -moz-appearance: none;
- min-height: 24px;
- border: 0px solid;
- border-bottom-width: 1px;
- padding: 0;
- background: var(--theme-tab-toolbar-background);
- border-bottom-color: var(--theme-splitter-color);
-}
-
-#toolbox-tabs {
- margin: 0;
-}
-
-.toolbox-panel {
- display: -moz-box;
- -moz-box-flex: 1;
- visibility: collapse;
-}
-
-.toolbox-panel[selected] {
- visibility: visible;
-}
-
-.devtools-tab {
- -moz-appearance: none;
- -moz-binding: url("chrome://global/content/bindings/general.xml#control-item");
- -moz-box-align: center;
- min-width: 32px;
- min-height: 24px;
- max-width: 100px;
- margin: 0;
- padding: 0;
- border-style: solid;
- border-width: 0;
- border-inline-start-width: 1px;
- -moz-box-align: center;
- -moz-user-focus: normal;
- -moz-box-flex: 1;
-}
-
-/* Save space on the tab-strip in Firebug theme */
-.theme-firebug .devtools-tab {
- -moz-box-flex: initial;
-}
-
-.theme-dark .devtools-tab {
- color: var(--theme-body-color-alt);
- border-color: #42484f;
-}
-
-.theme-light .devtools-tab {
- color: var(--theme-body-color);
- border-color: var(--theme-splitter-color);
-}
-
-.theme-dark .devtools-tab:hover {
- color: #ced3d9;
-}
-
-.devtools-tab:hover {
- background-color: var(--toolbar-tab-hover);
-}
-
-.theme-dark .devtools-tab:hover:active {
- color: var(--theme-selection-color);
-}
-
-.devtools-tab:hover:active {
- background-color: var(--toolbar-tab-hover-active);
-}
-
-.theme-dark .devtools-tab:not([selected])[highlighted] {
- background-color: hsla(99, 100%, 14%, .3);
-}
-
-.theme-light .devtools-tab:not([selected])[highlighted] {
- background-color: rgba(44, 187, 15, .2);
-}
-
-/* Display execution pointer in the Debugger tab to indicate
- that the debugger is paused. */
-.theme-firebug #toolbox-tab-jsdebugger.devtools-tab:not([selected])[highlighted] {
- background-color: rgba(89, 178, 234, .2);
- background-image: url(chrome://devtools/skin/images/firebug/tool-debugger-paused.svg);
- background-repeat: no-repeat;
- padding-left: 13px !important;
- background-position: 3px 6px;
-}
-
-.devtools-tab > image {
- border: none;
- margin: 0;
- margin-inline-start: 4px;
- opacity: 0.6;
- max-height: 16px;
- width: 16px; /* Prevents collapse during theme switching */
-}
-
-.devtools-tab > label {
- white-space: nowrap;
- margin: 0 4px;
-}
-
-.devtools-tab:hover > image {
- opacity: 0.8;
-}
-
-.devtools-tab:active > image,
-.devtools-tab[selected] > image {
- opacity: 1;
-}
-
-.devtools-tabbar .devtools-tab[selected],
-.devtools-tabbar .devtools-tab[selected]:hover:active {
- color: var(--theme-selection-color);
- background-color: var(--theme-selection-background);
-}
-
-#toolbox-tabs .devtools-tab[selected],
-#toolbox-tabs .devtools-tab[highlighted] {
- border-width: 0;
- padding-inline-start: 1px;
-}
-
-#toolbox-tabs .devtools-tab[selected]:last-child,
-#toolbox-tabs .devtools-tab[highlighted]:last-child {
- padding-inline-end: 1px;
-}
-
-#toolbox-tabs .devtools-tab[selected] + .devtools-tab,
-#toolbox-tabs .devtools-tab[highlighted] + .devtools-tab {
- border-inline-start-width: 0;
- padding-inline-start: 1px;
-}
-
-#toolbox-tabs .devtools-tab:first-child[selected] {
- border-inline-start-width: 0;
-}
-
-#toolbox-tabs .devtools-tab:last-child {
- border-inline-end-width: 1px;
-}
-
-.devtools-tab:not([highlighted]) > .highlighted-icon,
-.devtools-tab[selected] > .highlighted-icon,
-.devtools-tab:not([selected])[highlighted] > .default-icon {
- visibility: collapse;
-}
-
-/* The options tab is special - it doesn't have the same parent
- as the other tabs (toolbox-option-container vs toolbox-tabs) */
-#toolbox-option-container .devtools-tab:not([selected]) {
- background-color: transparent;
-}
-#toolbox-option-container .devtools-tab {
- border-color: transparent;
- border-width: 0;
- padding-inline-start: 1px;
-}
-#toolbox-tab-options > image {
- margin: 0 8px;
-}
-
/* Invert the colors of certain dark theme images for displaying
* inside of the light theme.
*/
.theme-light .devtools-tab[icon-invertable] > image,
.theme-light .devtools-toolbarbutton > image,
.theme-light .devtools-button::before,
.theme-light #breadcrumb-separator-normal,
.theme-light .scrollbutton-up > .toolbarbutton-icon,
@@ -943,41 +619,31 @@
/* Since selected backgrounds are blue, we want to use the normal
* (light) icons. */
.theme-light .devtools-tab[icon-invertable][selected] > image,
.theme-light .devtools-tab[icon-invertable][highlighted] > image {
filter: none !important;
}
-.theme-light .command-button:hover {
- background-color: inherit;
-}
-
-.theme-light .command-button:hover:active,
-.theme-light .command-button[checked=true]:not(:hover) {
- background-color: inherit;
-}
-
.hidden-labels-box:not(.visible) > label,
.hidden-labels-box.visible ~ .hidden-labels-box > label:last-child {
display: none;
}
.devtools-invisible-splitter {
border-color: transparent;
background-color: transparent;
}
.devtools-horizontal-splitter,
.devtools-side-splitter {
background-color: var(--theme-splitter-color);
}
-
/* Throbbers */
.devtools-throbber::before {
content: "";
display: inline-block;
vertical-align: bottom;
margin-inline-end: 0.5em;
width: 1em;
height: 1em;
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -1,63 +1,370 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */
- /* Toolbox command buttons */
+:root {
+ --close-button-image: url(chrome://devtools/skin/images/close.svg);
+ --dock-bottom-image: url(chrome://devtools/skin/images/dock-bottom.svg);
+ --dock-side-image: url(chrome://devtools/skin/images/dock-side.svg);
+ --dock-undock-image: url(chrome://devtools/skin/images/dock-undock.svg);
+
+ --command-paintflashing-image: url(images/command-paintflashing.svg);
+ --command-screenshot-image: url(images/command-screenshot.svg);
+ --command-responsive-image: url(images/command-responsivemode.svg);
+ --command-scratchpad-image: url(images/command-scratchpad.svg);
+ --command-pick-image: url(images/command-pick.svg);
+ --command-frames-image: url(images/command-frames.svg);
+ --command-splitconsole-image: url(images/command-console.svg);
+ --command-noautohide-image: url(images/command-noautohide.svg);
+ --command-eyedropper-image: url(images/command-eyedropper.svg);
+ --command-rulers-image: url(images/command-rulers.svg);
+ --command-measure-image: url(images/command-measure.svg);
+}
+
+.theme-firebug {
+ --close-button-image: url(chrome://devtools/skin/images/firebug/close.svg);
+ --dock-bottom-image: url(chrome://devtools/skin/images/firebug/dock-bottom.svg);
+ --dock-side-image: url(chrome://devtools/skin/images/firebug/dock-side.svg);
+ --dock-undock-image: url(chrome://devtools/skin/images/firebug/dock-undock.svg);
+
+ --command-paintflashing-image: url(images/firebug/command-paintflashing.svg);
+ --command-screenshot-image: url(images/firebug/command-screenshot.svg);
+ --command-responsive-image: url(images/firebug/command-responsivemode.svg);
+ --command-scratchpad-image: url(images/firebug/command-scratchpad.svg);
+ --command-pick-image: url(images/firebug/command-pick.svg);
+ --command-frames-image: url(images/firebug/command-frames.svg);
+ --command-splitconsole-image: url(images/firebug/command-console.svg);
+ --command-noautohide-image: url(images/firebug/command-noautohide.svg);
+ --command-eyedropper-image: url(images/firebug/command-eyedropper.svg);
+ --command-rulers-image: url(images/firebug/command-rulers.svg);
+ --command-measure-image: url(images/firebug/command-measure.svg);
+}
+
+/* Toolbox tabbar */
+
+.devtools-tabbar {
+ -moz-appearance: none;
+ min-height: 24px;
+ border: 0px solid;
+ border-bottom-width: 1px;
+ padding: 0;
+ background: var(--theme-tab-toolbar-background);
+ border-bottom-color: var(--theme-splitter-color);
+}
+
+#toolbox-tabs {
+ margin: 0;
+}
+
+/* Toolbox tabs */
+
+.devtools-tab {
+ -moz-appearance: none;
+ -moz-binding: url("chrome://global/content/bindings/general.xml#control-item");
+ -moz-box-align: center;
+ min-width: 32px;
+ min-height: 24px;
+ max-width: 100px;
+ margin: 0;
+ padding: 0;
+ border-style: solid;
+ border-width: 0;
+ border-inline-start-width: 1px;
+ -moz-box-align: center;
+ -moz-user-focus: normal;
+ -moz-box-flex: 1;
+}
+
+/* Save space on the tab-strip in Firebug theme */
+.theme-firebug .devtools-tab {
+ -moz-box-flex: initial;
+}
+
+.theme-dark .devtools-tab {
+ color: var(--theme-body-color-alt);
+ border-color: #42484f;
+}
+
+.theme-light .devtools-tab {
+ color: var(--theme-body-color);
+ border-color: var(--theme-splitter-color);
+}
+
+.theme-dark .devtools-tab:hover {
+ color: #ced3d9;
+}
+
+.devtools-tab:hover {
+ background-color: var(--toolbar-tab-hover);
+}
+
+.theme-dark .devtools-tab:hover:active {
+ color: var(--theme-selection-color);
+}
+
+.devtools-tab:hover:active {
+ background-color: var(--toolbar-tab-hover-active);
+}
+
+.theme-dark .devtools-tab:not([selected])[highlighted] {
+ background-color: hsla(99, 100%, 14%, .3);
+}
+
+.theme-light .devtools-tab:not([selected])[highlighted] {
+ background-color: rgba(44, 187, 15, .2);
+}
+
+/* Display execution pointer in the Debugger tab to indicate
+ that the debugger is paused. */
+.theme-firebug #toolbox-tab-jsdebugger.devtools-tab:not([selected])[highlighted] {
+ background-color: rgba(89, 178, 234, .2);
+ background-image: url(chrome://devtools/skin/images/firebug/tool-debugger-paused.svg);
+ background-repeat: no-repeat;
+ padding-left: 13px !important;
+ background-position: 3px 6px;
+}
+
+.devtools-tab > image {
+ border: none;
+ margin: 0;
+ margin-inline-start: 4px;
+ opacity: 0.6;
+ max-height: 16px;
+ width: 16px; /* Prevents collapse during theme switching */
+}
+
+.devtools-tab > label {
+ white-space: nowrap;
+ margin: 0 4px;
+}
+
+.devtools-tab:hover > image {
+ opacity: 0.8;
+}
+
+.devtools-tab:active > image,
+.devtools-tab[selected] > image {
+ opacity: 1;
+}
+
+.devtools-tabbar .devtools-tab[selected],
+.devtools-tabbar .devtools-tab[selected]:hover:active {
+ color: var(--theme-selection-color);
+ background-color: var(--theme-selection-background);
+}
+
+#toolbox-tabs .devtools-tab[selected],
+#toolbox-tabs .devtools-tab[highlighted] {
+ border-width: 0;
+ padding-inline-start: 1px;
+}
+
+#toolbox-tabs .devtools-tab[selected]:last-child,
+#toolbox-tabs .devtools-tab[highlighted]:last-child {
+ padding-inline-end: 1px;
+}
+
+#toolbox-tabs .devtools-tab[selected] + .devtools-tab,
+#toolbox-tabs .devtools-tab[highlighted] + .devtools-tab {
+ border-inline-start-width: 0;
+ padding-inline-start: 1px;
+}
+
+#toolbox-tabs .devtools-tab:first-child[selected] {
+ border-inline-start-width: 0;
+}
+
+#toolbox-tabs .devtools-tab:last-child {
+ border-inline-end-width: 1px;
+}
+
+.devtools-tab:not([highlighted]) > .highlighted-icon,
+.devtools-tab[selected] > .highlighted-icon,
+.devtools-tab:not([selected])[highlighted] > .default-icon {
+ visibility: collapse;
+}
+
+/* The options tab is special - it doesn't have the same parent
+ as the other tabs (toolbox-option-container vs toolbox-tabs) */
+#toolbox-option-container .devtools-tab:not([selected]) {
+ background-color: transparent;
+}
+#toolbox-option-container .devtools-tab {
+ border-color: transparent;
+ border-width: 0;
+ padding-inline-start: 1px;
+}
+#toolbox-tab-options > image {
+ margin: 0 8px;
+}
+
+/* Toolbox controls */
+
+#toolbox-controls > button,
+#toolbox-dock-buttons > button {
+ -moz-appearance: none;
+ -moz-user-focus: normal;
+ border: none;
+ margin: 0 4px;
+ min-width: 16px;
+ width: 16px;
+}
+
+/* Save space in Firebug theme */
+.theme-firebug #toolbox-controls button {
+ margin-inline-start: 0 !important;
+ min-width: 12px;
+ margin: 0 1px;
+}
+
+#toolbox-close::before {
+ background-image: var(--close-button-image);
+}
+
+#toolbox-dock-bottom::before {
+ background-image: var(--dock-bottom-image);
+}
+
+#toolbox-dock-side::before {
+ background-image: var(--dock-side-image);
+}
+
+#toolbox-dock-window::before {
+ background-image: var(--dock-undock-image);
+}
+
+#toolbox-dock-bottom-minimize {
+ /* Bug 1177463 - The minimize button is currently hidden until we agree on
+ the UI for it, and until bug 1173849 is fixed too. */
+ display: none;
+}
+
+#toolbox-dock-bottom-minimize::before {
+ background-image: url("chrome://devtools/skin/images/dock-bottom-minimize@2x.png");
+}
+
+#toolbox-dock-bottom-minimize.minimized::before {
+ background-image: url("chrome://devtools/skin/images/dock-bottom-maximize@2x.png");
+}
+
+#toolbox-buttons:empty + .devtools-separator,
+.devtools-separator[invisible] {
+ visibility: hidden;
+}
+
+#toolbox-controls-separator {
+ margin: 0;
+}
+
+/* Command buttons */
+
+.command-button {
+ padding: 0;
+ margin: 0;
+ position: relative;
+ -moz-user-focus: normal;
+}
+
+.command-button::before {
+ opacity: 0.7;
+}
+
+.command-button:hover {
+ background-color: var(--toolbar-tab-hover);
+}
+
+.theme-light .command-button:hover {
+ background-color: inherit;
+}
+
+.command-button:hover:active,
+.command-button[checked=true]:not(:hover) {
+ background-color: var(--toolbar-tab-hover-active)
+}
+
+.theme-light .command-button:hover:active,
+.theme-light .command-button[checked=true]:not(:hover) {
+ background-color: inherit;
+}
+
+.command-button:hover::before {
+ opacity: 0.85;
+}
+
+.command-button:hover:active::before,
+.command-button[checked=true]::before,
+.command-button[open=true]::before {
+ opacity: 1;
+}
+
+/* Command button images */
#command-button-paintflashing::before {
- background-image: var(--command-paintflashing-image);
- }
+ background-image: var(--command-paintflashing-image);
+}
#command-button-screenshot::before {
- background-image: var(--command-screenshot-image);
- }
+ background-image: var(--command-screenshot-image);
+}
#command-button-responsive::before {
- background-image: var(--command-responsive-image);
+ background-image: var(--command-responsive-image);
}
#command-button-scratchpad::before {
- background-image: var(--command-scratchpad-image);
+ background-image: var(--command-scratchpad-image);
}
#command-button-pick::before {
- background-image: var(--command-pick-image);
+ background-image: var(--command-pick-image);
}
#command-button-splitconsole::before {
- background-image: var(--command-splitconsole-image);
+ background-image: var(--command-splitconsole-image);
}
#command-button-noautohide::before {
- background-image: var(--command-noautohide-image);
+ background-image: var(--command-noautohide-image);
}
#command-button-eyedropper::before {
- background-image: var(--command-eyedropper-image);
+ background-image: var(--command-eyedropper-image);
}
#command-button-rulers::before {
- background-image: var(--command-rulers-image);
+ background-image: var(--command-rulers-image);
}
#command-button-measure::before {
- background-image: var(--command-measure-image);
+ background-image: var(--command-measure-image);
}
#command-button-frames::before {
- background-image: var(--command-frames-image);
+ background-image: var(--command-frames-image);
}
#command-button-frames {
- background: url("chrome://devtools/skin/images/dropmarker.svg") no-repeat right;
+ background: url("chrome://devtools/skin/images/dropmarker.svg") no-repeat right;
- /* Override background-size from the command-button.
+ /* Override background-size from the command-button.
The drop down arrow is smaller */
- background-size: 8px 4px !important;
- min-width: 32px;
+ background-size: 8px 4px !important;
+ min-width: 32px;
}
#command-button-frames:-moz-dir(rtl) {
- background-position: left;
+ background-position: left;
}
+
+/* Toolbox panels */
+
+.toolbox-panel {
+ display: -moz-box;
+ -moz-box-flex: 1;
+ visibility: collapse;
+}
+
+.toolbox-panel[selected] {
+ visibility: visible;
+}
\ No newline at end of file
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -200,18 +200,23 @@
.tooltip-arrow {
position: relative;
height: 16px;
width: 32px;
overflow: hidden;
flex-shrink: 0;
}
-.tooltip-arrow:-moz-locale-dir(rtl) {
- align-self: flex-end;
+/* In RTL locales, only use RTL on the tooltip content, keep LTR for positioning */
+.tooltip-container:-moz-locale-dir(rtl) {
+ direction: ltr;
+}
+
+.tooltip-panel:-moz-locale-dir(rtl) {
+ direction: rtl;
}
.tooltip-top .tooltip-arrow {
margin-top: -3px;
}
.tooltip-bottom .tooltip-arrow {
margin-bottom: -3px;
--- a/devtools/client/webconsole/net/components/post-tab.js
+++ b/devtools/client/webconsole/net/components/post-tab.js
@@ -1,18 +1,17 @@
/* 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/. */
"use strict";
const React = require("devtools/client/shared/vendor/react");
// Reps
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
-const { parseURLEncodedText } = require("devtools/client/shared/components/reps/url");
+const { createFactories, parseURLEncodedText } = require("devtools/client/shared/components/reps/rep-utils");
const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
// Network
const NetInfoParams = React.createFactory(require("./net-info-params"));
const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
const Spinner = React.createFactory(require("./spinner"));
const SizeLimit = React.createFactory(require("./size-limit"));
--- a/devtools/client/webconsole/net/net-request.js
+++ b/devtools/client/webconsole/net/net-request.js
@@ -3,17 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// React
const React = require("devtools/client/shared/vendor/react");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
// Reps
-const { parseURLParams } = require("devtools/client/shared/components/reps/url");
+const { parseURLParams } = require("devtools/client/shared/components/reps/rep-utils");
// Network
const { cancelEvent, isLeftClick } = require("./utils/events");
const NetInfoBody = React.createFactory(require("./components/net-info-body"));
const DataProvider = require("./data-provider");
// Constants
const XHTML_NS = "http://www.w3.org/1999/xhtml";
--- a/devtools/server/actors/css-properties.js
+++ b/devtools/server/actors/css-properties.js
@@ -23,44 +23,57 @@ exports.CssPropertiesActor = ActorClassW
this.parent = parent;
},
destroy() {
Actor.prototype.destroy.call(this);
},
getCSSDatabase() {
- const db = {};
- const properties = DOMUtils.getCSSPropertyNames(DOMUtils.INCLUDE_ALIASES);
-
- properties.forEach(name => {
- // Get the list of CSS types this property supports.
- let supports = [];
- for (let type in CSS_TYPES) {
- if (safeCssPropertySupportsType(name, DOMUtils["TYPE_" + type])) {
- supports.push(CSS_TYPES[type]);
- }
- }
+ const properties = generateCssProperties();
+ const pseudoElements = DOMUtils.getCSSPseudoElementNames();
- // In order to maintain any backwards compatible changes when debugging older
- // clients, take the definition from the static CSS properties database, and fill it
- // in with the most recent property definition from the server.
- const clientDefinition = CSS_PROPERTIES[name] || {};
- const serverDefinition = {
- isInherited: DOMUtils.isInheritedProperty(name),
- supports
- };
- db[name] = Object.assign(clientDefinition, serverDefinition);
- });
-
- return db;
+ return { properties, pseudoElements };
}
});
/**
+ * Generate the CSS properties object. Every key is the property name, while
+ * the values are objects that contain information about that property.
+ *
+ * @return {Object}
+ */
+function generateCssProperties() {
+ const properties = {};
+ const propertyNames = DOMUtils.getCSSPropertyNames(DOMUtils.INCLUDE_ALIASES);
+
+ propertyNames.forEach(name => {
+ // Get the list of CSS types this property supports.
+ let supports = [];
+ for (let type in CSS_TYPES) {
+ if (safeCssPropertySupportsType(name, DOMUtils["TYPE_" + type])) {
+ supports.push(CSS_TYPES[type]);
+ }
+ }
+
+ // In order to maintain any backwards compatible changes when debugging older
+ // clients, take the definition from the static CSS properties database, and fill it
+ // in with the most recent property definition from the server.
+ const clientDefinition = CSS_PROPERTIES[name] || {};
+ const serverDefinition = {
+ isInherited: DOMUtils.isInheritedProperty(name),
+ supports
+ };
+ properties[name] = Object.assign(clientDefinition, serverDefinition);
+ });
+
+ return properties;
+}
+
+/**
* Test if a CSS is property is known using server-code.
*
* @param {string} name
* @return {Boolean}
*/
function isCssPropertyKnown(name) {
try {
// If the property name is unknown, the cssPropertyIsShorthand
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -166,31 +166,35 @@
}
/* Text container */
:-moz-native-anonymous .box-model-nodeinfobar-text {
overflow: hidden;
white-space: nowrap;
direction: ltr;
- text-align: center;
padding-bottom: 1px;
+ display: flex;
}
:-moz-native-anonymous .box-model-nodeinfobar-tagname {
color: hsl(285,100%, 75%);
}
:-moz-native-anonymous .box-model-nodeinfobar-id {
color: hsl(103, 46%, 54%);
+ overflow: hidden;
+ text-overflow: ellipsis;
}
:-moz-native-anonymous .box-model-nodeinfobar-classes,
:-moz-native-anonymous .box-model-nodeinfobar-pseudo-classes {
color: hsl(200, 74%, 57%);
+ overflow: hidden;
+ text-overflow: ellipsis;
}
:-moz-native-anonymous .box-model-nodeinfobar-dimensions {
color: hsl(210, 30%, 85%);
border-inline-start: 1px solid #5a6169;
margin-inline-start: 6px;
padding-inline-start: 6px;
}
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -377,16 +377,35 @@ BrowserTabList.prototype._getActorForBro
actor = new BrowserTabActor(this._connection, browser);
this._actorByBrowser.set(browser, actor);
this._checkListening();
return promise.resolve(actor);
};
BrowserTabList.prototype.getTab = function ({ outerWindowID, tabId }) {
if (typeof outerWindowID == "number") {
+ // First look for in-process frames with this ID
+ let window = Services.wm.getOuterWindowWithId(outerWindowID);
+ // Safety check to prevent debugging top level window via getTab
+ if (window instanceof Ci.nsIDOMChromeWindow) {
+ return promise.reject({
+ error: "forbidden",
+ message: "Window with outerWindowID '" + outerWindowID + "' is chrome"
+ });
+ }
+ if (window) {
+ let iframe = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .containerElement;
+ if (iframe) {
+ return this._getActorForBrowser(iframe);
+ }
+ }
+ // Then also look on registered <xul:browsers> when using outerWindowID for
+ // OOP tabs
for (let browser of this._getBrowsers()) {
if (browser.outerWindowID == outerWindowID) {
return this._getActorForBrowser(browser);
}
}
return promise.reject({
error: "noTab",
message: "Unable to find tab with outerWindowID '" + outerWindowID + "'"
@@ -1120,20 +1139,17 @@ TabActor.prototype = {
}
// Tell the thread actor that the tab is closed, so that it may terminate
// instead of resuming the debuggee script.
if (this._attached) {
this.threadActor._tabClosed = true;
}
- if (this._detach()) {
- this.conn.send({ from: this.actorID,
- type: "tabDetached" });
- }
+ this._detach();
Object.defineProperty(this, "docShell", {
value: null,
configurable: true
});
this._extraActors = null;
@@ -1515,16 +1531,19 @@ TabActor.prototype = {
if (this._workerActorPool !== null) {
this.conn.removeActorPool(this._workerActorPool);
this._workerActorPool = null;
}
this._attached = false;
+ this.conn.send({ from: this.actorID,
+ type: "tabDetached" });
+
return true;
},
// Protocol Request Handlers
onAttach(request) {
if (this.exited) {
return { type: "exited" };
--- a/devtools/shared/css-properties-db.js
+++ b/devtools/shared/css-properties-db.js
@@ -40,16 +40,31 @@ exports.COLOR_TAKING_FUNCTIONS = ["linea
*/
exports.ANGLE_TAKING_FUNCTIONS = ["linear-gradient", "-moz-linear-gradient",
"repeating-linear-gradient",
"-moz-repeating-linear-gradient", "rotate", "rotateX",
"rotateY", "rotateZ", "rotate3d", "skew", "skewX",
"skewY", "hue-rotate"];
/**
+ * The list of all CSS Pseudo Elements. This list can be generated from:
+ *
+ * let domUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+ * domUtils.getCSSPseudoElementNames();
+ */
+exports.PSEUDO_ELEMENTS = [":after", ":before", ":backdrop", ":first-letter",
+ ":first-line", ":-moz-selection", ":-moz-focus-inner",
+ ":-moz-focus-outer", ":-moz-list-bullet",
+ ":-moz-list-number", ":-moz-math-anonymous",
+ ":-moz-progress-bar", ":-moz-range-track",
+ ":-moz-range-progress", ":-moz-range-thumb",
+ ":-moz-meter-bar", ":-moz-placeholder",
+ ":-moz-color-swatch"];
+
+/**
* This list is generated from the output of the CssPropertiesActor. If a server
* does not support the actor, this is loaded as a backup. This list does not
* guarantee that the server actually supports these CSS properties.
*/
exports.CSS_PROPERTIES = {
"align-content": {
isInherited: false,
supports: []
@@ -1738,8 +1753,13 @@ exports.CSS_PROPERTIES = {
isInherited: false,
supports: []
},
"-webkit-user-select": {
isInherited: false,
supports: []
}
};
+
+exports.CSS_PROPERTIES_DB = {
+ properties: exports.CSS_PROPERTIES,
+ pseudoElements: exports.PSEUDO_ELEMENTS
+};
--- a/devtools/shared/fronts/css-properties.js
+++ b/devtools/shared/fronts/css-properties.js
@@ -1,17 +1,17 @@
/* 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/. */
"use strict";
const { FrontClassWithSpec, Front } = require("devtools/shared/protocol");
const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
const { Task } = require("devtools/shared/task");
-const { CSS_PROPERTIES } = require("devtools/shared/css-properties-db");
+const { CSS_PROPERTIES_DB } = require("devtools/shared/css-properties-db");
/**
* Build up a regular expression that matches a CSS variable token. This is an
* ident token that starts with two dashes "--".
*
* https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
*/
var NON_ASCII = "[^\\x00-\\x7F]";
@@ -48,24 +48,25 @@ const CssPropertiesFront = FrontClassWit
exports.CssPropertiesFront = CssPropertiesFront;
/**
* Ask questions to a CSS database. This class does not care how the database
* gets loaded in, only the questions that you can ask to it.
* Prototype functions are bound to 'this' so they can be passed around as helper
* functions.
*
- * @param {Array} propertiesList
- * A list of known properties.
+ * @param {Object} db
+ * A database of CSS properties
* @param {Object} inheritedList
* The key is the property name, the value is whether or not
* that property is inherited.
*/
-function CssProperties(properties) {
- this.properties = properties;
+function CssProperties(db) {
+ this.properties = db.properties;
+ this.pseudoElements = db.pseudoElements;
this.isKnown = this.isKnown.bind(this);
this.isInherited = this.isInherited.bind(this);
this.supportsType = this.supportsType.bind(this);
}
CssProperties.prototype = {
/**
@@ -123,31 +124,41 @@ exports.initCssProperties = Task.async(f
let db, front;
// Get the list dynamically if the cssProperties actor exists.
if (toolbox.target.hasActor("cssProperties")) {
front = CssPropertiesFront(client, toolbox.target.form);
db = yield front.getCSSDatabase();
- // Even if the target has the cssProperties actor, it may not be the latest version.
- // So, the "supports" data may be missing.
- // Start with the server's list (because that's the correct one), and add the supports
- // information if required.
- if (!db.color.supports) {
- for (let name in db) {
- if (typeof CSS_PROPERTIES[name] === "object") {
- db[name].supports = CSS_PROPERTIES[name].supports;
+ // Even if the target has the cssProperties actor, the returned data may
+ // not be in the same shape or have all of the data we need. The following
+ // code normalizes this data.
+
+ // Firefox 49's getCSSDatabase() just returned the properties object, but
+ // now it returns an object with multiple types of CSS information.
+ if (!db.properties) {
+ db = { properties: db };
+ }
+
+ // Fill in any missing DB information from the static database.
+ db = Object.assign({}, CSS_PROPERTIES_DB, db);
+
+ // Add "supports" information to the css properties if it's missing.
+ if (!db.properties.color.supports) {
+ for (let name in db.properties) {
+ if (typeof CSS_PROPERTIES_DB.properties[name] === "object") {
+ db.properties[name].supports = CSS_PROPERTIES_DB.properties[name].supports;
}
}
}
} else {
// The target does not support this actor, so require a static list of supported
// properties.
- db = CSS_PROPERTIES;
+ db = CSS_PROPERTIES_DB;
}
const cssProperties = new CssProperties(db);
cachedCssProperties.set(client, {cssProperties, front});
return {cssProperties, front};
});
/**
new file mode 100644
--- /dev/null
+++ b/devtools/shared/tests/unit/test_css-properties-db.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that css-properties-db matches platform.
+
+"use strict";
+
+const DOMUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"]
+ .getService(Components.interfaces.inIDOMUtils);
+
+const {PSEUDO_ELEMENTS} = require("devtools/shared/css-properties-db");
+
+function run_test() {
+ // Check that the platform and client match for pseudo elements.
+ let foundPseudoElements = 0;
+ const platformPseudoElements = DOMUtils.getCSSPseudoElementNames();
+ const instructions = "If this assertion fails then it means that pseudo elements " +
+ "have been added, removed, or changed on the platform and need " +
+ "to be updated on the static client-side list of pseudo " +
+ "elements within the devtools. " +
+ "See devtools/shared/css-properties-db.js and " +
+ "exports.PSEUDO_ELEMENTS for information on how to update the " +
+ "list of pseudo elements to fix this test.";
+
+ for (let element of PSEUDO_ELEMENTS) {
+ const hasElement = platformPseudoElements.includes(element);
+ ok(hasElement,
+ `"${element}" pseudo element from the client-side CSS properties database was ` +
+ `found on the platform. ${instructions}`);
+ foundPseudoElements += hasElement ? 1 : 0;
+ }
+
+ equal(foundPseudoElements, platformPseudoElements.length,
+ `The client side CSS properties database of psuedo element names should match ` +
+ `those found on the platform. ${instructions}`);
+}
--- a/devtools/shared/tests/unit/xpcshell.ini
+++ b/devtools/shared/tests/unit/xpcshell.ini
@@ -4,16 +4,17 @@ head = head_devtools.js
tail =
firefox-appdir = browser
skip-if = toolkit == 'android' || toolkit == 'gonk'
support-files =
exposeLoader.js
[test_assert.js]
[test_csslexer.js]
+[test_css-properties-db.js]
[test_fetch-chrome.js]
[test_fetch-file.js]
[test_fetch-http.js]
[test_fetch-resource.js]
[test_flatten.js]
[test_indentation.js]
[test_independent_loaders.js]
[test_invisible_loader.js]
--- a/docshell/base/nsAboutRedirector.cpp
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -60,17 +60,18 @@ static RedirEntry kRedirMap[] = {
#ifdef MOZ_DEVTOOLS_ALL
{
"debugging", "chrome://devtools/content/aboutdebugging/aboutdebugging.xhtml",
nsIAboutModule::ALLOW_SCRIPT
},
#endif
{
"license", "chrome://global/content/license.html",
- nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::MAKE_LINKABLE
},
{
"logo", "chrome://branding/content/about.png",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
// Linkable for testing reasons.
nsIAboutModule::MAKE_LINKABLE
},
{
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -762,30 +762,29 @@ nsDocShell::nsDocShell()
, mAppType(nsIDocShell::APP_TYPE_UNKNOWN)
, mLoadType(0)
, mMarginWidth(-1)
, mMarginHeight(-1)
, mItemType(typeContent)
, mPreviousTransIndex(-1)
, mLoadedTransIndex(-1)
, mSandboxFlags(0)
+ , mOrientationLock(eScreenOrientation_None)
, mFullscreenAllowed(CHECK_ATTRIBUTES)
- , mOrientationLock(eScreenOrientation_None)
, mCreated(false)
, mAllowSubframes(true)
, mAllowPlugins(true)
, mAllowJavascript(true)
, mAllowMetaRedirects(true)
, mAllowImages(true)
, mAllowMedia(true)
, mAllowDNSPrefetch(true)
, mAllowWindowControl(true)
, mAllowContentRetargeting(true)
, mAllowContentRetargetingOnChildren(true)
- , mCreatingDocument(false)
, mUseErrorPages(false)
, mObserveErrorPages(true)
, mAllowAuth(true)
, mAllowKeywordFixup(false)
, mIsOffScreenBrowser(false)
, mIsActive(true)
, mDisableMetaRefreshWhenInactive(false)
, mIsPrerendered(false)
@@ -798,24 +797,25 @@ nsDocShell::nsDocShell()
, mCanExecuteScripts(false)
, mFiredUnloadEvent(false)
, mEODForCurrentDocument(false)
, mURIResultedInDocument(false)
, mIsBeingDestroyed(false)
, mIsExecutingOnLoadHandler(false)
, mIsPrintingOrPP(false)
, mSavingOldViewer(false)
+ , mAffectPrivateSessionLifetime(true)
+ , mInvisible(false)
+ , mHasLoadedNonBlankURI(false)
+ , mBlankTiming(false)
+ , mCreatingDocument(false)
#ifdef DEBUG
, mInEnsureScriptEnv(false)
#endif
- , mAffectPrivateSessionLifetime(true)
- , mInvisible(false)
- , mHasLoadedNonBlankURI(false)
, mDefaultLoadFlags(nsIRequest::LOAD_NORMAL)
- , mBlankTiming(false)
, mFrameType(FRAME_TYPE_REGULAR)
, mPrivateBrowsingId(0)
, mParentCharsetSource(0)
, mJSRunToCompletionDepth(0)
{
AssertOriginAttributesMatchPrivateBrowsing();
mHistoryID = ++gDocshellIDCounter;
if (gDocShellCount++ == 0) {
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -893,120 +893,124 @@ protected:
// Index into the SHTransaction list, indicating the previous and current
// transaction at the time that this DocShell begins to load
int32_t mPreviousTransIndex;
int32_t mLoadedTransIndex;
uint32_t mSandboxFlags;
nsWeakPtr mOnePermittedSandboxedNavigator;
+ // The orientation lock as described by
+ // https://w3c.github.io/screen-orientation/
+ mozilla::dom::ScreenOrientationInternal mOrientationLock;
+
// mFullscreenAllowed stores how we determine whether fullscreen is allowed
// when GetFullscreenAllowed() is called. Fullscreen is allowed in a
// docshell when all containing iframes have the allowfullscreen
// attribute set to true. When mFullscreenAllowed is CHECK_ATTRIBUTES
// we check this docshell's containing frame for the allowfullscreen
// attribute, and recurse onto the parent docshell to ensure all containing
// frames also have the allowfullscreen attribute. If we find an ancestor
// docshell with mFullscreenAllowed not equal to CHECK_ATTRIBUTES, we've
// reached a content boundary, and mFullscreenAllowed denotes whether the
// parent across the content boundary has allowfullscreen=true in all its
// containing iframes. mFullscreenAllowed defaults to CHECK_ATTRIBUTES and
// is set otherwise when docshells which are content boundaries are created.
- enum FullscreenAllowedState
+ enum FullscreenAllowedState : uint8_t
{
CHECK_ATTRIBUTES,
PARENT_ALLOWS,
PARENT_PROHIBITS
};
FullscreenAllowedState mFullscreenAllowed;
- // The orientation lock as described by
- // https://w3c.github.io/screen-orientation/
- mozilla::dom::ScreenOrientationInternal mOrientationLock;
-
// Cached value of the "browser.xul.error_pages.enabled" preference.
static bool sUseErrorPages;
- bool mCreated;
- bool mAllowSubframes;
- bool mAllowPlugins;
- bool mAllowJavascript;
- bool mAllowMetaRedirects;
- bool mAllowImages;
- bool mAllowMedia;
- bool mAllowDNSPrefetch;
- bool mAllowWindowControl;
- bool mAllowContentRetargeting;
- bool mAllowContentRetargetingOnChildren;
- bool mCreatingDocument; // (should be) debugging only
- bool mUseErrorPages;
- bool mObserveErrorPages;
- bool mAllowAuth;
- bool mAllowKeywordFixup;
- bool mIsOffScreenBrowser;
- bool mIsActive;
- bool mDisableMetaRefreshWhenInactive;
- bool mIsPrerendered;
- bool mIsAppTab;
- bool mUseGlobalHistory;
- bool mUseRemoteTabs;
- bool mDeviceSizeIsPageSize;
- bool mWindowDraggingAllowed;
- bool mInFrameSwap;
+ bool mCreated : 1;
+ bool mAllowSubframes : 1;
+ bool mAllowPlugins : 1;
+ bool mAllowJavascript : 1;
+ bool mAllowMetaRedirects : 1;
+ bool mAllowImages : 1;
+ bool mAllowMedia : 1;
+ bool mAllowDNSPrefetch : 1;
+ bool mAllowWindowControl : 1;
+ bool mAllowContentRetargeting : 1;
+ bool mAllowContentRetargetingOnChildren : 1;
+ bool mUseErrorPages : 1;
+ bool mObserveErrorPages : 1;
+ bool mAllowAuth : 1;
+ bool mAllowKeywordFixup : 1;
+ bool mIsOffScreenBrowser : 1;
+ bool mIsActive : 1;
+ bool mDisableMetaRefreshWhenInactive : 1;
+ bool mIsPrerendered : 1;
+ bool mIsAppTab : 1;
+ bool mUseGlobalHistory : 1;
+ bool mUseRemoteTabs : 1;
+ bool mDeviceSizeIsPageSize : 1;
+ bool mWindowDraggingAllowed : 1;
+ bool mInFrameSwap : 1;
// Because scriptability depends on the mAllowJavascript values of our
// ancestors, we cache the effective scriptability and recompute it when
// it might have changed;
- bool mCanExecuteScripts;
+ bool mCanExecuteScripts : 1;
void RecomputeCanExecuteScripts();
// This boolean is set to true right before we fire pagehide and generally
// unset when we embed a new content viewer. While it's true no navigation
// is allowed in this docshell.
- bool mFiredUnloadEvent;
+ bool mFiredUnloadEvent : 1;
// this flag is for bug #21358. a docshell may load many urls
// which don't result in new documents being created (i.e. a new
// content viewer) we want to make sure we don't call a on load
// event more than once for a given content viewer.
- bool mEODForCurrentDocument;
- bool mURIResultedInDocument;
+ bool mEODForCurrentDocument : 1;
+ bool mURIResultedInDocument : 1;
- bool mIsBeingDestroyed;
+ bool mIsBeingDestroyed : 1;
- bool mIsExecutingOnLoadHandler;
+ bool mIsExecutingOnLoadHandler : 1;
// Indicates that a DocShell in this "docshell tree" is printing
- bool mIsPrintingOrPP;
+ bool mIsPrintingOrPP : 1;
// Indicates to CreateContentViewer() that it is safe to cache the old
// presentation of the page, and to SetupNewViewer() that the old viewer
// should be passed a SHEntry to save itself into.
- bool mSavingOldViewer;
+ bool mSavingOldViewer : 1;
// @see nsIDocShellHistory::createdDynamically
- bool mDynamicallyCreated;
+ bool mDynamicallyCreated : 1;
+ bool mAffectPrivateSessionLifetime : 1;
+ bool mInvisible : 1;
+ bool mHasLoadedNonBlankURI : 1;
+
+ // This flag means that mTiming has been initialized but nulled out.
+ // We will check the innerWin's timing before creating a new one
+ // in MaybeInitTiming()
+ bool mBlankTiming : 1;
+
+ // The following two fields cannot be declared as bit fields
+ // because of uses with AutoRestore.
+ bool mCreatingDocument; // (should be) debugging only
#ifdef DEBUG
bool mInEnsureScriptEnv;
#endif
- bool mAffectPrivateSessionLifetime;
- bool mInvisible;
- bool mHasLoadedNonBlankURI;
+
uint64_t mHistoryID;
uint32_t mDefaultLoadFlags;
static nsIURIFixup* sURIFixup;
RefPtr<nsDOMNavigationTiming> mTiming;
- // This flag means that mTiming has been initialized but nulled out.
- // We will check the innerWin's timing before creating a new one
- // in MaybeInitTiming()
- bool mBlankTiming;
-
// Are we a regular frame, a browser frame, or an app frame?
uint32_t mFrameType;
// This represents the state of private browsing in the docshell.
// Currently treated as a binary value: 1 - in private mode, 0 - not private mode
// On content docshells mPrivateBrowsingId == mOriginAttributes.mPrivateBrowsingId
// On chrome docshells this value will be set, but not have the corresponding
// origin attribute set.
--- a/dom/base/FormData.cpp
+++ b/dom/base/FormData.cpp
@@ -124,48 +124,54 @@ FormData::Append(const nsAString& aName,
if (NS_WARN_IF(aRv.Failed())) {
return;
}
AddNameBlobOrNullPair(aName, file);
}
void
+FormData::Append(const nsAString& aName, Directory* aDirectory)
+{
+ AddNameDirectoryPair(aName, aDirectory);
+}
+
+void
FormData::Delete(const nsAString& aName)
{
// We have to use this slightly awkward for loop since uint32_t >= 0 is an
// error for being always true.
for (uint32_t i = mFormData.Length(); i-- > 0; ) {
if (aName.Equals(mFormData[i].name)) {
mFormData.RemoveElementAt(i);
}
}
}
void
FormData::Get(const nsAString& aName,
- Nullable<OwningBlobOrUSVString>& aOutValue)
+ Nullable<OwningBlobOrDirectoryOrUSVString>& aOutValue)
{
for (uint32_t i = 0; i < mFormData.Length(); ++i) {
if (aName.Equals(mFormData[i].name)) {
aOutValue.SetValue() = mFormData[i].value;
return;
}
}
aOutValue.SetNull();
}
void
FormData::GetAll(const nsAString& aName,
- nsTArray<OwningBlobOrUSVString>& aValues)
+ nsTArray<OwningBlobOrDirectoryOrUSVString>& aValues)
{
for (uint32_t i = 0; i < mFormData.Length(); ++i) {
if (aName.Equals(mFormData[i].name)) {
- OwningBlobOrUSVString* element = aValues.AppendElement();
+ OwningBlobOrDirectoryOrUSVString* element = aValues.AppendElement();
*element = mFormData[i].value;
}
}
}
bool
FormData::Has(const nsAString& aName)
{
@@ -195,16 +201,26 @@ FormData::AddNameBlobOrNullPair(const ns
return rv.StealNSResult();
}
FormDataTuple* data = mFormData.AppendElement();
SetNameFilePair(data, aName, file);
return NS_OK;
}
+nsresult
+FormData::AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory)
+{
+ MOZ_ASSERT(aDirectory);
+
+ FormDataTuple* data = mFormData.AppendElement();
+ SetNameDirectoryPair(data, aName, aDirectory);
+ return NS_OK;
+}
+
FormData::FormDataTuple*
FormData::RemoveAllOthersAndGetFirstFormDataTuple(const nsAString& aName)
{
FormDataTuple* lastFoundTuple = nullptr;
uint32_t lastFoundIndex = mFormData.Length();
// We have to use this slightly awkward for loop since uint32_t >= 0 is an
// error for being always true.
for (uint32_t i = mFormData.Length(); i-- > 0; ) {
@@ -260,17 +276,17 @@ FormData::GetIterableLength() const
const nsAString&
FormData::GetKeyAtIndex(uint32_t aIndex) const
{
MOZ_ASSERT(aIndex < mFormData.Length());
return mFormData[aIndex].name;
}
-const OwningBlobOrUSVString&
+const OwningBlobOrDirectoryOrUSVString&
FormData::GetValueAtIndex(uint32_t aIndex) const
{
MOZ_ASSERT(aIndex < mFormData.Length());
return mFormData[aIndex].value;
}
void
FormData::SetNameValuePair(FormDataTuple* aData,
@@ -292,16 +308,29 @@ FormData::SetNameFilePair(FormDataTuple*
MOZ_ASSERT(aData);
MOZ_ASSERT(aFile);
aData->name = aName;
aData->wasNullBlob = false;
aData->value.SetAsBlob() = aFile;
}
+void
+FormData::SetNameDirectoryPair(FormDataTuple* aData,
+ const nsAString& aName,
+ Directory* aDirectory)
+{
+ MOZ_ASSERT(aData);
+ MOZ_ASSERT(aDirectory);
+
+ aData->name = aName;
+ aData->wasNullBlob = false;
+ aData->value.SetAsDirectory() = aDirectory;
+}
+
// -------------------------------------------------------------------------
// nsIDOMFormData
NS_IMETHODIMP
FormData::Append(const nsAString& aName, nsIVariant* aValue)
{
uint16_t dataType;
nsresult rv = aValue->GetDataType(&dataType);
@@ -376,20 +405,23 @@ FormData::GetSendInfo(nsIInputStream** a
for (uint32_t i = 0; i < mFormData.Length(); ++i) {
if (mFormData[i].wasNullBlob) {
MOZ_ASSERT(mFormData[i].value.IsUSVString());
fs.AddNameBlobOrNullPair(mFormData[i].name, nullptr);
} else if (mFormData[i].value.IsUSVString()) {
fs.AddNameValuePair(mFormData[i].name,
mFormData[i].value.GetAsUSVString());
- } else {
- MOZ_ASSERT(mFormData[i].value.IsBlob());
+ } else if (mFormData[i].value.IsBlob()) {
fs.AddNameBlobOrNullPair(mFormData[i].name,
mFormData[i].value.GetAsBlob());
+ } else {
+ MOZ_ASSERT(mFormData[i].value.IsDirectory());
+ fs.AddNameDirectoryPair(mFormData[i].name,
+ mFormData[i].value.GetAsDirectory());
}
}
fs.GetContentType(aContentType);
aCharset.Truncate();
*aContentLength = 0;
NS_ADDREF(*aBody = fs.GetSubmissionBody(aContentLength));
--- a/dom/base/FormData.h
+++ b/dom/base/FormData.h
@@ -31,33 +31,37 @@ class FormData final : public nsIDOMForm
{
private:
~FormData() {}
struct FormDataTuple
{
nsString name;
bool wasNullBlob;
- OwningBlobOrUSVString value;
+ OwningBlobOrDirectoryOrUSVString value;
};
// Returns the FormDataTuple to modify. This may be null, in which case
// no element with aName was found.
FormDataTuple*
RemoveAllOthersAndGetFirstFormDataTuple(const nsAString& aName);
void SetNameValuePair(FormDataTuple* aData,
const nsAString& aName,
const nsAString& aValue,
bool aWasNullBlob = false);
void SetNameFilePair(FormDataTuple* aData,
const nsAString& aName,
File* aFile);
+ void SetNameDirectoryPair(FormDataTuple* aData,
+ const nsAString& aName,
+ Directory* aDirectory);
+
public:
explicit FormData(nsISupports* aOwner = nullptr);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(FormData,
nsIDOMFormData)
NS_DECL_NSIDOMFORMDATA
@@ -75,57 +79,63 @@ public:
static already_AddRefed<FormData>
Constructor(const GlobalObject& aGlobal,
const Optional<NonNull<HTMLFormElement> >& aFormElement,
ErrorResult& aRv);
void Append(const nsAString& aName, const nsAString& aValue,
ErrorResult& aRv);
+
void Append(const nsAString& aName, Blob& aBlob,
const Optional<nsAString>& aFilename,
ErrorResult& aRv);
+ void Append(const nsAString& aName, Directory* aDirectory);
+
void Delete(const nsAString& aName);
- void Get(const nsAString& aName, Nullable<OwningBlobOrUSVString>& aOutValue);
+ void Get(const nsAString& aName, Nullable<OwningBlobOrDirectoryOrUSVString>& aOutValue);
- void GetAll(const nsAString& aName, nsTArray<OwningBlobOrUSVString>& aValues);
+ void GetAll(const nsAString& aName, nsTArray<OwningBlobOrDirectoryOrUSVString>& aValues);
bool Has(const nsAString& aName);
void Set(const nsAString& aName, Blob& aBlob,
const Optional<nsAString>& aFilename,
ErrorResult& aRv);
void Set(const nsAString& aName, const nsAString& aValue,
ErrorResult& aRv);
uint32_t GetIterableLength() const;
const nsAString& GetKeyAtIndex(uint32_t aIndex) const;
- const OwningBlobOrUSVString& GetValueAtIndex(uint32_t aIndex) const;
+ const OwningBlobOrDirectoryOrUSVString& GetValueAtIndex(uint32_t aIndex) const;
// HTMLFormSubmission
virtual nsresult
GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
virtual nsresult AddNameValuePair(const nsAString& aName,
const nsAString& aValue) override
{
FormDataTuple* data = mFormData.AppendElement();
SetNameValuePair(data, aName, aValue);
return NS_OK;
}
virtual nsresult AddNameBlobOrNullPair(const nsAString& aName,
Blob* aBlob) override;
+ virtual nsresult AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory) override;
+
typedef bool (*FormDataEntryCallback)(const nsString& aName,
- const OwningBlobOrUSVString& aValue,
+ const OwningBlobOrDirectoryOrUSVString& aValue,
void* aClosure);
uint32_t
Length() const
{
return mFormData.Length();
}
--- a/dom/base/IframeSandboxKeywordList.h
+++ b/dom/base/IframeSandboxKeywordList.h
@@ -18,8 +18,10 @@ SANDBOX_KEYWORD("allow-top-navigation",
SANDBOXED_TOPLEVEL_NAVIGATION)
SANDBOX_KEYWORD("allow-pointer-lock", allowpointerlock, SANDBOXED_POINTER_LOCK)
SANDBOX_KEYWORD("allow-orientation-lock", alloworientationlock,
SANDBOXED_ORIENTATION_LOCK)
SANDBOX_KEYWORD("allow-popups", allowpopups, SANDBOXED_AUXILIARY_NAVIGATION)
SANDBOX_KEYWORD("allow-modals", allowmodals, SANDBOXED_MODALS)
SANDBOX_KEYWORD("allow-popups-to-escape-sandbox", allowpopupstoescapesandbox,
SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS)
+SANDBOX_KEYWORD("allow-presentation", allowpresentation,
+ SANDBOXED_PRESENTATION)
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -738,23 +738,21 @@ WriteDirectory(JSStructuredCloneWriter*
nsAutoString path;
aDirectory->GetFullRealPath(path);
size_t charSize = sizeof(nsString::char_type);
return JS_WriteUint32Pair(aWriter, SCTAG_DOM_DIRECTORY, path.Length()) &&
JS_WriteBytes(aWriter, path.get(), path.Length() * charSize);
}
-JSObject*
-ReadDirectory(JSContext* aCx,
- JSStructuredCloneReader* aReader,
- uint32_t aPathLength,
- StructuredCloneHolder* aHolder)
+already_AddRefed<Directory>
+ReadDirectoryInternal(JSStructuredCloneReader* aReader,
+ uint32_t aPathLength,
+ StructuredCloneHolder* aHolder)
{
- MOZ_ASSERT(aCx);
MOZ_ASSERT(aReader);
MOZ_ASSERT(aHolder);
nsAutoString path;
path.SetLength(aPathLength);
size_t charSize = sizeof(nsString::char_type);
if (!JS_ReadBytes(aReader, (void*) path.BeginWriting(),
aPathLength * charSize)) {
@@ -763,25 +761,43 @@ ReadDirectory(JSContext* aCx,
nsCOMPtr<nsIFile> file;
nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
+ RefPtr<Directory> directory =
+ Directory::Create(aHolder->ParentDuringRead(), file);
+ return directory.forget();
+}
+
+JSObject*
+ReadDirectory(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aPathLength,
+ StructuredCloneHolder* aHolder)
+{
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aReader);
+ MOZ_ASSERT(aHolder);
+
// RefPtr<Directory> needs to go out of scope before toObject() is
// called because the static analysis thinks dereferencing XPCOM objects
// can GC (because in some cases it can!), and a return statement with a
// JSObject* type means that JSObject* is on the stack as a raw pointer
// while destructors are running.
JS::Rooted<JS::Value> val(aCx);
{
RefPtr<Directory> directory =
- Directory::Create(aHolder->ParentDuringRead(), file);
+ ReadDirectoryInternal(aReader, aPathLength, aHolder);
+ if (!directory) {
+ return nullptr;
+ }
if (!ToJSValue(aCx, directory, &val)) {
return nullptr;
}
}
return &val.toObject();
}
@@ -921,16 +937,25 @@ ReadFormData(JSContext* aCx,
ErrorResult rv;
formData->Append(name, *blob, thirdArg, rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
return nullptr;
}
+ } else if (tag == SCTAG_DOM_DIRECTORY) {
+ RefPtr<Directory> directory =
+ ReadDirectoryInternal(aReader, indexOrLengthOfString, aHolder);
+ if (!directory) {
+ return nullptr;
+ }
+
+ formData->Append(name, directory);
+
} else {
MOZ_ASSERT(tag == 0);
nsAutoString value;
value.SetLength(indexOrLengthOfString);
size_t charSize = sizeof(nsString::char_type);
if (!JS_ReadBytes(aReader, (void*) value.BeginWriting(),
indexOrLengthOfString * charSize)) {
@@ -956,16 +981,19 @@ ReadFormData(JSContext* aCx,
// The format of the FormData serialization is:
// - pair of ints: SCTAG_DOM_FORMDATA, Length of the FormData elements
// - for each Element element:
// - name string
// - if it's a blob:
// - pair of ints: SCTAG_DOM_BLOB, index of the BlobImpl in the array
// mBlobImplArray.
+// - if it's a directory (See WriteDirectory):
+// - pair of ints: SCTAG_DOM_DIRECTORY, path length
+// - path as string
// - else:
// - pair of ints: 0, string length
// - value string
bool
WriteFormData(JSStructuredCloneWriter* aWriter,
FormData* aFormData,
StructuredCloneHolder* aHolder)
{
@@ -986,17 +1014,17 @@ WriteFormData(JSStructuredCloneWriter* a
public:
Closure(JSStructuredCloneWriter* aWriter,
StructuredCloneHolder* aHolder)
: mWriter(aWriter),
mHolder(aHolder)
{ }
static bool
- Write(const nsString& aName, const OwningBlobOrUSVString& aValue,
+ Write(const nsString& aName, const OwningBlobOrDirectoryOrUSVString& aValue,
void* aClosure)
{
Closure* closure = static_cast<Closure*>(aClosure);
if (!WriteString(closure->mWriter, aName)) {
return false;
}
if (aValue.IsBlob()) {
@@ -1005,16 +1033,28 @@ WriteFormData(JSStructuredCloneWriter* a
closure->mHolder->BlobImpls().Length())) {
return false;
}
closure->mHolder->BlobImpls().AppendElement(blobImpl);
return true;
}
+ if (aValue.IsDirectory()) {
+ Directory* directory = aValue.GetAsDirectory();
+
+ if (closure->mHolder->SupportedContext() !=
+ StructuredCloneHolder::SameProcessSameThread &&
+ !directory->ClonableToDifferentThreadOrProcess()) {
+ return false;
+ }
+
+ return WriteDirectory(closure->mWriter, directory);
+ }
+
size_t charSize = sizeof(nsString::char_type);
if (!JS_WriteUint32Pair(closure->mWriter, 0,
aValue.GetAsUSVString().Length()) ||
!JS_WriteBytes(closure->mWriter, aValue.GetAsUSVString().get(),
aValue.GetAsUSVString().Length() * charSize)) {
return false;
}
--- a/dom/base/StructuredCloneHolder.h
+++ b/dom/base/StructuredCloneHolder.h
@@ -189,16 +189,21 @@ public:
}
nsTArray<RefPtr<BlobImpl>>& BlobImpls()
{
MOZ_ASSERT(mSupportsCloning, "Blobs cannot be taken/set if cloning is not supported.");
return mBlobImplArray;
}
+ ContextSupport SupportedContext() const
+ {
+ return mSupportedContext;
+ }
+
// The parent object is set internally just during the Read(). This method
// can be used by read functions to retrieve it.
nsISupports* ParentDuringRead() const
{
return mParent;
}
// This must be called if the transferring has ports generated by Read().
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -79,16 +79,17 @@ GK_ATOM(allowevents, "allowevents")
GK_ATOM(allownegativeassertions, "allownegativeassertions")
GK_ATOM(allowforms,"allow-forms")
GK_ATOM(allowfullscreen, "allowfullscreen")
GK_ATOM(allowmodals, "allow-modals")
GK_ATOM(alloworientationlock,"allow-orientation-lock")
GK_ATOM(allowpointerlock,"allow-pointer-lock")
GK_ATOM(allowpopupstoescapesandbox,"allow-popups-to-escape-sandbox")
GK_ATOM(allowpopups,"allow-popups")
+GK_ATOM(allowpresentation,"allow-presentation")
GK_ATOM(allowsameorigin,"allow-same-origin")
GK_ATOM(allowscripts,"allow-scripts")
GK_ATOM(allowtopnavigation,"allow-top-navigation")
GK_ATOM(allowuntrusted, "allowuntrusted")
GK_ATOM(alt, "alt")
GK_ATOM(alternate, "alternate")
GK_ATOM(always, "always")
GK_ATOM(ancestor, "ancestor")
--- a/dom/base/nsSandboxFlags.h
+++ b/dom/base/nsSandboxFlags.h
@@ -103,10 +103,15 @@ const unsigned long SANDBOXED_MODALS = 0
*/
const unsigned long SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS = 0x1000;
/**
* This flag prevents locking screen orientation.
*/
const unsigned long SANDBOXED_ORIENTATION_LOCK = 0x2000;
-const unsigned long SANDBOX_ALL_FLAGS = 0x3FFF;
+/**
+ * This flag disables the Presentation API.
+ */
+const unsigned long SANDBOXED_PRESENTATION = 0x4000;
+
+const unsigned long SANDBOX_ALL_FLAGS = 0x7FFF;
#endif
--- a/dom/bindings/CallbackInterface.cpp
+++ b/dom/bindings/CallbackInterface.cpp
@@ -11,17 +11,17 @@
namespace mozilla {
namespace dom {
bool
CallbackInterface::GetCallableProperty(JSContext* cx, JS::Handle<jsid> aPropId,
JS::MutableHandle<JS::Value> aCallable)
{
- if (!JS_GetPropertyById(cx, CallbackPreserveColor(), aPropId, aCallable)) {
+ if (!JS_GetPropertyById(cx, CallbackKnownNotGray(), aPropId, aCallable)) {
return false;
}
if (!aCallable.isObject() ||
!JS::IsCallable(&aCallable.toObject())) {
char* propName =
JS_EncodeString(cx, JS_FORGET_STRING_FLATNESS(JSID_TO_FLAT_STRING(aPropId)));
nsPrintfCString description("Property '%s'", propName);
JS_free(cx, propName);
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -114,16 +114,27 @@ public:
*/
JS::Handle<JSObject*> CallbackPreserveColor() const
{
// Calling fromMarkedLocation() is safe because we trace our mCallback, and
// because the value of mCallback cannot change after if has been set.
return JS::Handle<JSObject*>::fromMarkedLocation(mCallback.address());
}
+ /*
+ * If the callback is known to be non-gray, then this method can be
+ * used instead of Callback() to avoid the overhead of
+ * ExposeObjectToActiveJS().
+ */
+ JS::Handle<JSObject*> CallbackKnownNotGray() const
+ {
+ MOZ_ASSERT(!JS::ObjectIsMarkedGray(mCallback.get()));
+ return CallbackPreserveColor();
+ }
+
nsIGlobalObject* IncumbentGlobalOrNull() const
{
return mIncumbentGlobal;
}
enum ExceptionHandling {
// Report any exception and don't throw it to the caller code.
eReportExceptions,
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -15042,22 +15042,22 @@ class FakeMember():
# Claim to be a [NewObject] so we can avoid the "return a raw pointer"
# comments CGNativeMember codegen would otherwise stick in.
if name == "NewObject":
return True
return None
class CallbackMember(CGNativeMember):
- # XXXbz It's OK to use CallbackPreserveColor for wrapScope because
+ # XXXbz It's OK to use CallbackKnownNotGray for wrapScope because
# CallSetup already handled the unmark-gray bits for us. we don't have
# anything better to use for 'obj', really...
def __init__(self, sig, name, descriptorProvider, needThisHandling,
rethrowContentException=False, typedArraysAreStructs=False,
- wrapScope='CallbackPreserveColor()'):
+ wrapScope='CallbackKnownNotGray()'):
"""
needThisHandling is True if we need to be able to accept a specified
thisObj, False otherwise.
"""
assert not rethrowContentException or not needThisHandling
self.retvalType = sig[0]
self.originalSig = sig
@@ -15498,17 +15498,17 @@ class CallbackSetter(CallbackAccessor):
return ""
def getCall(self):
return fill(
"""
MOZ_ASSERT(argv.length() == 1);
${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx);
if ((!*reinterpret_cast<jsid**>(atomsCache) && !InitIds(cx, atomsCache)) ||
- !JS_SetPropertyById(cx, CallbackPreserveColor(), atomsCache->${attrAtomName}, argv[0])) {
+ !JS_SetPropertyById(cx, CallbackKnownNotGray(), atomsCache->${attrAtomName}, argv[0])) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return${errorReturn};
}
""",
atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms",
attrAtomName=CGDictionary.makeIdName(self.descriptorProvider.binaryNameFor(self.attrName)),
errorReturn=self.getDefaultRetval())
--- a/dom/canvas/CanvasGradient.h
+++ b/dom/canvas/CanvasGradient.h
@@ -57,16 +57,18 @@ public:
}
CanvasRenderingContext2D* GetParentObject()
{
return mContext;
}
protected:
+ friend struct CanvasBidiProcessor;
+
CanvasGradient(CanvasRenderingContext2D* aContext, Type aType)
: mContext(aContext)
, mType(aType)
{
}
RefPtr<CanvasRenderingContext2D> mContext;
nsTArray<mozilla::gfx::GradientStop> mRawStops;
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -213,16 +213,17 @@ public:
const Point& aBegin, const Point& aEnd)
: CanvasGradient(aContext, Type::LINEAR)
, mBegin(aBegin)
, mEnd(aEnd)
{
}
protected:
+ friend struct CanvasBidiProcessor;
friend class CanvasGeneralPattern;
// Beginning of linear gradient.
Point mBegin;
// End of linear gradient.
Point mEnd;
};
@@ -3654,16 +3655,18 @@ CanvasRenderingContext2D::GetHitRegionRe
return false;
}
/**
* Used for nsBidiPresUtils::ProcessText
*/
struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor
{
+ typedef CanvasRenderingContext2D::Style Style;
+
CanvasBidiProcessor()
: nsBidiPresUtils::BidiProcessor()
{
if (Preferences::GetBool(GFX_MISSING_FONTS_NOTIFY_PREF)) {
mMissingFonts = new gfxMissingFontRecorder();
}
}
@@ -3706,22 +3709,81 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
if (mDoMeasureBoundingBox) {
textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel);
mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox);
}
return NSToCoordRound(textRunMetrics.mAdvanceWidth);
}
+ already_AddRefed<gfxPattern> GetGradientFor(Style aStyle)
+ {
+ RefPtr<gfxPattern> pattern;
+ CanvasGradient* gradient = mState->gradientStyles[aStyle];
+ CanvasGradient::Type type = gradient->GetType();
+
+ switch (type) {
+ case CanvasGradient::Type::RADIAL: {
+ CanvasRadialGradient* radial =
+ static_cast<CanvasRadialGradient*>(gradient);
+ pattern = new gfxPattern(radial->mCenter1.x, radial->mCenter1.y,
+ radial->mRadius1, radial->mCenter2.x,
+ radial->mCenter2.y, radial->mRadius2);
+ break;
+ }
+ case CanvasGradient::Type::LINEAR: {
+ CanvasLinearGradient* linear =
+ static_cast<CanvasLinearGradient*>(gradient);
+ pattern = new gfxPattern(linear->mBegin.x, linear->mBegin.y,
+ linear->mEnd.x, linear->mEnd.y);
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "Should be linear or radial gradient.");
+ return nullptr;
+ }
+
+ for (auto stop : gradient->mRawStops) {
+ pattern->AddColorStop(stop.offset, stop.color);
+ }
+
+ return pattern.forget();
+ }
+
+ gfx::ExtendMode CvtCanvasRepeatToGfxRepeat(
+ CanvasPattern::RepeatMode aRepeatMode)
+ {
+ switch (aRepeatMode) {
+ case CanvasPattern::RepeatMode::REPEAT:
+ return gfx::ExtendMode::REPEAT;
+ case CanvasPattern::RepeatMode::REPEATX:
+ return gfx::ExtendMode::REPEAT_X;
+ case CanvasPattern::RepeatMode::REPEATY:
+ return gfx::ExtendMode::REPEAT_Y;
+ case CanvasPattern::RepeatMode::NOREPEAT:
+ return gfx::ExtendMode::CLAMP;
+ default:
+ return gfx::ExtendMode::CLAMP;
+ }
+ }
+
+ already_AddRefed<gfxPattern> GetPatternFor(Style aStyle)
+ {
+ const CanvasPattern* pat = mState->patternStyles[aStyle];
+ RefPtr<gfxPattern> pattern = new gfxPattern(pat->mSurface, Matrix());
+ pattern->SetExtend(CvtCanvasRepeatToGfxRepeat(pat->mRepeat));
+ return pattern.forget();
+ }
+
virtual void DrawText(nscoord aXOffset, nscoord aWidth)
{
gfxPoint point = mPt;
bool rtl = mTextRun->IsRightToLeft();
bool verticalRun = mTextRun->IsVertical();
- bool centerBaseline = mTextRun->UseCenterBaseline();
+ RefPtr<gfxPattern> pattern;
gfxFloat& inlineCoord = verticalRun ? point.y : point.x;
inlineCoord += aXOffset;
// offset is given in terms of left side of string
if (rtl) {
// Bug 581092 - don't use rounded pixel width to advance to
// right-hand end of run, because this will cause different
@@ -3737,215 +3799,72 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
// old code was:
// point.x += width * mAppUnitsPerDevPixel;
// TODO: restore this if/when we move to fractional coords
// throughout the text layout process
}
mCtx->EnsureTarget();
- // If the operation is 'fill' with a simple color, we defer to gfxTextRun
- // which will handle color/svg-in-ot fonts appropriately. Such fonts will
- // not render well via the code below.
- if (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL &&
- mState->StyleIsColor(CanvasRenderingContext2D::Style::FILL)) {
- // TODO: determine if mCtx->mTarget is guaranteed to be non-null and valid
- // here. If it's not, thebes will be null and we'll crash.
- RefPtr<gfxContext> thebes =
- gfxContext::CreatePreservingTransformOrNull(mCtx->mTarget);
- nscolor fill = mState->colorStyles[CanvasRenderingContext2D::Style::FILL];
- thebes->SetColor(Color::FromABGR(fill));
- gfxTextRun::DrawParams params(thebes);
- mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params);
- return;
- }
-
- uint32_t numRuns;
- const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns);
- const int32_t appUnitsPerDevUnit = mAppUnitsPerDevPixel;
- const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit);
- Point baselineOrigin =
- Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit);
-
- float advanceSum = 0;
-
- for (uint32_t c = 0; c < numRuns; c++) {
- gfxFont *font = runs[c].mFont;
-
- bool verticalFont =
- runs[c].mOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
-
- const float& baselineOriginInline =
- verticalFont ? baselineOrigin.y : baselineOrigin.x;
- const float& baselineOriginBlock =
- verticalFont ? baselineOrigin.x : baselineOrigin.y;
-
- uint32_t endRun = 0;
- if (c + 1 < numRuns) {
- endRun = runs[c + 1].mCharacterOffset;
+ // Defer the tasks to gfxTextRun which will handle color/svg-in-ot fonts
+ // appropriately.
+ StrokeOptions strokeOpts;
+ DrawOptions drawOpts;
+ Style style = (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL)
+ ? Style::FILL
+ : Style::STROKE;
+ RefPtr<gfxContext> thebes =
+ gfxContext::CreatePreservingTransformOrNull(mCtx->mTarget);
+ gfxTextRun::DrawParams params(thebes);
+
+ if (mState->StyleIsColor(style)) { // Color
+ nscolor fontColor = mState->colorStyles[style];
+ if (style == Style::FILL) {
+ params.context->SetColor(Color::FromABGR(fontColor));
} else {
- endRun = mTextRun->GetLength();
+ params.textStrokeColor = fontColor;
}
-
- const gfxTextRun::CompressedGlyph *glyphs = mTextRun->GetCharacterGlyphs();
-
- RefPtr<ScaledFont> scaledFont =
- gfxPlatform::GetPlatform()->GetScaledFontForFont(mCtx->mTarget, font);
-
- if (!scaledFont) {
- // This can occur when something switched DirectWrite off.
+ } else {
+ if (mState->gradientStyles[style]) { // Gradient
+ pattern = GetGradientFor(style);
+ } else if (mState->patternStyles[style]) { // Pattern
+ pattern = GetPatternFor(style);
+ } else {
+ MOZ_ASSERT(false, "Should never reach here.");
return;
}
-
- AutoRestoreTransform sidewaysRestore;
- if (runs[c].mOrientation ==
- gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT) {
- sidewaysRestore.Init(mCtx->mTarget);
- const gfxFont::Metrics& metrics = mTextRun->GetFontGroup()->
- GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal);
-
- gfx::Matrix mat = mCtx->mTarget->GetTransform().Copy().
- PreTranslate(baselineOrigin). // translate origin for rotation
- PreRotate(gfx::Float(M_PI / 2.0)). // turn 90deg clockwise
- PreTranslate(-baselineOrigin); // undo the translation
-
- if (centerBaseline) {
- // TODO: The baseline adjustment here is kinda ad hoc; eventually
- // perhaps we should check for horizontal and vertical baseline data
- // in the font, and adjust accordingly.
- // (The same will be true for HTML text layout.)
- float offset = (metrics.emAscent - metrics.emDescent) / 2;
- mat = mat.PreTranslate(Point(0, offset));
- // offset the (alphabetic) baseline of the
- // horizontally-shaped text from the (centered)
- // default baseline used for vertical
- }
-
- mCtx->mTarget->SetTransform(mat);
- }
-
- RefPtr<GlyphRenderingOptions> renderingOptions = font->GetGlyphRenderingOptions();
-
- GlyphBuffer buffer;
-
- std::vector<Glyph> glyphBuf;
-
- // TODO:
- // This more-or-less duplicates the code found in gfxTextRun::Draw
- // and the gfxFont methods that uses (Draw, DrawGlyphs, DrawOneGlyph);
- // it would be nice to refactor and share that code.
- for (uint32_t i = runs[c].mCharacterOffset; i < endRun; i++) {
- Glyph newGlyph;
-
- float& inlinePos =
- verticalFont ? newGlyph.mPosition.y : newGlyph.mPosition.x;
- float& blockPos =
- verticalFont ? newGlyph.mPosition.x : newGlyph.mPosition.y;
-
- if (glyphs[i].IsSimpleGlyph()) {
- newGlyph.mIndex = glyphs[i].GetSimpleGlyph();
- if (rtl) {
- inlinePos = baselineOriginInline - advanceSum -
- glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit;
- } else {
- inlinePos = baselineOriginInline + advanceSum;
- }
- blockPos = baselineOriginBlock;
- advanceSum += glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit;
- glyphBuf.push_back(newGlyph);
- continue;
- }
-
- if (!glyphs[i].GetGlyphCount()) {
- continue;
- }
-
- const gfxTextRun::DetailedGlyph *d = mTextRun->GetDetailedGlyphs(i);
-
- if (glyphs[i].IsMissing()) {
- if (d->mAdvance > 0) {
- // Perhaps we should render a hexbox here, but for now
- // we just draw the font's .notdef glyph. (See bug 808288.)
- newGlyph.mIndex = 0;
- if (rtl) {
- inlinePos = baselineOriginInline - advanceSum -
- d->mAdvance * devUnitsPerAppUnit;
- } else {
- inlinePos = baselineOriginInline + advanceSum;
- }
- blockPos = baselineOriginBlock;
- advanceSum += d->mAdvance * devUnitsPerAppUnit;
- glyphBuf.push_back(newGlyph);
- }
- continue;
- }
-
- for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++, d++) {
- newGlyph.mIndex = d->mGlyphID;
- if (rtl) {
- inlinePos = baselineOriginInline - advanceSum -
- d->mAdvance * devUnitsPerAppUnit;
- } else {
- inlinePos = baselineOriginInline + advanceSum;
- }
- inlinePos += d->mXOffset * devUnitsPerAppUnit;
- blockPos = baselineOriginBlock + d->mYOffset * devUnitsPerAppUnit;
- glyphBuf.push_back(newGlyph);
- advanceSum += d->mAdvance * devUnitsPerAppUnit;
- }
- }
-
- if (!glyphBuf.size()) {
- // This may happen for glyph runs for a 0 size font.
- continue;
- }
-
- buffer.mGlyphs = &glyphBuf.front();
- buffer.mNumGlyphs = glyphBuf.size();
-
- Rect bounds = mCtx->mTarget->GetTransform().
- TransformBounds(Rect(mBoundingBox.x, mBoundingBox.y,
- mBoundingBox.width, mBoundingBox.height));
- if (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL) {
- AdjustedTarget(mCtx, &bounds)->
- FillGlyphs(scaledFont, buffer,
- CanvasGeneralPattern().
- ForStyle(mCtx, CanvasRenderingContext2D::Style::FILL, mCtx->mTarget),
- DrawOptions(mState->globalAlpha, mCtx->UsedOperation()),
- renderingOptions);
- } else if (mOp == CanvasRenderingContext2D::TextDrawOperation::STROKE) {
- // stroke glyphs one at a time to avoid poor CoreGraphics performance
- // when stroking a path with a very large number of points
- buffer.mGlyphs = &glyphBuf.front();
- buffer.mNumGlyphs = 1;
- const ContextState& state = *mState;
-
- const StrokeOptions strokeOpts(state.lineWidth, state.lineJoin,
- state.lineCap, state.miterLimit,
- state.dash.Length(),
- state.dash.Elements(),
- state.dashOffset);
-
- // We need to adjust the bounds for the adjusted target
- bounds.Inflate(MaxStrokeExtents(strokeOpts, mCtx->mTarget->GetTransform()));
-
- AdjustedTarget target(mCtx, &bounds);
-
- CanvasGeneralPattern cgp;
- const Pattern& patForStyle
- (cgp.ForStyle(mCtx, CanvasRenderingContext2D::Style::STROKE, mCtx->mTarget));
- const DrawOptions drawOpts(state.globalAlpha, mCtx->UsedOperation());
-
- for (unsigned i = glyphBuf.size(); i > 0; --i) {
- RefPtr<Path> path = scaledFont->GetPathForGlyphs(buffer, mCtx->mTarget);
- target->Stroke(path, patForStyle, strokeOpts, drawOpts);
- buffer.mGlyphs++;
- }
+ MOZ_ASSERT(pattern, "No valid pattern.");
+
+ if (style == Style::FILL) {
+ params.context->SetPattern(pattern);
+ } else {
+ params.textStrokePattern = pattern;
}
}
+
+ if (style == Style::STROKE) {
+ const ContextState& state = *mState;
+ drawOpts.mAlpha = state.globalAlpha;
+ drawOpts.mCompositionOp = mCtx->UsedOperation();
+
+ strokeOpts.mLineWidth = state.lineWidth;
+ strokeOpts.mLineJoin = state.lineJoin;
+ strokeOpts.mLineCap = state.lineCap;
+ strokeOpts.mMiterLimit = state.miterLimit;
+ strokeOpts.mDashLength = state.dash.Length();
+ strokeOpts.mDashPattern =
+ (strokeOpts.mDashLength > 0) ? state.dash.Elements() : 0;
+ strokeOpts.mDashOffset = state.dashOffset;
+
+ params.drawMode = DrawMode::GLYPH_STROKE;
+ params.strokeOpts = &strokeOpts;
+ params.drawOpts = &drawOpts;
+ }
+
+ mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params);
}
// current text run
UniquePtr<gfxTextRun> mTextRun;
// pointer to a screen reference context used to measure text and such
RefPtr<DrawTarget> mDrawTarget;
--- a/dom/canvas/WebGL2ContextRenderbuffers.cpp
+++ b/dom/canvas/WebGL2ContextRenderbuffers.cpp
@@ -13,28 +13,53 @@ namespace mozilla {
void
WebGL2Context::GetInternalformatParameter(JSContext* cx, GLenum target,
GLenum internalformat, GLenum pname,
JS::MutableHandleValue retval,
ErrorResult& out_rv)
{
const char funcName[] = "getInternalfomratParameter";
+ retval.setObjectOrNull(nullptr);
+
if (IsContextLost())
return;
if (target != LOCAL_GL_RENDERBUFFER) {
ErrorInvalidEnum("%s: `target` must be RENDERBUFFER, was: 0x%04x.", funcName,
target);
return;
}
- // GL_INVALID_ENUM is generated if internalformat is not color-, depth-, or
- // stencil-renderable.
- // TODO: When format table queries lands.
+ // GLES 3.0.4 $4.4.4 p212:
+ // "An internal format is color-renderable if it is one of the formats from table 3.13
+ // noted as color-renderable or if it is unsized format RGBA or RGB."
+
+ GLenum sizedFormat;
+ switch (internalformat) {
+ case LOCAL_GL_RGB:
+ sizedFormat = LOCAL_GL_RGB8;
+ break;
+ case LOCAL_GL_RGBA:
+ sizedFormat = LOCAL_GL_RGBA8;
+ break;
+ default:
+ sizedFormat = internalformat;
+ break;
+ }
+
+ // In RenderbufferStorage, we allow DEPTH_STENCIL. Therefore, it is accepted for
+ // internalformat as well. Please ignore the conformance test fail for DEPTH_STENCIL.
+
+ const auto usage = mFormatUsage->GetRBUsage(sizedFormat);
+ if (!usage) {
+ ErrorInvalidEnum("%s: `internalformat` must be color-, depth-, or stencil-renderable, was: 0x%04x.",
+ funcName, internalformat);
+ return;
+ }
if (pname != LOCAL_GL_SAMPLES) {
ErrorInvalidEnumInfo("%s: `pname` must be SAMPLES, was 0x%04x.", funcName, pname);
return;
}
GLint* samples = nullptr;
GLint sampleCount = 0;
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/GetFilesHelper.cpp
@@ -0,0 +1,511 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GetFilesHelper.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+
+namespace mozilla {
+namespace dom {
+
+///////////////////////////////////////////////////////////////////////////////
+// GetFilesHelper Base class
+
+already_AddRefed<GetFilesHelper>
+GetFilesHelper::Create(nsIGlobalObject* aGlobal,
+ const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
+ bool aRecursiveFlag, ErrorResult& aRv)
+{
+ RefPtr<GetFilesHelper> helper;
+
+ if (XRE_IsParentProcess()) {
+ helper = new GetFilesHelper(aGlobal, aRecursiveFlag);
+ } else {
+ helper = new GetFilesHelperChild(aGlobal, aRecursiveFlag);
+ }
+
+ nsAutoString directoryPath;
+
+ for (uint32_t i = 0; i < aFilesOrDirectory.Length(); ++i) {
+ const OwningFileOrDirectory& data = aFilesOrDirectory[i];
+ if (data.IsFile()) {
+ if (!helper->mFiles.AppendElement(data.GetAsFile(), fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+ } else {
+ MOZ_ASSERT(data.IsDirectory());
+
+ // We support the upload of only 1 top-level directory from our
+ // directory picker. This means that we cannot have more than 1
+ // Directory object in aFilesOrDirectory array.
+ MOZ_ASSERT(directoryPath.IsEmpty());
+
+ RefPtr<Directory> directory = data.GetAsDirectory();
+ MOZ_ASSERT(directory);
+
+ aRv = directory->GetFullRealPath(directoryPath);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+ }
+
+ // No directories to explore.
+ if (directoryPath.IsEmpty()) {
+ helper->mListingCompleted = true;
+ return helper.forget();
+ }
+
+ MOZ_ASSERT(helper->mFiles.IsEmpty());
+ helper->SetDirectoryPath(directoryPath);
+
+ helper->Work(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return helper.forget();
+}
+
+GetFilesHelper::GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag)
+ : mGlobal(aGlobal)
+ , mRecursiveFlag(aRecursiveFlag)
+ , mListingCompleted(false)
+ , mErrorResult(NS_OK)
+ , mMutex("GetFilesHelper::mMutex")
+ , mCanceled(false)
+{
+}
+
+void
+GetFilesHelper::AddPromise(Promise* aPromise)
+{
+ MOZ_ASSERT(aPromise);
+
+ // Still working.
+ if (!mListingCompleted) {
+ mPromises.AppendElement(aPromise);
+ return;
+ }
+
+ MOZ_ASSERT(mPromises.IsEmpty());
+ ResolveOrRejectPromise(aPromise);
+}
+
+void
+GetFilesHelper::AddCallback(GetFilesCallback* aCallback)
+{
+ MOZ_ASSERT(aCallback);
+
+ // Still working.
+ if (!mListingCompleted) {
+ mCallbacks.AppendElement(aCallback);
+ return;
+ }
+
+ MOZ_ASSERT(mCallbacks.IsEmpty());
+ RunCallback(aCallback);
+}
+
+void
+GetFilesHelper::Unlink()
+{
+ mGlobal = nullptr;
+ mFiles.Clear();
+ mPromises.Clear();
+ mCallbacks.Clear();
+
+ {
+ MutexAutoLock lock(mMutex);
+ mCanceled = true;
+ }
+
+ Cancel();
+}
+
+void
+GetFilesHelper::Traverse(nsCycleCollectionTraversalCallback &cb)
+{
+ GetFilesHelper* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFiles);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises);
+}
+
+void
+GetFilesHelper::Work(ErrorResult& aRv)
+{
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target);
+
+ aRv = target->Dispatch(this, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+GetFilesHelper::Run()
+{
+ MOZ_ASSERT(!mDirectoryPath.IsEmpty());
+ MOZ_ASSERT(!mListingCompleted);
+
+ // First step is to retrieve the list of file paths.
+ // This happens in the I/O thread.
+ if (!NS_IsMainThread()) {
+ RunIO();
+
+ // If this operation has been canceled, we don't have to go back to
+ // main-thread.
+ if (IsCanceled()) {
+ return NS_OK;
+ }
+
+ return NS_DispatchToMainThread(this);
+ }
+
+ // We are here, but we should not do anything on this thread because, in the
+ // meantime, the operation has been canceled.
+ if (IsCanceled()) {
+ return NS_OK;
+ }
+
+ RunMainThread();
+
+ OperationCompleted();
+ return NS_OK;
+}
+
+void
+GetFilesHelper::OperationCompleted()
+{
+ // We mark the operation as completed here.
+ mListingCompleted = true;
+
+ // Let's process the pending promises.
+ nsTArray<RefPtr<Promise>> promises;
+ promises.SwapElements(mPromises);
+
+ for (uint32_t i = 0; i < promises.Length(); ++i) {
+ ResolveOrRejectPromise(promises[i]);
+ }
+
+ // Let's process the pending callbacks.
+ nsTArray<RefPtr<GetFilesCallback>> callbacks;
+ callbacks.SwapElements(mCallbacks);
+
+ for (uint32_t i = 0; i < callbacks.Length(); ++i) {
+ RunCallback(callbacks[i]);
+ }
+}
+
+void
+GetFilesHelper::RunIO()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!mDirectoryPath.IsEmpty());
+ MOZ_ASSERT(!mListingCompleted);
+
+ nsCOMPtr<nsIFile> file;
+ mErrorResult = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(mDirectoryPath), true,
+ getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
+ return;
+ }
+
+ nsAutoString path;
+ path.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+
+ mErrorResult = ExploreDirectory(path, file);
+}
+
+void
+GetFilesHelper::RunMainThread()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mDirectoryPath.IsEmpty());
+ MOZ_ASSERT(!mListingCompleted);
+
+ // If there is an error, do nothing.
+ if (NS_FAILED(mErrorResult)) {
+ return;
+ }
+
+ // Create the sequence of Files.
+ for (uint32_t i = 0; i < mTargetPathArray.Length(); ++i) {
+ nsCOMPtr<nsIFile> file;
+ mErrorResult =
+ NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(mTargetPathArray[i].mRealPath),
+ true, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
+ mFiles.Clear();
+ return;
+ }
+
+ RefPtr<File> domFile =
+ File::CreateFromFile(mGlobal, file);
+ MOZ_ASSERT(domFile);
+
+ domFile->SetPath(mTargetPathArray[i].mDomPath);
+
+ if (!mFiles.AppendElement(domFile, fallible)) {
+ mErrorResult = NS_ERROR_OUT_OF_MEMORY;
+ mFiles.Clear();
+ return;
+ }
+ }
+}
+
+nsresult
+GetFilesHelper::ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aFile);
+
+ // We check if this operation has to be terminated at each recursion.
+ if (IsCanceled()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (;;) {
+ bool hasMore = false;
+ if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) {
+ break;
+ }
+
+ nsCOMPtr<nsISupports> supp;
+ if (NS_WARN_IF(NS_FAILED(entries->GetNext(getter_AddRefs(supp))))) {
+ break;
+ }
+
+ nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp);
+ MOZ_ASSERT(currFile);
+
+ bool isLink, isSpecial, isFile, isDir;
+ if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
+ NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
+ isLink || isSpecial) {
+ continue;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
+ NS_FAILED(currFile->IsDirectory(&isDir))) ||
+ !(isFile || isDir)) {
+ continue;
+ }
+
+ // The new domPath
+ nsAutoString domPath;
+ domPath.Assign(aDOMPath);
+ if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) {
+ domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+ }
+
+ nsAutoString leafName;
+ if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) {
+ continue;
+ }
+ domPath.Append(leafName);
+
+ if (isFile) {
+ FileData* data = mTargetPathArray.AppendElement(fallible);
+ if (!data) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(currFile->GetPath(data->mRealPath)))) {
+ continue;
+ }
+
+ data->mDomPath = domPath;
+ continue;
+ }
+
+ MOZ_ASSERT(isDir);
+ if (!mRecursiveFlag) {
+ continue;
+ }
+
+ // Recursive.
+ rv = ExploreDirectory(domPath, currFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+GetFilesHelper::ResolveOrRejectPromise(Promise* aPromise)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mListingCompleted);
+ MOZ_ASSERT(aPromise);
+
+ // Error propagation.
+ if (NS_FAILED(mErrorResult)) {
+ aPromise->MaybeReject(mErrorResult);
+ return;
+ }
+
+ aPromise->MaybeResolve(mFiles);
+}
+
+void
+GetFilesHelper::RunCallback(GetFilesCallback* aCallback)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mListingCompleted);
+ MOZ_ASSERT(aCallback);
+
+ aCallback->Callback(mErrorResult, mFiles);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// GetFilesHelperChild class
+
+void
+GetFilesHelperChild::Work(ErrorResult& aRv)
+{
+ ContentChild* cc = ContentChild::GetSingleton();
+ if (NS_WARN_IF(!cc)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aRv = nsContentUtils::GenerateUUIDInPlace(mUUID);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ mPendingOperation = true;
+ cc->CreateGetFilesRequest(mDirectoryPath, mRecursiveFlag, mUUID, this);
+}
+
+void
+GetFilesHelperChild::Cancel()
+{
+ if (!mPendingOperation) {
+ return;
+ }
+
+ ContentChild* cc = ContentChild::GetSingleton();
+ if (NS_WARN_IF(!cc)) {
+ return;
+ }
+
+ mPendingOperation = false;
+ cc->DeleteGetFilesRequest(mUUID, this);
+}
+
+bool
+GetFilesHelperChild::AppendBlobImpl(BlobImpl* aBlobImpl)
+{
+ MOZ_ASSERT(mPendingOperation);
+ MOZ_ASSERT(aBlobImpl);
+ MOZ_ASSERT(aBlobImpl->IsFile());
+
+ RefPtr<File> file = File::Create(mGlobal, aBlobImpl);
+ MOZ_ASSERT(file);
+
+ return mFiles.AppendElement(file, fallible);
+}
+
+void
+GetFilesHelperChild::Finished(nsresult aError)
+{
+ MOZ_ASSERT(mPendingOperation);
+ MOZ_ASSERT(NS_SUCCEEDED(mErrorResult));
+
+ mPendingOperation = false;
+ mErrorResult = aError;
+
+ OperationCompleted();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// GetFilesHelperParent class
+
+class GetFilesHelperParentCallback final : public GetFilesCallback
+{
+public:
+ explicit GetFilesHelperParentCallback(GetFilesHelperParent* aParent)
+ : mParent(aParent)
+ {
+ MOZ_ASSERT(aParent);
+ }
+
+ void
+ Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override
+ {
+ if (NS_FAILED(aStatus)) {
+ mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
+ GetFilesResponseFailure(aStatus));
+ return;
+ }
+
+ GetFilesResponseSuccess success;
+ nsTArray<PBlobParent*>& blobsParent = success.blobsParent();
+ blobsParent.SetLength(aFiles.Length());
+
+ for (uint32_t i = 0; i < aFiles.Length(); ++i) {
+ blobsParent[i] =
+ mParent->mContentParent->GetOrCreateActorForBlob(aFiles[i]);
+ if (!blobsParent[i]) {
+ mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
+ GetFilesResponseFailure(NS_ERROR_OUT_OF_MEMORY));
+ return;
+ }
+ }
+
+ mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
+ success);
+ }
+
+private:
+ // Raw pointer because this callback is kept alive by this parent object.
+ GetFilesHelperParent* mParent;
+};
+
+GetFilesHelperParent::GetFilesHelperParent(const nsID& aUUID,
+ ContentParent* aContentParent,
+ bool aRecursiveFlag)
+ : GetFilesHelper(nullptr, aRecursiveFlag)
+ , mContentParent(aContentParent)
+ , mUUID(aUUID)
+{}
+
+/* static */ already_AddRefed<GetFilesHelperParent>
+GetFilesHelperParent::Create(const nsID& aUUID, const nsAString& aDirectoryPath,
+ bool aRecursiveFlag, ContentParent* aContentParent,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aContentParent);
+
+ RefPtr<GetFilesHelperParent> helper =
+ new GetFilesHelperParent(aUUID, aContentParent, aRecursiveFlag);
+ helper->SetDirectoryPath(aDirectoryPath);
+
+ helper->Work(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<GetFilesHelperParentCallback> callback =
+ new GetFilesHelperParentCallback(helper);
+ helper->AddCallback(callback);
+
+ return helper.forget();
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/GetFilesHelper.h
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_GetFilesHelper_h
+#define mozilla_dom_GetFilesHelper_h
+
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "nsCycleCollectionTraversalCallback.h"
+#include "nsTArray.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpl;
+class ContentParent;
+class File;
+class GetFilesHelperParent;
+class OwningFileOrDirectory;
+class Promise;
+
+class GetFilesCallback
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GetFilesCallback);
+
+ virtual void
+ Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) = 0;
+
+protected:
+ virtual ~GetFilesCallback() {}
+};
+
+// Retrieving the list of files can be very time/IO consuming. We use this
+// helper class to do it just once.
+class GetFilesHelper : public Runnable
+{
+ friend class GetFilesHelperParent;
+
+public:
+ static already_AddRefed<GetFilesHelper>
+ Create(nsIGlobalObject* aGlobal,
+ const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
+ bool aRecursiveFlag, ErrorResult& aRv);
+
+ void
+ AddPromise(Promise* aPromise);
+
+ void
+ AddCallback(GetFilesCallback* aCallback);
+
+ // CC methods
+ void Unlink();
+ void Traverse(nsCycleCollectionTraversalCallback &cb);
+
+protected:
+ GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag);
+
+ virtual ~GetFilesHelper() {}
+
+ void
+ SetDirectoryPath(const nsAString& aDirectoryPath)
+ {
+ mDirectoryPath = aDirectoryPath;
+ }
+
+ bool
+ IsCanceled()
+ {
+ MutexAutoLock lock(mMutex);
+ return mCanceled;
+ }
+
+ virtual void
+ Work(ErrorResult& aRv);
+
+ virtual void
+ Cancel() {};
+
+ NS_IMETHOD
+ Run() override;
+
+ void
+ RunIO();
+
+ void
+ RunMainThread();
+
+ void
+ OperationCompleted();
+
+ nsresult
+ ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile);
+ void
+ ResolveOrRejectPromise(Promise* aPromise);
+
+ void
+ RunCallback(GetFilesCallback* aCallback);
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ bool mRecursiveFlag;
+ bool mListingCompleted;
+ nsString mDirectoryPath;
+
+ // We populate this array in the I/O thread with the paths of the Files that
+ // we want to send as result to the promise objects.
+ struct FileData {
+ nsString mDomPath;
+ nsString mRealPath;
+ };
+ FallibleTArray<FileData> mTargetPathArray;
+
+ // This is the real File sequence that we expose via Promises.
+ Sequence<RefPtr<File>> mFiles;
+
+ // Error code to propagate.
+ nsresult mErrorResult;
+
+ nsTArray<RefPtr<Promise>> mPromises;
+ nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
+
+ Mutex mMutex;
+
+ // This variable is protected by mutex.
+ bool mCanceled;
+};
+
+class GetFilesHelperChild final : public GetFilesHelper
+{
+public:
+ GetFilesHelperChild(nsIGlobalObject* aGlobal, bool aRecursiveFlag)
+ : GetFilesHelper(aGlobal, aRecursiveFlag)
+ , mPendingOperation(false)
+ {}
+
+ virtual void
+ Work(ErrorResult& aRv) override;
+
+ virtual void
+ Cancel() override;
+
+ bool
+ AppendBlobImpl(BlobImpl* aBlobImpl);
+
+ void
+ Finished(nsresult aResult);
+
+private:
+ nsID mUUID;
+ bool mPendingOperation;
+};
+
+class GetFilesHelperParentCallback;
+
+class GetFilesHelperParent final : public GetFilesHelper
+{
+ friend class GetFilesHelperParentCallback;
+
+public:
+ static already_AddRefed<GetFilesHelperParent>
+ Create(const nsID& aUUID, const nsAString& aDirectoryPath,
+ bool aRecursiveFlag, ContentParent* aContentParent, ErrorResult& aRv);
+
+private:
+ GetFilesHelperParent(const nsID& aUUID, ContentParent* aContentParent,
+ bool aRecursiveFlag);
+
+ RefPtr<ContentParent> mContentParent;
+ nsID mUUID;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_GetFilesHelper_h
--- a/dom/filesystem/compat/tests/mochitest.ini
+++ b/dom/filesystem/compat/tests/mochitest.ini
@@ -1,6 +1,8 @@
[DEFAULT]
support-files =
script_entries.js
+ !/dom/html/test/form_submit_server.sjs
[test_basic.html]
[test_no_dnd.html]
+[test_formSubmission.html]
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/compat/tests/test_formSubmission.html
@@ -0,0 +1,266 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Directory form submission</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+</head>
+<body onload="return next();">
+
+<iframe name="target_iframe" id="target_iframe"></iframe>
+
+<form action="../../../html/test/form_submit_server.sjs" target="target_iframe" id="form"
+ method="POST" enctype="multipart/form-data">
+</form>
+
+<script class="testbody" type="text/javascript;version=1.8">
+
+var form;
+var iframe;
+var input;
+var xhr;
+
+function setup_tests() {
+ form = document.getElementById("form");
+
+ iframe = document.getElementById("target_iframe");
+ iframe.onload = function() {
+ info("Frame loaded!");
+ next();
+ }
+
+ SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true],
+ ["dom.webkitBlink.dirPicker.enabled", true],
+ ["dom.webkitBlink.filesystem.enabled", true]]}, next);
+}
+
+function populate_entries(webkitDirectory) {
+ if (input) {
+ form.removeChild(input);
+ }
+
+ input = document.createElement('input');
+ input.setAttribute('id', 'input');
+ input.setAttribute('type', 'file');
+ input.setAttribute('name', 'input');
+
+ if (webkitDirectory) {
+ input.setAttribute('webkitdirectory', 'true');
+ }
+
+ form.appendChild(input);
+
+ var url = SimpleTest.getTestFileURL("script_entries.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ function onOpened(message) {
+ input.addEventListener("change", function onChange() {
+ input.removeEventListener("change", onChange);
+ next();
+ });
+
+ SpecialPowers.wrap(input).mozSetDndFilesAndDirectories([message.data[0]]);
+ script.destroy();
+ }
+
+ script.addMessageListener("entries.opened", onOpened);
+ script.sendAsyncMessage("entries.open");
+}
+
+function setup_plain() {
+ info("Preparing for a plain text submission...");
+ form.action = "../../../html/test/form_submit_server.sjs?plain";
+ form.method = "POST";
+ form.enctype = "text/plain";
+ form.submit();
+}
+
+function test_plain() {
+ var content = iframe.contentDocument.documentElement.textContent;
+ var submission = JSON.parse(content);
+ input.getFilesAndDirectories().then(function(array) {
+ is(submission, array.map(function(v) {
+ return "input=" + v.name + "\r\n";
+ }).join(""), "Data match");
+
+ next();
+ });
+}
+
+function setup_urlencoded() {
+ info("Preparing for a urlencoded submission...");
+ form.action = "../../../html/test/form_submit_server.sjs?url";
+ form.method = "POST";
+ form.enctype = "application/x-www-form-urlencoded";
+ form.submit();
+}
+
+function setup_urlencoded_get() {
+ info("Preparing for a urlencoded+GET submission...");
+ form.action = "../../../html/test/form_submit_server.sjs?xxyy";
+ form.method = "GET";
+ form.enctype = "";
+ form.submit();
+}
+
+function setup_urlencoded_empty() {
+ info("Preparing for a urlencoded+default values submission...");
+ form.action = "../../../html/test/form_submit_server.sjs";
+ form.method = "";
+ form.enctype = "";
+ form.submit();
+}
+
+function test_urlencoded() {
+ var content = iframe.contentDocument.documentElement.textContent;
+ var submission = JSON.parse(content);
+ input.getFilesAndDirectories().then(function(array) {
+ is(submission, array.map(function(v) {
+ return "input=" + v.name;
+ }).join("&"), "Data match");
+
+ next();
+ });
+}
+
+function setup_formData() {
+ info("Preparing for a fromData submission...");
+
+ xhr = new XMLHttpRequest();
+ xhr.onload = next;
+ xhr.open("POST", "../../../html/test/form_submit_server.sjs");
+ xhr.send(new FormData(form));
+}
+
+function test_multipart() {
+ var submission = JSON.parse(xhr.responseText);
+ input.getFilesAndDirectories().then(function(array) {
+ is(submission.length, array.length, "Same length");
+
+ for (var i = 0; i < array.length; ++i) {
+ if (array[i] instanceof Directory) {
+ is(submission[i].headers["Content-Disposition"],
+ "form-data; name=\"input\"; filename=\"/" + array[i].name + "\"",
+ "Correct Content-Disposition");
+ is(submission[i].headers["Content-Type"], "application/octet-stream",
+ "Correct Content-Type");
+ is(submission[i].body, "", "Correct body");
+ } else {
+ ok(array[i] instanceof File);
+ is(submission[i].headers["Content-Disposition"],
+ "form-data; name=\"input\"; filename=\"" + array[i].name + "\"",
+ "Correct Content-Disposition");
+ is(submission[i].headers["Content-Type"], array[i].type,
+ "Correct Content-Type");
+ is(submission[i].body, "", "Correct body");
+ }
+ }
+ next();
+ });
+}
+
+function test_webkit_plain() {
+ var content = iframe.contentDocument.documentElement.textContent;
+ var submission = JSON.parse(content);
+
+ input.getFiles(true).then(function(array) {
+ is(submission, array.map(function(v) {
+ return "input=" + v.name + "\r\n";
+ }).join(""), "Data match");
+
+ next();
+ });
+}
+
+function test_webkit_urlencoded() {
+ var content = iframe.contentDocument.documentElement.textContent;
+ var submission = JSON.parse(content);
+ input.getFiles(true).then(function(array) {
+ is(submission, array.map(function(v) {
+ return "input=" + v.name;
+ }).join("&"), "Data match");
+
+ next();
+ });
+}
+
+function test_webkit_multipart() {
+ var submission = JSON.parse(xhr.responseText);
+ input.getFiles(true).then(function(array) {
+ is(submission.length, array.length, "Same length");
+
+ for (var i = 0; i < array.length; ++i) {
+ if (array[i] instanceof Directory) {
+ is(submission[i].headers["Content-Disposition"],
+ "form-data; name=\"input\"; filename=\"/" + array[i].name + "\"",
+ "Correct Content-Disposition");
+ is(submission[i].headers["Content-Type"], "application/octet-stream",
+ "Correct Content-Type");
+ is(submission[i].body, "", "Correct body");
+ } else {
+ ok(array[i] instanceof File);
+ is(submission[i].headers["Content-Disposition"],
+ "form-data; name=\"input\"; filename=\"" + array[i].webkitRelativePath + "\"",
+ "Correct Content-Disposition");
+ is(submission[i].headers["Content-Type"], array[i].type,
+ "Correct Content-Type");
+ is(submission[i].body, "", "Correct body");
+ }
+ }
+ next();
+ });
+}
+
+var tests = [
+ setup_tests,
+ function() { populate_entries(false); },
+
+ setup_plain,
+ test_plain,
+
+ setup_urlencoded,
+ test_urlencoded,
+
+ setup_urlencoded_get,
+ test_urlencoded,
+
+ setup_urlencoded_empty,
+ test_urlencoded,
+
+ setup_formData,
+ test_multipart,
+
+ function() { populate_entries(true); },
+
+ setup_plain,
+ test_webkit_plain,
+
+ setup_urlencoded,
+ test_webkit_urlencoded,
+
+ setup_urlencoded_get,
+ test_webkit_urlencoded,
+
+ setup_urlencoded_empty,
+ test_webkit_urlencoded,
+
+ setup_formData,
+ test_webkit_multipart,
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
--- a/dom/filesystem/moz.build
+++ b/dom/filesystem/moz.build
@@ -10,31 +10,33 @@ TEST_DIRS += ['tests']
EXPORTS.mozilla.dom += [
'DeviceStorageFileSystem.h',
'Directory.h',
'FileSystemBase.h',
'FileSystemRequestParent.h',
'FileSystemTaskBase.h',
'FileSystemUtils.h',
+ 'GetFilesHelper.h',
'OSFileSystem.h',
]
UNIFIED_SOURCES += [
'CreateDirectoryTask.cpp',
'CreateFileTask.cpp',
'DeviceStorageFileSystem.cpp',
'Directory.cpp',
'FileSystemBase.cpp',
'FileSystemPermissionRequest.cpp',
'FileSystemRequestParent.cpp',
'FileSystemTaskBase.cpp',
'FileSystemUtils.cpp',
'GetDirectoryListingTask.cpp',
'GetFileOrDirectoryTask.cpp',
+ 'GetFilesHelper.cpp',
'GetFilesTask.cpp',
'OSFileSystem.cpp',
'RemoveTask.cpp',
]
FINAL_LIBRARY = 'xul'
IPDL_SOURCES += [
--- a/dom/html/HTMLFormSubmission.cpp
+++ b/dom/html/HTMLFormSubmission.cpp
@@ -65,16 +65,29 @@ RetrieveFileName(Blob* aBlob, nsAString&
}
RefPtr<File> file = aBlob->ToFile();
if (file) {
file->GetName(aFilename);
}
}
+void
+RetrieveDirectoryName(Directory* aDirectory, nsAString& aDirname)
+{
+ MOZ_ASSERT(aDirectory);
+
+ ErrorResult rv;
+ aDirectory->GetName(aDirname, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ aDirname.Truncate();
+ }
+}
+
// --------------------------------------------------------------------------
class FSURLEncoded : public EncodingFormSubmission
{
public:
/**
* @param aCharset the charset of the form as a string
* @param aMethod the method of the submit (either NS_FORM_METHOD_GET or
@@ -93,16 +106,19 @@ public:
virtual nsresult
AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
virtual nsresult
AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
virtual nsresult
+ AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
+
+ virtual nsresult
GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
virtual bool SupportsIsindexSubmission() override
{
return true;
}
virtual nsresult AddIsindex(const nsAString& aValue) override;
@@ -189,16 +205,27 @@ FSURLEncoded::AddNameBlobOrNullPair(cons
mWarnedFileControl = true;
}
nsAutoString filename;
RetrieveFileName(aBlob, filename);
return AddNameValuePair(aName, filename);
}
+nsresult
+FSURLEncoded::AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory)
+{
+ // No warning about because Directory objects are never sent via form.
+
+ nsAutoString dirname;
+ RetrieveDirectoryName(aDirectory, dirname);
+ return AddNameValuePair(aName, dirname);
+}
+
void
HandleMailtoSubject(nsCString& aPath)
{
// Walk through the string and see if we have a subject already.
bool hasSubject = false;
bool hasParams = false;
int32_t paramSep = aPath.FindChar('?');
while (paramSep != kNotFound && paramSep < (int32_t)aPath.Length()) {
@@ -463,16 +490,19 @@ FSMultipartFormData::AddNameValuePair(co
nsresult
FSMultipartFormData::AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob)
{
// Encode the control name
nsAutoCString nameStr;
nsresult rv = EncodeVal(aName, nameStr, true);
NS_ENSURE_SUCCESS(rv, rv);
+ ErrorResult error;
+
+ uint64_t size = 0;
nsAutoCString filename;
nsAutoCString contentType;
nsCOMPtr<nsIInputStream> fileStream;
if (aBlob) {
nsAutoString filename16;
RefPtr<File> file = aBlob->ToFile();
@@ -500,72 +530,119 @@ FSMultipartFormData::AddNameBlobOrNullPa
}
contentType.Adopt(nsLinebreakConverter::
ConvertLineBreaks(NS_ConvertUTF16toUTF8(contentType16).get(),
nsLinebreakConverter::eLinebreakAny,
nsLinebreakConverter::eLinebreakSpace));
// Get input stream
- ErrorResult error;
aBlob->GetInternalStream(getter_AddRefs(fileStream), error);
if (NS_WARN_IF(error.Failed())) {
return error.StealNSResult();
}
+ // Get size
+ size = aBlob->GetSize(error);
+ if (error.Failed()) {
+ error.SuppressException();
+ fileStream = nullptr;
+ }
+
if (fileStream) {
// Create buffered stream (for efficiency)
nsCOMPtr<nsIInputStream> bufferedStream;
rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
fileStream, 8192);
NS_ENSURE_SUCCESS(rv, rv);
fileStream = bufferedStream;
}
} else {
contentType.AssignLiteral("application/octet-stream");
}
+ AddDataChunk(nameStr, filename, contentType, fileStream, size);
+ return NS_OK;
+}
+
+nsresult
+FSMultipartFormData::AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory)
+{
+ if (!Directory::WebkitBlinkDirectoryPickerEnabled(nullptr, nullptr)) {
+ return NS_OK;
+ }
+
+ // Encode the control name
+ nsAutoCString nameStr;
+ nsresult rv = EncodeVal(aName, nameStr, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString dirname;
+ nsAutoString dirname16;
+
+ ErrorResult error;
+ nsAutoString path;
+ aDirectory->GetPath(path, error);
+ if (NS_WARN_IF(error.Failed())) {
+ error.SuppressException();
+ } else {
+ dirname16 = path;
+ }
+
+ if (dirname16.IsEmpty()) {
+ RetrieveDirectoryName(aDirectory, dirname16);
+ }
+
+ rv = EncodeVal(dirname16, dirname, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AddDataChunk(nameStr, dirname,
+ NS_LITERAL_CSTRING("application/octet-stream"),
+ nullptr, 0);
+ return NS_OK;
+}
+
+void
+FSMultipartFormData::AddDataChunk(const nsACString& aName,
+ const nsACString& aFilename,
+ const nsACString& aContentType,
+ nsIInputStream* aInputStream,
+ uint64_t aInputStreamSize)
+{
//
// Make MIME block for name/value pair
//
// more appropriate than always using binary?
mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary
+ NS_LITERAL_CSTRING(CRLF);
// XXX: name/filename parameter should be encoded per RFC 2231
// RFC 2388 specifies that RFC 2047 be used, but I think it's not
// consistent with the MIME standard.
mPostDataChunk +=
NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"")
- + nameStr + NS_LITERAL_CSTRING("\"; filename=\"")
- + filename + NS_LITERAL_CSTRING("\"" CRLF)
+ + aName + NS_LITERAL_CSTRING("\"; filename=\"")
+ + aFilename + NS_LITERAL_CSTRING("\"" CRLF)
+ NS_LITERAL_CSTRING("Content-Type: ")
- + contentType + NS_LITERAL_CSTRING(CRLF CRLF);
+ + aContentType + NS_LITERAL_CSTRING(CRLF CRLF);
// We should not try to append an invalid stream. That will happen for example
// if we try to update a file that actually do not exist.
- if (fileStream) {
- ErrorResult error;
- uint64_t size = aBlob->GetSize(error);
- if (error.Failed()) {
- error.SuppressException();
- } else {
- // We need to dump the data up to this point into the POST data stream
- // here, since we're about to add the file input stream
- AddPostDataStream();
+ if (aInputStream) {
+ // We need to dump the data up to this point into the POST data stream
+ // here, since we're about to add the file input stream
+ AddPostDataStream();
- mPostDataStream->AppendStream(fileStream);
- mTotalLength += size;
- }
+ mPostDataStream->AppendStream(aInputStream);
+ mTotalLength += aInputStreamSize;
}
// CRLF after file
mPostDataChunk.AppendLiteral(CRLF);
-
- return NS_OK;
}
nsresult
FSMultipartFormData::GetEncodedSubmission(nsIURI* aURI,
nsIInputStream** aPostDataStream)
{
nsresult rv;
@@ -619,16 +696,19 @@ public:
virtual nsresult
AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
virtual nsresult
AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
virtual nsresult
+ AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
+
+ virtual nsresult
GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
private:
nsString mBody;
};
nsresult
FSTextPlain::AddNameValuePair(const nsAString& aName, const nsAString& aValue)
@@ -647,16 +727,26 @@ FSTextPlain::AddNameBlobOrNullPair(const
{
nsAutoString filename;
RetrieveFileName(aBlob, filename);
AddNameValuePair(aName, filename);
return NS_OK;
}
nsresult
+FSTextPlain::AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory)
+{
+ nsAutoString dirname;
+ RetrieveDirectoryName(aDirectory, dirname);
+ AddNameValuePair(aName, dirname);
+ return NS_OK;
+}
+
+nsresult
FSTextPlain::GetEncodedSubmission(nsIURI* aURI,
nsIInputStream** aPostDataStream)
{
nsresult rv = NS_OK;
// XXX HACK We are using the standard URL mechanism to give the body to the
// mailer instead of passing the post data stream to it, since that sounds
// hard.
--- a/dom/html/HTMLFormSubmission.h
+++ b/dom/html/HTMLFormSubmission.h
@@ -17,16 +17,17 @@ class nsIURI;
class nsIInputStream;
class nsGenericHTMLElement;
class nsIMultiplexInputStream;
namespace mozilla {
namespace dom {
class Blob;
+class Directory;
/**
* Class for form submissions; encompasses the function to call to submit as
* well as the form submission name/value pairs
*/
class HTMLFormSubmission
{
public:
@@ -64,16 +65,25 @@ public:
* @param aBlob the blob to submit. The file's name will be used if the Blob
* is actually a File, otherwise 'blob' string is used instead if the aBlob is
* not null.
*/
virtual nsresult
AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) = 0;
/**
+ * Submit a name/directory pair
+ *
+ * @param aName the name of the parameter
+ * @param aBlob the directory to submit.
+ */
+ virtual nsresult AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory) = 0;
+
+ /**
* Reports whether the instance supports AddIsindex().
*
* @return true if supported.
*/
virtual bool SupportsIsindexSubmission()
{
return false;
}
@@ -175,16 +185,19 @@ public:
virtual nsresult
AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
virtual nsresult
AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
virtual nsresult
+ AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
+
+ virtual nsresult
GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
void GetContentType(nsACString& aContentType)
{
aContentType =
NS_LITERAL_CSTRING("multipart/form-data; boundary=") + mBoundary;
}
@@ -193,16 +206,21 @@ public:
protected:
/**
* Roll up the data we have so far and add it to the multiplexed data stream.
*/
nsresult AddPostDataStream();
private:
+ void AddDataChunk(const nsACString& aName,
+ const nsACString& aFilename,
+ const nsACString& aContentType,
+ nsIInputStream* aInputStream,
+ uint64_t aInputStreamSize);
/**
* The post data stream as it is so far. This is a collection of smaller
* chunks--string streams and file streams interleaved to make one big POST
* stream.
*/
nsCOMPtr<nsIMultiplexInputStream> mPostDataStream;
/**
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -8,16 +8,17 @@
#include "mozilla/ArrayUtils.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/Date.h"
#include "mozilla/dom/Directory.h"
#include "mozilla/dom/HTMLFormSubmission.h"
#include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/GetFilesHelper.h"
#include "nsAttrValueInlines.h"
#include "nsCRTGlue.h"
#include "nsIDOMHTMLInputElement.h"
#include "nsITextControlElement.h"
#include "nsIDOMNSEditableElement.h"
#include "nsIRadioVisitor.h"
#include "nsIPhonetic.h"
@@ -221,414 +222,21 @@ const double HTMLInputElement::kMinimumY
0x23e2, \
0x4479, \
{0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \
}
#define PROGRESS_STR "progress"
static const uint32_t kProgressEventInterval = 50; // ms
-class GetFilesCallback
-{
-public:
- NS_INLINE_DECL_REFCOUNTING(GetFilesCallback);
-
- virtual void
- Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) = 0;
-
-protected:
- virtual ~GetFilesCallback() {}
-};
-
-// Retrieving the list of files can be very time/IO consuming. We use this
-// helper class to do it just once.
-class GetFilesHelper final : public Runnable
-{
-public:
- static already_AddRefed<GetFilesHelper>
- Create(nsIGlobalObject* aGlobal,
- const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
- bool aRecursiveFlag, ErrorResult& aRv)
- {
- MOZ_ASSERT(aGlobal);
-
- RefPtr<GetFilesHelper> helper = new GetFilesHelper(aGlobal, aRecursiveFlag);
-
- nsAutoString directoryPath;
-
- for (uint32_t i = 0; i < aFilesOrDirectory.Length(); ++i) {
- const OwningFileOrDirectory& data = aFilesOrDirectory[i];
- if (data.IsFile()) {
- if (!helper->mFiles.AppendElement(data.GetAsFile(), fallible)) {
- aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return nullptr;
- }
- } else {
- MOZ_ASSERT(data.IsDirectory());
-
- // We support the upload of only 1 top-level directory from our
- // directory picker. This means that we cannot have more than 1
- // Directory object in aFilesOrDirectory array.
- MOZ_ASSERT(directoryPath.IsEmpty());
-
- RefPtr<Directory> directory = data.GetAsDirectory();
- MOZ_ASSERT(directory);
-
- aRv = directory->GetFullRealPath(directoryPath);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- }
- }
-
- // No directories to explore.
- if (directoryPath.IsEmpty()) {
- helper->mListingCompleted = true;
- return helper.forget();
- }
-
- MOZ_ASSERT(helper->mFiles.IsEmpty());
- helper->SetDirectoryPath(directoryPath);
-
- nsCOMPtr<nsIEventTarget> target =
- do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
- MOZ_ASSERT(target);
-
- aRv = target->Dispatch(helper, NS_DISPATCH_NORMAL);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
-
- return helper.forget();
- }
-
- void
- AddPromise(Promise* aPromise)
- {
- MOZ_ASSERT(aPromise);
-
- // Still working.
- if (!mListingCompleted) {
- mPromises.AppendElement(aPromise);
- return;
- }
-
- MOZ_ASSERT(mPromises.IsEmpty());
- ResolveOrRejectPromise(aPromise);
- }
-
- void
- AddCallback(GetFilesCallback* aCallback)
- {
- MOZ_ASSERT(aCallback);
-
- // Still working.
- if (!mListingCompleted) {
- mCallbacks.AppendElement(aCallback);
- return;
- }
-
- MOZ_ASSERT(mCallbacks.IsEmpty());
- RunCallback(aCallback);
- }
-
- // CC methods
- void Unlink()
- {
- mGlobal = nullptr;
- mFiles.Clear();
- mPromises.Clear();
- mCallbacks.Clear();
-
- MutexAutoLock lock(mMutex);
- mCanceled = true;
- }
-
- void Traverse(nsCycleCollectionTraversalCallback &cb)
- {
- GetFilesHelper* tmp = this;
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal);
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFiles);
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises);
- }
-
-private:
- GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag)
- : mGlobal(aGlobal)
- , mRecursiveFlag(aRecursiveFlag)
- , mListingCompleted(false)
- , mErrorResult(NS_OK)
- , mMutex("GetFilesHelper::mMutex")
- , mCanceled(false)
- {
- MOZ_ASSERT(aGlobal);
- }
-
- void
- SetDirectoryPath(const nsAString& aDirectoryPath)
- {
- mDirectoryPath = aDirectoryPath;
- }
-
- bool
- IsCanceled()
- {
- MutexAutoLock lock(mMutex);
- return mCanceled;
- }
-
- NS_IMETHOD
- Run() override
- {
- MOZ_ASSERT(!mDirectoryPath.IsEmpty());
- MOZ_ASSERT(!mListingCompleted);
-
- // First step is to retrieve the list of file paths.
- // This happens in the I/O thread.
- if (!NS_IsMainThread()) {
- RunIO();
-
- // If this operation has been canceled, we don't have to go back to
- // main-thread.
- if (IsCanceled()) {
- return NS_OK;
- }
-
- return NS_DispatchToMainThread(this);
- }
-
- // We are here, but we should not do anything on this thread because, in the
- // meantime, the operation has been canceled.
- if (IsCanceled()) {
- return NS_OK;
- }
-
- RunMainThread();
-
- // We mark the operation as completed here.
- mListingCompleted = true;
-
- // Let's process the pending promises.
- nsTArray<RefPtr<Promise>> promises;
- promises.SwapElements(mPromises);
-
- for (uint32_t i = 0; i < promises.Length(); ++i) {
- ResolveOrRejectPromise(promises[i]);
- }
-
- // Let's process the pending callbacks.
- nsTArray<RefPtr<GetFilesCallback>> callbacks;
- callbacks.SwapElements(mCallbacks);
-
- for (uint32_t i = 0; i < callbacks.Length(); ++i) {
- RunCallback(callbacks[i]);
- }
-
- return NS_OK;
- }
-
- void
- RunIO()
- {
- MOZ_ASSERT(!NS_IsMainThread());
- MOZ_ASSERT(!mDirectoryPath.IsEmpty());
- MOZ_ASSERT(!mListingCompleted);
-
- nsCOMPtr<nsIFile> file;
- mErrorResult = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(mDirectoryPath), true,
- getter_AddRefs(file));
- if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
- return;
- }
-
- nsAutoString path;
- path.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
-
- mErrorResult = ExploreDirectory(path, file);
- }
-
- void
- RunMainThread()
- {
- MOZ_ASSERT(NS_IsMainThread());
- MOZ_ASSERT(!mDirectoryPath.IsEmpty());
- MOZ_ASSERT(!mListingCompleted);
-
- // If there is an error, do nothing.
- if (NS_FAILED(mErrorResult)) {
- return;
- }
-
- // Create the sequence of Files.
- for (uint32_t i = 0; i < mTargetPathArray.Length(); ++i) {
- nsCOMPtr<nsIFile> file;
- mErrorResult =
- NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(mTargetPathArray[i].mRealPath),
- true, getter_AddRefs(file));
- if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
- mFiles.Clear();
- return;
- }
-
- RefPtr<File> domFile =
- File::CreateFromFile(mGlobal, file);
- MOZ_ASSERT(domFile);
-
- domFile->SetPath(mTargetPathArray[i].mDomPath);
-
- if (!mFiles.AppendElement(domFile, fallible)) {
- mErrorResult = NS_ERROR_OUT_OF_MEMORY;
- mFiles.Clear();
- return;
- }
- }
- }
-
- nsresult
- ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile)
- {
- MOZ_ASSERT(!NS_IsMainThread());
- MOZ_ASSERT(aFile);
-
- // We check if this operation has to be terminated at each recursion.
- if (IsCanceled()) {
- return NS_OK;
- }
-
- nsCOMPtr<nsISimpleEnumerator> entries;
- nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
-
- for (;;) {
- bool hasMore = false;
- if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) {
- break;
- }
-
- nsCOMPtr<nsISupports> supp;
- if (NS_WARN_IF(NS_FAILED(entries->GetNext(getter_AddRefs(supp))))) {
- break;
- }
-
- nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp);
- MOZ_ASSERT(currFile);
-
- bool isLink, isSpecial, isFile, isDir;
- if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
- NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
- isLink || isSpecial) {
- continue;
- }
-
- if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
- NS_FAILED(currFile->IsDirectory(&isDir))) ||
- !(isFile || isDir)) {
- continue;
- }
-
- // The new domPath
- nsAutoString domPath;
- domPath.Assign(aDOMPath);
- if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) {
- domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
- }
-
- nsAutoString leafName;
- if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) {
- continue;
- }
- domPath.Append(leafName);
-
- if (isFile) {
- FileData* data = mTargetPathArray.AppendElement(fallible);
- if (!data) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
-
- if (NS_WARN_IF(NS_FAILED(currFile->GetPath(data->mRealPath)))) {
- continue;
- }
-
- data->mDomPath = domPath;
- continue;
- }
-
- MOZ_ASSERT(isDir);
- if (!mRecursiveFlag) {
- continue;
- }
-
- // Recursive.
- rv = ExploreDirectory(domPath, currFile);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
- }
-
- return NS_OK;
- }
-
- void
- ResolveOrRejectPromise(Promise* aPromise)
- {
- MOZ_ASSERT(NS_IsMainThread());
- MOZ_ASSERT(mListingCompleted);
- MOZ_ASSERT(aPromise);
-
- // Error propagation.
- if (NS_FAILED(mErrorResult)) {
- aPromise->MaybeReject(mErrorResult);
- return;
- }
-
- aPromise->MaybeResolve(mFiles);
- }
-
- void
- RunCallback(GetFilesCallback* aCallback)
- {
- MOZ_ASSERT(NS_IsMainThread());
- MOZ_ASSERT(mListingCompleted);
- MOZ_ASSERT(aCallback);
-
- aCallback->Callback(mErrorResult, mFiles);
- }
-
- nsCOMPtr<nsIGlobalObject> mGlobal;
-
- bool mRecursiveFlag;
- bool mListingCompleted;
- nsString mDirectoryPath;
-
- // We populate this array in the I/O thread with the paths of the Files that
- // we want to send as result to the promise objects.
- struct FileData {
- nsString mDomPath;
- nsString mRealPath;
- };
- FallibleTArray<FileData> mTargetPathArray;
-
- // This is the real File sequence that we expose via Promises.
- Sequence<RefPtr<File>> mFiles;
-
- // Error code to propagate.
- nsresult mErrorResult;
-
- nsTArray<RefPtr<Promise>> mPromises;
- nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
-
- Mutex mMutex;
-
- // This variable is protected by mutex.
- bool mCanceled;
-};
-
// An helper class for the dispatching of the 'change' event.
+// This class is used when the FilePicker finished its task (or when files and
+// directories are set by some chrome/test only method).
+// The task of this class is to postpone the dispatching of 'change' and 'input'
+// events at the end of the exploration of the directories.
class DispatchChangeEventCallback final : public GetFilesCallback
{
public:
explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement)
: mInputElement(aInputElement)
{
MOZ_ASSERT(aInputElement);
}
@@ -663,42 +271,16 @@ public:
return rv;
}
private:
RefPtr<HTMLInputElement> mInputElement;
};
-// This callback is used for postponing the calling of SetFilesOrDirectories
-// when the exploration of the directory is completed.
-class AfterSetFilesOrDirectoriesCallback : public GetFilesCallback
-{
-public:
- AfterSetFilesOrDirectoriesCallback(HTMLInputElement* aInputElement,
- bool aSetValueChanged)
- : mInputElement(aInputElement)
- , mSetValueChanged(aSetValueChanged)
- {
- MOZ_ASSERT(aInputElement);
- }
-
- void
- Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override
- {
- if (NS_SUCCEEDED(aStatus)) {
- mInputElement->AfterSetFilesOrDirectoriesInternal(mSetValueChanged);
- }
- }
-
-private:
- RefPtr<HTMLInputElement> mInputElement;
- bool mSetValueChanged;
-};
-
class HTMLInputElementState final : public nsISupports
{
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID)
NS_DECL_ISUPPORTS
bool IsCheckedSet()
{
@@ -3293,42 +2875,48 @@ HTMLInputElement::SetFiles(nsIDOMFileLis
OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
element->SetAsFile() = files->Item(i);
}
}
AfterSetFilesOrDirectories(aSetValueChanged);
}
+// This method is used for testing only.
void
HTMLInputElement::MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
{
if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
UpdateEntries(aFilesOrDirectories);
}
SetFilesOrDirectories(aFilesOrDirectories, true);
+
+ RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
+ new DispatchChangeEventCallback(this);
+
+ if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
+ ErrorResult rv;
+ GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */,
+ rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return;
+ }
+
+ helper->AddCallback(dispatchChangeEventCallback);
+ } else {
+ dispatchChangeEventCallback->DispatchEvents();
+ }
}
void
HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged)
{
- if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
- HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
- // This will call AfterSetFilesOrDirectoriesInternal eventually.
- ExploreDirectoryRecursively(aSetValueChanged);
- return;
- }
-
- AfterSetFilesOrDirectoriesInternal(aSetValueChanged);
-}
-
-void
-HTMLInputElement::AfterSetFilesOrDirectoriesInternal(bool aSetValueChanged)
-{
// No need to flush here, if there's no frame at this point we
// don't need to force creation of one just to tell it about this
// new value. We just want the display to update as needed.
nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
if (formControlFrame) {
nsAutoString readableValue;
GetDisplayFileName(readableValue);
formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue);
@@ -6565,28 +6153,30 @@ HTMLInputElement::SubmitNamesValues(HTML
// Submit file if its input type=file and this encoding method accepts files
//
if (mType == NS_FORM_INPUT_FILE) {
// Submit files
const nsTArray<OwningFileOrDirectory>& files =
GetFilesOrDirectoriesInternal();
- bool hasBlobs = false;
+ if (files.IsEmpty()) {
+ aFormSubmission->AddNameBlobOrNullPair(name, nullptr);
+ return NS_OK;
+ }
+
for (uint32_t i = 0; i < files.Length(); ++i) {
if (files[i].IsFile()) {
- hasBlobs = true;
aFormSubmission->AddNameBlobOrNullPair(name, files[i].GetAsFile());
+ } else {
+ MOZ_ASSERT(files[i].IsDirectory());
+ aFormSubmission->AddNameDirectoryPair(name, files[i].GetAsDirectory());
}
}
- if (!hasBlobs) {
- aFormSubmission->AddNameBlobOrNullPair(name, nullptr);
- }
-
return NS_OK;
}
if (mType == NS_FORM_INPUT_HIDDEN && name.EqualsLiteral("_charset_")) {
nsCString charset;
aFormSubmission->GetCharset(charset);
return aFormSubmission->AddNameValuePair(name,
NS_ConvertASCIItoUTF16(charset));
@@ -8509,32 +8099,16 @@ HTMLInputElement::GetOrCreateGetFilesHel
return nullptr;
}
}
return mGetFilesNonRecursiveHelper;
}
void
-HTMLInputElement::ExploreDirectoryRecursively(bool aSetValueChanged)
-{
- ErrorResult rv;
- GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */,
- rv);
- if (NS_WARN_IF(rv.Failed())) {
- AfterSetFilesOrDirectoriesInternal(aSetValueChanged);
- return;
- }
-
- RefPtr<AfterSetFilesOrDirectoriesCallback> callback =
- new AfterSetFilesOrDirectoriesCallback(this, aSetValueChanged);
- helper->AddCallback(callback);
-}
-
-void
HTMLInputElement::UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
{
mEntries.Clear();
nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
MOZ_ASSERT(global);
RefPtr<DOMFileSystem> fs = DOMFileSystem::Create(global);
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -238,16 +238,18 @@ public:
{
return mFilesOrDirectories;
}
void SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
bool aSetValueChanged);
void SetFiles(nsIDOMFileList* aFiles, bool aSetValueChanged);
+ // This method is used for test only. Onces the data is set, a 'change' event
+ // is dispatched.
void MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory>& aSequence);
// Called when a nsIFilePicker or a nsIColorPicker terminate.
void PickerClosed();
void SetCheckedChangedInternal(bool aCheckedChanged);
bool GetCheckedChanged() const {
return mCheckedChanged;
@@ -966,17 +968,16 @@ protected:
void UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories);
/**
* Called after calling one of the SetFilesOrDirectories() functions.
* This method can explore the directory recursively if needed.
*/
void AfterSetFilesOrDirectories(bool aSetValueChanged);
- void AfterSetFilesOrDirectoriesInternal(bool aSetValueChanged);
/**
* Recursively explore the directory and populate mFileOrDirectories correctly
* for webkitdirectory.
*/
void ExploreDirectoryRecursively(bool aSetValuechanged);
/**
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -901,16 +901,21 @@ CreateBlobImpl(const ParentBlobConstruct
return nullptr;
}
if (NS_WARN_IF(params.modDate() == INT64_MAX)) {
ASSERT_UNLESS_FUZZING();
return nullptr;
}
+ if (NS_WARN_IF(!params.path().IsEmpty())) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
metadata.mContentType = params.contentType();
metadata.mName = params.name();
metadata.mLength = params.length();
metadata.mLastModifiedDate = params.modDate();
}
RefPtr<BlobImpl> blobImpl =
CreateBlobImplFromBlobData(aBlobData, metadata);
@@ -1708,16 +1713,17 @@ protected:
const bool mIsSlice;
public:
// For File.
RemoteBlobImpl(BlobChild* aActor,
BlobImpl* aRemoteBlobImpl,
const nsAString& aName,
const nsAString& aContentType,
+ const nsAString& aPath,
uint64_t aLength,
int64_t aModDate,
bool aIsSameProcessBlob);
// For Blob.
RemoteBlobImpl(BlobChild* aActor,
BlobImpl* aRemoteBlobImpl,
const nsAString& aContentType,
@@ -2019,22 +2025,25 @@ private:
* BlobChild::RemoteBlobImpl
******************************************************************************/
BlobChild::
RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor,
BlobImpl* aRemoteBlobImpl,
const nsAString& aName,
const nsAString& aContentType,
+ const nsAString& aPath,
uint64_t aLength,
int64_t aModDate,
bool aIsSameProcessBlob)
: BlobImplBase(aName, aContentType, aLength, aModDate)
, mIsSlice(false)
{
+ SetPath(aPath);
+
if (aIsSameProcessBlob) {
MOZ_ASSERT(aRemoteBlobImpl);
mSameProcessBlobImpl = aRemoteBlobImpl;
MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
} else {
mDifferentProcessBlobImpl = aRemoteBlobImpl;
}
@@ -2929,24 +2938,28 @@ BlobChild::CommonInit(BlobChild* aOther,
otherImpl->GetType(contentType);
ErrorResult rv;
uint64_t length = otherImpl->GetSize(rv);
MOZ_ASSERT(!rv.Failed());
RemoteBlobImpl* remoteBlob = nullptr;
if (otherImpl->IsFile()) {
- nsString name;
+ nsAutoString name;
otherImpl->GetName(name);
+ nsAutoString path;
+ otherImpl->GetPath(path);
+
int64_t modDate = otherImpl->GetLastModified(rv);
MOZ_ASSERT(!rv.Failed());
- remoteBlob = new RemoteBlobImpl(this, otherImpl, name, contentType, length,
- modDate, false /* SameProcessBlobImpl */);
+ remoteBlob =
+ new RemoteBlobImpl(this, otherImpl, name, contentType, path,
+ length, modDate, false /* SameProcessBlobImpl */);
} else {
remoteBlob = new RemoteBlobImpl(this, otherImpl, contentType, length,
false /* SameProcessBlobImpl */);
}
// This RemoteBlob must be kept alive untill RecvCreatedFromKnownBlob is
// called because the parent will send this notification and we must be able
// to manage it.
@@ -2986,16 +2999,17 @@ BlobChild::CommonInit(const ChildBlobCon
case AnyBlobConstructorParams::TFileBlobConstructorParams: {
const FileBlobConstructorParams& params =
blobParams.get_FileBlobConstructorParams();
remoteBlob = new RemoteBlobImpl(this,
nullptr,
params.name(),
params.contentType(),
+ params.path(),
params.length(),
params.modDate(),
false /* SameProcessBlobImpl */);
break;
}
case AnyBlobConstructorParams::TSameProcessBlobConstructorParams: {
MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
@@ -3010,27 +3024,31 @@ BlobChild::CommonInit(const ChildBlobCon
ErrorResult rv;
uint64_t size = blobImpl->GetSize(rv);
MOZ_ASSERT(!rv.Failed());
nsString contentType;
blobImpl->GetType(contentType);
if (blobImpl->IsFile()) {
- nsString name;
+ nsAutoString name;
blobImpl->GetName(name);
+ nsAutoString path;
+ blobImpl->GetPath(path);
+
int64_t lastModifiedDate = blobImpl->GetLastModified(rv);
MOZ_ASSERT(!rv.Failed());
remoteBlob =
new RemoteBlobImpl(this,
blobImpl,
name,
contentType,
+ path,
size,
lastModifiedDate,
true /* SameProcessBlobImpl */);
} else {
remoteBlob = new RemoteBlobImpl(this, blobImpl, contentType, size,
true /* SameProcessBlobImpl */);
}
@@ -3196,24 +3214,28 @@ BlobChild::GetOrCreateFromImpl(ChildMana
nsString contentType;
aBlobImpl->GetType(contentType);
ErrorResult rv;
uint64_t length = aBlobImpl->GetSize(rv);
MOZ_ASSERT(!rv.Failed());
if (aBlobImpl->IsFile()) {
- nsString name;
+ nsAutoString name;
aBlobImpl->GetName(name);
+ nsAutoString path;
+ aBlobImpl->GetPath(path);
+
int64_t modDate = aBlobImpl->GetLastModified(rv);
MOZ_ASSERT(!rv.Failed());
blobParams =
- FileBlobConstructorParams(name, contentType, length, modDate, blobData);
+ FileBlobConstructorParams(name, contentType, path, length, modDate,
+ blobData);
} else {
blobParams = NormalBlobConstructorParams(contentType, length, blobData);
}
}
BlobChild* actor = new BlobChild(aManager, aBlobImpl);
ParentBlobConstructorParams params(blobParams);
@@ -3387,16 +3409,17 @@ BlobChild::SetMysteryBlobInfo(const nsSt
MOZ_ASSERT(mBlobImpl);
MOZ_ASSERT(mRemoteBlobImpl);
MOZ_ASSERT(aLastModifiedDate != INT64_MAX);
mBlobImpl->SetLazyData(aName, aContentType, aLength, aLastModifiedDate);
FileBlobConstructorParams params(aName,
aContentType,
+ EmptyString(),
aLength,
aLastModifiedDate,
void_t() /* optionalBlobData */);
return SendResolveMystery(params);
}
bool
BlobChild::SetMysteryBlobInfo(const nsString& aContentType, uint64_t aLength)
@@ -3743,24 +3766,27 @@ BlobParent::GetOrCreateFromImpl(ParentMa
nsString contentType;
aBlobImpl->GetType(contentType);
ErrorResult rv;
uint64_t length = aBlobImpl->GetSize(rv);
MOZ_ASSERT(!rv.Failed());
if (aBlobImpl->IsFile()) {
- nsString name;
+ nsAutoString name;
aBlobImpl->GetName(name);
+ nsAutoString path;
+ aBlobImpl->GetPath(path);
+
int64_t modDate = aBlobImpl->GetLastModified(rv);
MOZ_ASSERT(!rv.Failed());
blobParams =
- FileBlobConstructorParams(name, contentType, length, modDate,
+ FileBlobConstructorParams(name, contentType, path, length, modDate,
void_t());
} else {
blobParams = NormalBlobConstructorParams(contentType, length, void_t());
}
}
}
nsID id;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -24,16 +24,17 @@
#include "mozilla/docshell/OfflineCacheUpdateChild.h"
#include "mozilla/dom/ContentBridgeChild.h"
#include "mozilla/dom/ContentBridgeParent.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/DataTransfer.h"
#include "mozilla/dom/DOMStorageIPC.h"
#include "mozilla/dom/ExternalHelperAppChild.h"
#include "mozilla/dom/FlyWebPublishedServerIPC.h"
+#include "mozilla/dom/GetFilesHelper.h"
#include "mozilla/dom/PCrashReporterChild.h"
#include "mozilla/dom/ProcessGlobal.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/workers/ServiceWorkerManager.h"
#include "mozilla/dom/nsIContentChild.h"
#include "mozilla/psm/PSMContentListener.h"
#include "mozilla/hal_sandbox/PHalChild.h"
#include "mozilla/ipc/BackgroundChild.h"
@@ -3367,10 +3368,66 @@ ContentChild::RecvNotifyPushSubscription
{
#ifndef MOZ_SIMPLEPUSH
PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal);
Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObservers()));
#endif
return true;
}
+void
+ContentChild::CreateGetFilesRequest(const nsAString& aDirectoryPath,
+ bool aRecursiveFlag,
+ nsID& aUUID,
+ GetFilesHelperChild* aChild)
+{
+ MOZ_ASSERT(aChild);
+ MOZ_ASSERT(!mGetFilesPendingRequests.GetWeak(aUUID));
+
+ Unused << SendGetFilesRequest(aUUID, nsString(aDirectoryPath),
+ aRecursiveFlag);
+ mGetFilesPendingRequests.Put(aUUID, aChild);
+}
+
+void
+ContentChild::DeleteGetFilesRequest(nsID& aUUID, GetFilesHelperChild* aChild)
+{
+ MOZ_ASSERT(aChild);
+ MOZ_ASSERT(mGetFilesPendingRequests.GetWeak(aUUID));
+
+ Unused << SendDeleteGetFilesRequest(aUUID);
+ mGetFilesPendingRequests.Remove(aUUID);
+}
+
+bool
+ContentChild::RecvGetFilesResponse(const nsID& aUUID,
+ const GetFilesResponseResult& aResult)
+{
+ GetFilesHelperChild* child = mGetFilesPendingRequests.GetWeak(aUUID);
+ // This object can already been deleted in case DeleteGetFilesRequest has
+ // been called when the response was sending by the parent.
+ if (!child) {
+ return true;
+ }
+
+ if (aResult.type() == GetFilesResponseResult::TGetFilesResponseFailure) {
+ child->Finished(aResult.get_GetFilesResponseFailure().errorCode());
+ } else {
+ MOZ_ASSERT(aResult.type() == GetFilesResponseResult::TGetFilesResponseSuccess);
+
+ const nsTArray<PBlobChild*>& blobs =
+ aResult.get_GetFilesResponseSuccess().blobsChild();
+
+ bool succeeded = true;
+ for (uint32_t i = 0; succeeded && i < blobs.Length(); ++i) {
+ RefPtr<BlobImpl> impl = static_cast<BlobChild*>(blobs[i])->GetBlobImpl();
+ succeeded = child->AppendBlobImpl(impl);
+ }
+
+ child->Finished(succeeded ? NS_OK : NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ mGetFilesPendingRequests.Remove(aUUID);
+ return true;
+}
+
} // namespace dom
} // namespace mozilla
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -11,16 +11,17 @@
#include "mozilla/dom/ContentBridgeParent.h"
#include "mozilla/dom/nsIContentChild.h"
#include "mozilla/dom/PBrowserOrId.h"
#include "mozilla/dom/PContentChild.h"
#include "nsAutoPtr.h"
#include "nsHashKeys.h"
#include "nsIObserver.h"
#include "nsTHashtable.h"
+#include "nsRefPtrHashtable.h"
#include "nsWeakPtr.h"
#include "nsIWindowProvider.h"
struct ChromePackage;
class nsIObserver;
struct SubstitutionMapping;
@@ -42,16 +43,17 @@ class PCompositorBridgeChild;
namespace dom {
class AlertObserver;
class ConsoleListener;
class PStorageChild;
class ClonedMessageData;
class TabChild;
+class GetFilesHelperChild;
class ContentChild final : public PContentChild
, public nsIWindowProvider
, public nsIContentChild
{
typedef mozilla::dom::ClonedMessageData ClonedMessageData;
typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
typedef mozilla::ipc::PFileDescriptorSetChild PFileDescriptorSetChild;
@@ -622,16 +624,30 @@ public:
DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor) override;
// Windows specific - set up audio session
virtual bool
RecvSetAudioSessionData(const nsID& aId,
const nsString& aDisplayName,
const nsString& aIconPath) override;
+
+ // GetFiles for WebKit/Blink FileSystem API and Directory API must run on the
+ // parent process.
+ void
+ CreateGetFilesRequest(const nsAString& aDirectoryPath, bool aRecursiveFlag,
+ nsID& aUUID, GetFilesHelperChild* aChild);
+
+ void
+ DeleteGetFilesRequest(nsID& aUUID, GetFilesHelperChild* aChild);
+
+ virtual bool
+ RecvGetFilesResponse(const nsID& aUUID,
+ const GetFilesResponseResult& aResult) override;
+
private:
static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure);
void StartForceKillTimer();
virtual void ActorDestroy(ActorDestroyReason why) override;
virtual void ProcessingError(Result aCode, const char* aReason) override;
@@ -659,16 +675,21 @@ private:
bool mIsAlive;
nsString mProcessName;
static ContentChild* sSingleton;
nsCOMPtr<nsIDomainPolicy> mPolicy;
nsCOMPtr<nsITimer> mForceKillTimer;
+ // Hashtable to keep track of the pending GetFilesHelper objects.
+ // This GetFilesHelperChild objects are removed when RecvGetFilesResponse is
+ // received.
+ nsRefPtrHashtable<nsIDHashKey, GetFilesHelperChild> mGetFilesPendingRequests;
+
DISALLOW_EVIL_CONSTRUCTORS(ContentChild);
};
void
InitOnContentProcessCreated();
uint64_t
NextWindowID();
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -41,16 +41,17 @@
#include "mozilla/DataStorage.h"
#include "mozilla/devtools/HeapSnapshotTempFileHelperParent.h"
#include "mozilla/docshell/OfflineCacheUpdateParent.h"
#include "mozilla/dom/DataTransfer.h"
#include "mozilla/dom/DOMStorageIPC.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/ExternalHelperAppParent.h"
+#include "mozilla/dom/GetFilesHelper.h"
#include "mozilla/dom/GeolocationBinding.h"
#ifdef MOZ_EME
#include "mozilla/dom/MediaKeySystemAccess.h"
#endif
#include "mozilla/dom/Notification.h"
#include "mozilla/dom/NuwaParent.h"
#include "mozilla/dom/PContentBridgeParent.h"
#include "mozilla/dom/PContentPermissionRequestParent.h"
@@ -5719,8 +5720,51 @@ ContentParent::HandleWindowsMessages(con
// a11y message we can reenter the ipc message sending code.
if (a11y::PDocAccessible::PDocAccessibleStart < aMsg.type() &&
a11y::PDocAccessible::PDocAccessibleEnd > aMsg.type()) {
return false;
}
return true;
}
+
+bool
+ContentParent::RecvGetFilesRequest(const nsID& aUUID,
+ const nsString& aDirectoryPath,
+ const bool& aRecursiveFlag)
+{
+ MOZ_ASSERT(!mGetFilesPendingRequests.GetWeak(aUUID));
+
+ ErrorResult rv;
+ RefPtr<GetFilesHelper> helper =
+ GetFilesHelperParent::Create(aUUID, aDirectoryPath, aRecursiveFlag, this,
+ rv);
+
+ if (NS_WARN_IF(rv.Failed())) {
+ return SendGetFilesResponse(aUUID,
+ GetFilesResponseFailure(rv.StealNSResult()));
+ }
+
+ mGetFilesPendingRequests.Put(aUUID, helper);
+ return true;
+}
+
+bool
+ContentParent::RecvDeleteGetFilesRequest(const nsID& aUUID)
+{
+ GetFilesHelper* helper = mGetFilesPendingRequests.GetWeak(aUUID);
+ if (helper) {
+ mGetFilesPendingRequests.Remove(aUUID);
+ }
+
+ return true;
+}
+
+void
+ContentParent::SendGetFilesResponseAndForget(const nsID& aUUID,
+ const GetFilesResponseResult& aResult)
+{
+ GetFilesHelper* helper = mGetFilesPendingRequests.GetWeak(aUUID);
+ if (helper) {
+ mGetFilesPendingRequests.Remove(aUUID);
+ Unused << SendGetFilesResponse(aUUID, aResult);
+ }
+}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -20,16 +20,17 @@
#include "nsDataHashtable.h"
#include "nsFrameMessageManager.h"
#include "nsHashKeys.h"
#include "nsIObserver.h"
#include "nsIThreadInternal.h"
#include "nsIDOMGeoPositionCallback.h"
#include "nsIDOMGeoPositionErrorCallback.h"
+#include "nsRefPtrHashtable.h"
#include "PermissionMessageUtils.h"
#include "DriverCrashGuard.h"
#define CHILD_PROCESS_SHUTDOWN_MESSAGE NS_LITERAL_STRING("child-process-shutdown")
class mozIApplication;
class nsConsoleService;
class nsICycleCollectorLogSink;
@@ -78,16 +79,17 @@ namespace dom {
class Element;
class TabParent;
class PStorageParent;
class ClonedMessageData;
class MemoryReport;
class TabContext;
class ContentBridgeParent;
+class GetFilesHelper;
class ContentParent final : public PContentParent
, public nsIContentParent
, public nsIObserver
, public nsIDOMGeoPositionCallback
, public nsIDOMGeoPositionErrorCallback
, public mozilla::LinkedListElement<ContentParent>
{
@@ -1163,16 +1165,28 @@ private:
virtual bool RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope,
const IPC::Principal& aPrincipal) override;
virtual bool RecvNotifyPushSubscriptionModifiedObservers(const nsCString& aScope,
const IPC::Principal& aPrincipal) override;
virtual bool RecvNotifyLowMemory() override;
+ virtual bool RecvGetFilesRequest(const nsID& aID,
+ const nsString& aDirectoryPath,
+ const bool& aRecursiveFlag) override;
+
+ virtual bool RecvDeleteGetFilesRequest(const nsID& aID) override;
+
+public:
+ void SendGetFilesResponseAndForget(const nsID& aID,
+ const GetFilesResponseResult& aResult);
+
+private:
+
// If you add strong pointers to cycle collected objects here, be sure to
// release these objects in ShutDownProcess. See the comment there for more
// details.
GeckoChildProcessHost* mSubprocess;
ContentParent* mOpener;
ContentParentId mChildID;
@@ -1258,16 +1272,20 @@ private:
mozilla::UniquePtr<SandboxBroker> mSandboxBroker;
static mozilla::UniquePtr<SandboxBrokerPolicyFactory>
sSandboxBrokerPolicyFactory;
#endif
#ifdef NS_PRINTING
RefPtr<embedding::PrintingParent> mPrintingParent;
#endif
+
+ // This hashtable is used to run GetFilesHelper objects in the parent process.
+ // GetFilesHelper can be aborted by receiving RecvDeleteGetFilesRequest.
+ nsRefPtrHashtable<nsIDHashKey, GetFilesHelper> mGetFilesPendingRequests;
};
} // namespace dom
} // namespace mozilla
class ParentIdleListener : public nsIObserver
{
friend class mozilla::dom::ContentParent;
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -66,16 +66,17 @@ struct NormalBlobConstructorParams
// be of type void_t in a parent->child message.
OptionalBlobData optionalBlobData;
};
struct FileBlobConstructorParams
{
nsString name;
nsString contentType;
+ nsString path;
uint64_t length;
int64_t modDate;
// This must be of type BlobData in a child->parent message, and will always
// be of type void_t in a parent->child message.
OptionalBlobData optionalBlobData;
};
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -352,16 +352,32 @@ struct AndroidSystemInfo
nsString device;
nsString manufacturer;
nsString release_version;
nsString hardware;
uint32_t sdk_version;
bool isTablet;
};
+struct GetFilesResponseSuccess
+{
+ PBlob[] blobs;
+};
+
+struct GetFilesResponseFailure
+{
+ nsresult errorCode;
+};
+
+union GetFilesResponseResult
+{
+ GetFilesResponseSuccess;
+ GetFilesResponseFailure;
+};
+
prio(normal upto urgent) sync protocol PContent
{
parent spawns PPluginModule;
parent opens PCompositorBridge;
parent opens PProcessHangMonitor;
parent opens PSharedBufferManager;
parent opens PImageBridge;
@@ -652,16 +668,19 @@ child:
/**
* Windows specific: associate this content process with the browsers
* audio session.
*/
async SetAudioSessionData(nsID aID,
nsString aDisplayName,
nsString aIconPath);
+
+ async GetFilesResponse(nsID aID, GetFilesResponseResult aResult);
+
parent:
/**
* Tell the content process some attributes of itself. This is
* among the first information queried by content processes after
* startup. (The message is sync to allow the content process to
* control when it receives the information.)
*
* |id| is a unique ID among all subprocesses. When |isForApp &&
@@ -1161,16 +1180,19 @@ parent:
/**
* Tell the parent process that the child process is low on memory. This
* allows the parent process to save a memory report that can potentially be
* sent with a crash report from the content process.
*/
async NotifyLowMemory();
+ async GetFilesRequest(nsID aID, nsString aDirectory, bool aRecursiveFlag);
+ async DeleteGetFilesRequest(nsID aID);
+
both:
async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
Principal aPrincipal, ClonedMessageData aData);
/**
* Notify `push-subscription-modified` observers in the parent and child.
*/
async NotifyPushSubscriptionModifiedObservers(nsCString scope,
--- a/dom/media/eme/CDMProxy.cpp
+++ b/dom/media/eme/CDMProxy.cpp
@@ -11,17 +11,17 @@
#include "mozIGeckoMediaPluginService.h"
#include "nsContentCID.h"
#include "nsServiceManagerUtils.h"
#include "MainThreadUtils.h"
#include "mozilla/EMEUtils.h"
#include "nsIConsoleService.h"
#include "prenv.h"
#include "mozilla/PodOperations.h"
-#include "mozilla/CDMCallbackProxy.h"
+#include "GMPCDMCallbackProxy.h"
#include "MediaData.h"
#include "nsPrintfCString.h"
#include "GMPService.h"
namespace mozilla {
CDMProxy::CDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem)
: mKeys(aKeys)
@@ -114,17 +114,17 @@ CDMProxy::gmp_InitDone(GMPDecryptorProxy
}
if (!aCDM) {
RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("GetGMPDecryptor failed to return a CDM"));
return;
}
mCDM = aCDM;
- mCallback = new CDMCallbackProxy(this);
+ mCallback = new GMPCDMCallbackProxy(this);
mCDM->Init(mCallback);
nsCOMPtr<nsIRunnable> task(
NewRunnableMethod<uint32_t>(this,
&CDMProxy::OnCDMCreated,
aData->mPromiseId));
NS_DispatchToMainThread(task);
}
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -15,17 +15,17 @@
#include "nsIThread.h"
#include "nsString.h"
#include "nsAutoPtr.h"
#include "GMPDecryptorProxy.h"
namespace mozilla {
class MediaRawData;
-class CDMCallbackProxy;
+class GMPCDMCallbackProxy;
namespace dom {
class MediaKeySession;
} // namespace dom
struct DecryptResult {
DecryptResult(GMPErr aStatus, MediaRawData* aSample)
: mStatus(aStatus)
@@ -333,17 +333,17 @@ private:
// Gecko Media Plugin thread. All interactions with the out-of-process
// EME plugin must come from this thread.
RefPtr<nsIThread> mGMPThread;
nsCString mNodeId;
GMPDecryptorProxy* mCDM;
CDMCaps mCapabilites;
- nsAutoPtr<CDMCallbackProxy> mCallback;
+ nsAutoPtr<GMPCDMCallbackProxy> mCallback;
// Decryption jobs sent to CDM, awaiting result.
// GMP thread only.
nsTArray<RefPtr<DecryptJob>> mDecryptionJobs;
// Number of buffers we've decrypted. Used to uniquely identify
// decryption jobs sent to CDM. Note we can't just use the length of
// mDecryptionJobs as that shrinks as jobs are completed and removed
--- a/dom/media/eme/moz.build
+++ b/dom/media/eme/moz.build
@@ -11,25 +11,23 @@ EXPORTS.mozilla.dom += [
'MediaKeys.h',
'MediaKeySession.h',
'MediaKeyStatusMap.h',
'MediaKeySystemAccess.h',
'MediaKeySystemAccessManager.h',
]
EXPORTS.mozilla += [
- 'CDMCallbackProxy.h',
'CDMCaps.h',
'CDMProxy.h',
'DetailedPromise.h',
'EMEUtils.h',
]
UNIFIED_SOURCES += [
- 'CDMCallbackProxy.cpp',
'CDMCaps.cpp',
'CDMProxy.cpp',
'DetailedPromise.cpp',
'EMEUtils.cpp',
'MediaEncryptedEvent.cpp',
'MediaKeyError.cpp',
'MediaKeyMessageEvent.cpp',
'MediaKeys.cpp',
rename from dom/media/eme/CDMCallbackProxy.cpp
rename to dom/media/gmp/GMPCDMCallbackProxy.cpp
--- a/dom/media/eme/CDMCallbackProxy.cpp
+++ b/dom/media/gmp/GMPCDMCallbackProxy.cpp
@@ -1,28 +1,28 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
-#include "mozilla/CDMCallbackProxy.h"
+#include "GMPCDMCallbackProxy.h"
#include "mozilla/CDMProxy.h"
#include "nsString.h"
#include "mozilla/dom/MediaKeys.h"
#include "mozilla/dom/MediaKeySession.h"
#include "mozIGeckoMediaPluginService.h"
#include "nsContentCID.h"
#include "nsServiceManagerUtils.h"
#include "MainThreadUtils.h"
#include "mozilla/EMEUtils.h"
namespace mozilla {
-CDMCallbackProxy::CDMCallbackProxy(CDMProxy* aProxy)
+GMPCDMCallbackProxy::GMPCDMCallbackProxy(CDMProxy* aProxy)
: mProxy(aProxy)
{
}
class SetSessionIdTask : public Runnable {
public:
SetSessionIdTask(CDMProxy* aProxy,
@@ -40,18 +40,18 @@ public:
}
RefPtr<CDMProxy> mProxy;
uint32_t mToken;
nsString mSid;
};
void
-CDMCallbackProxy::SetSessionId(uint32_t aToken,
- const nsCString& aSessionId)
+GMPCDMCallbackProxy::SetSessionId(uint32_t aToken,
+ const nsCString& aSessionId)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsCOMPtr<nsIRunnable> task(new SetSessionIdTask(mProxy,
aToken,
aSessionId));
NS_DispatchToMainThread(task);
}
@@ -73,28 +73,29 @@ public:
}
RefPtr<CDMProxy> mProxy;
dom::PromiseId mPid;
bool mSuccess;
};
void
-CDMCallbackProxy::ResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess)
+GMPCDMCallbackProxy::ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsCOMPtr<nsIRunnable> task(new LoadSessionTask(mProxy,
aPromiseId,
aSuccess));
NS_DispatchToMainThread(task);
}
void
-CDMCallbackProxy::ResolvePromise(uint32_t aPromiseId)
+GMPCDMCallbackProxy::ResolvePromise(uint32_t aPromiseId)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
// Note: CDMProxy proxies this from non-main threads to main thread.
mProxy->ResolvePromise(aPromiseId);
}
class RejectPromiseTask : public Runnable {
@@ -118,19 +119,19 @@ public:
RefPtr<CDMProxy> mProxy;
dom::PromiseId mPid;
nsresult mException;
nsCString mMsg;
};
void
-CDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
- nsresult aException,
- const nsCString& aMessage)
+GMPCDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
+ nsresult aException,
+ const nsCString& aMessage)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsCOMPtr<nsIRunnable> task;
task = new RejectPromiseTask(mProxy,
aPromiseId,
aException,
aMessage);
@@ -158,19 +159,19 @@ public:
RefPtr<CDMProxy> mProxy;
dom::PromiseId mPid;
nsString mSid;
GMPSessionMessageType mMsgType;
nsTArray<uint8_t> mMsg;
};
void
-CDMCallbackProxy::SessionMessage(const nsCString& aSessionId,
- GMPSessionMessageType aMessageType,
- const nsTArray<uint8_t>& aMessage)
+GMPCDMCallbackProxy::SessionMessage(const nsCString& aSessionId,
+ GMPSessionMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsCOMPtr<nsIRunnable> task;
task = new SessionMessageTask(mProxy,
aSessionId,
aMessageType,
aMessage);
@@ -193,30 +194,30 @@ public:
}
RefPtr<CDMProxy> mProxy;
nsString mSid;
GMPTimestamp mTimestamp;
};
void
-CDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
- GMPTimestamp aExpiryTime)
+GMPCDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
+ GMPTimestamp aExpiryTime)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsCOMPtr<nsIRunnable> task;
task = new ExpirationChangeTask(mProxy,
aSessionId,
aExpiryTime);
NS_DispatchToMainThread(task);
}
void
-CDMCallbackProxy::SessionClosed(const nsCString& aSessionId)
+GMPCDMCallbackProxy::SessionClosed(const nsCString& aSessionId)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
bool keyStatusesChange = false;
{
CDMCaps::AutoLock caps(mProxy->Capabilites());
keyStatusesChange = caps.RemoveKeysForSession(NS_ConvertUTF8toUTF16(aSessionId));
}
@@ -258,36 +259,36 @@ public:
dom::PromiseId mPid;
nsString mSid;
nsresult mException;
uint32_t mSystemCode;
nsString mMsg;
};
void
-CDMCallbackProxy::SessionError(const nsCString& aSessionId,
- nsresult aException,
- uint32_t aSystemCode,
- const nsCString& aMessage)
+GMPCDMCallbackProxy::SessionError(const nsCString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsCString& aMessage)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsCOMPtr<nsIRunnable> task;
task = new SessionErrorTask(mProxy,
aSessionId,
aException,
aSystemCode,
aMessage);
NS_DispatchToMainThread(task);
}
void
-CDMCallbackProxy::KeyStatusChanged(const nsCString& aSessionId,
- const nsTArray<uint8_t>& aKeyId,
- GMPMediaKeyStatus aStatus)
+GMPCDMCallbackProxy::KeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<uint8_t>& aKeyId,
+ GMPMediaKeyStatus aStatus)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
bool keyStatusesChange = false;
{
CDMCaps::AutoLock caps(mProxy->Capabilites());
keyStatusesChange = caps.SetKeyStatus(aKeyId,
NS_ConvertUTF8toUTF16(aSessionId),
@@ -298,26 +299,26 @@ CDMCallbackProxy::KeyStatusChanged(const
task = NewRunnableMethod<nsString>(mProxy,
&CDMProxy::OnKeyStatusesChange,
NS_ConvertUTF8toUTF16(aSessionId));
NS_DispatchToMainThread(task);
}
}
void
-CDMCallbackProxy::Decrypted(uint32_t aId,
- GMPErr aResult,
- const nsTArray<uint8_t>& aDecryptedData)
+GMPCDMCallbackProxy::Decrypted(uint32_t aId,
+ GMPErr aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
mProxy->gmp_Decrypted(aId, aResult, aDecryptedData);
}
void
-CDMCallbackProxy::Terminated()
+GMPCDMCallbackProxy::Terminated()
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
nsCOMPtr<nsIRunnable> task = NewRunnableMethod(mProxy, &CDMProxy::Terminated);
NS_DispatchToMainThread(task);
}
} // namespace mozilla
rename from dom/media/eme/CDMCallbackProxy.h
rename to dom/media/gmp/GMPCDMCallbackProxy.h
--- a/dom/media/eme/CDMCallbackProxy.h
+++ b/dom/media/gmp/GMPCDMCallbackProxy.h
@@ -1,26 +1,26 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
-#ifndef CDMCallbackProxy_h_
-#define CDMCallbackProxy_h_
+#ifndef GMPCDMCallbackProxy_h_
+#define GMPCDMCallbackProxy_h_
#include "mozilla/CDMProxy.h"
#include "gmp-decryption.h"
#include "GMPDecryptorProxy.h"
namespace mozilla {
// Proxies call backs from the CDM on the GMP thread back to the MediaKeys
// object on the main thread.
-class CDMCallbackProxy : public GMPDecryptorProxyCallback {
+class GMPCDMCallbackProxy : public GMPDecryptorProxyCallback {
public:
void SetSessionId(uint32_t aCreateSessionToken,
const nsCString& aSessionId) override;
void ResolveLoadSessionPromise(uint32_t aPromiseId,
bool aSuccess) override;
void ResolvePromise(uint32_t aPromiseId) override;
@@ -48,21 +48,21 @@ public:
GMPMediaKeyStatus aStatus) override;
void Decrypted(uint32_t aId,
GMPErr aResult,
const nsTArray<uint8_t>& aDecryptedData) override;
void Terminated() override;
- ~CDMCallbackProxy() {}
+ ~GMPCDMCallbackProxy() {}
private:
friend class CDMProxy;
- explicit CDMCallbackProxy(CDMProxy* aProxy);
+ explicit GMPCDMCallbackProxy(CDMProxy* aProxy);
// Warning: Weak ref.
CDMProxy* mProxy;
};
} // namespace mozilla
-#endif // CDMCallbackProxy_h_
+#endif // GMPCDMCallbackProxy_h_
--- a/dom/media/gmp/moz.build
+++ b/dom/media/gmp/moz.build
@@ -30,16 +30,17 @@ EXPORTS += [
'gmp-api/gmp-video-frame.h',
'gmp-api/gmp-video-host.h',
'gmp-api/gmp-video-plane.h',
'GMPAudioDecoderChild.h',
'GMPAudioDecoderParent.h',
'GMPAudioDecoderProxy.h',
'GMPAudioHost.h',
'GMPCallbackBase.h',
+ 'GMPCDMCallbackProxy.h',
'GMPChild.h',
'GMPContentChild.h',
'GMPContentParent.h',
'GMPCrashHelperHolder.h',
'GMPDecryptorChild.h',
'GMPDecryptorParent.h',
'GMPDecryptorProxy.h',
'GMPEncryptedBufferDataImpl.h',
@@ -80,16 +81,17 @@ if CONFIG['OS_TARGET'] == 'Android':
USE_LIBS += [
'rlz',
]
UNIFIED_SOURCES += [
'GMPAudioDecoderChild.cpp',
'GMPAudioDecoderParent.cpp',
'GMPAudioHost.cpp',
+ 'GMPCDMCallbackProxy.cpp',
'GMPChild.cpp',
'GMPContentChild.cpp',
'GMPContentParent.cpp',
'GMPDecryptorChild.cpp',
'GMPDecryptorParent.cpp',
'GMPDiskStorage.cpp',
'GMPEncryptedBufferDataImpl.cpp',
'GMPMemoryStorage.cpp',
--- a/dom/presentation/Presentation.cpp
+++ b/dom/presentation/Presentation.cpp
@@ -1,23 +1,26 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "Presentation.h"
+
#include <ctype.h>
+
#include "mozilla/dom/PresentationBinding.h"
#include "mozilla/dom/Promise.h"
#include "nsContentUtils.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIDocShell.h"
#include "nsIPresentationService.h"
+#include "nsSandboxFlags.h"
#include "nsServiceManagerUtils.h"
-#include "Presentation.h"
#include "PresentationReceiver.h"
using namespace mozilla;
using namespace mozilla::dom;
NS_IMPL_CYCLE_COLLECTION_INHERITED(Presentation, DOMEventTargetHelper,
mDefaultRequest, mReceiver)
@@ -52,16 +55,25 @@ Presentation::WrapObject(JSContext* aCx,
void
Presentation::SetDefaultRequest(PresentationRequest* aRequest)
{
if (IsInPresentedContent()) {
return;
}
+ nsCOMPtr<nsIDocument> doc = GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
+ if (NS_WARN_IF(!doc)) {
+ return;
+ }
+
+ if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
+ return;
+ }
+
mDefaultRequest = aRequest;
}
already_AddRefed<PresentationRequest>
Presentation::GetDefaultRequest() const
{
if (IsInPresentedContent()) {
return nullptr;
--- a/dom/presentation/PresentationRequest.cpp
+++ b/dom/presentation/PresentationRequest.cpp
@@ -1,25 +1,27 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "PresentationRequest.h"
+
#include "mozilla/dom/PresentationRequestBinding.h"
#include "mozilla/dom/PresentationConnectionAvailableEvent.h"
#include "mozilla/dom/Promise.h"
#include "mozIThirdPartyUtil.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIPresentationService.h"
#include "nsIUUIDGenerator.h"
+#include "nsSandboxFlags.h"
#include "nsServiceManagerUtils.h"
#include "PresentationAvailability.h"
#include "PresentationCallbacks.h"
-#include "PresentationRequest.h"
using namespace mozilla;
using namespace mozilla::dom;
NS_IMPL_CYCLE_COLLECTION_INHERITED(PresentationRequest, DOMEventTargetHelper,
mAvailability)
NS_IMPL_ADDREF_INHERITED(PresentationRequest, DOMEventTargetHelper)
@@ -97,21 +99,32 @@ PresentationRequest::StartWithDevice(con
// Get the origin.
nsAutoString origin;
nsresult rv = nsContentUtils::GetUTFOrigin(global->PrincipalOrNull(), origin);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return nullptr;
}
+ nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
RefPtr<Promise> promise = Promise::Create(global, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
+ if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
// Generate a session ID.
nsCOMPtr<nsIUUIDGenerator> uuidgen =
do_GetService("@mozilla.org/uuid-generator;1");
if(NS_WARN_IF(!uuidgen)) {
promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
return promise.forget();
}
@@ -143,21 +156,32 @@ already_AddRefed<Promise>
PresentationRequest::GetAvailability(ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
if (NS_WARN_IF(!global)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
+ nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
RefPtr<Promise> promise = Promise::Create(global, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
+ if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
promise->MaybeResolve(mAvailability);
return promise.forget();
}
nsresult
PresentationRequest::DispatchConnectionAvailableEvent(PresentationConnection* aConnection)
{
PresentationConnectionAvailableEventInit init;
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_sandboxed_presentation.html
@@ -0,0 +1,96 @@
+
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test allow-presentation sandboxing flag</title>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+function is(a, b, msg) {
+ window.parent.postMessage((a === b ? "OK " : "KO ") + msg, "*");
+}
+
+function ok(a, msg) {
+ window.parent.postMessage((a ? "OK " : "KO ") + msg, "*");
+}
+
+function info(msg) {
+ window.parent.postMessage("INFO " + msg, "*");
+}
+
+function command(msg) {
+ window.parent.postMessage("COMMAND " + JSON.stringify(msg), "*");
+}
+
+function finish() {
+ window.parent.postMessage("DONE", "*");
+}
+
+function testGetAvailability() {
+ return new Promise(function(aResolve, aReject) {
+ ok(navigator.presentation, "navigator.presentation should be available.");
+ var request = new PresentationRequest("http://example.com");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ ok(false, "Unexpected success, should get a security error.");
+ aReject();
+ },
+ function(aError) {
+ is(aError.name, "SecurityError", "Should get a security error.");
+ aResolve();
+ }
+ );
+ });
+}
+
+function testStartRequest() {
+ return new Promise(function(aResolve, aReject) {
+ var request = new PresentationRequest("http://example.com");
+
+ request.start().then(
+ function(aAvailability) {
+ ok(false, "Unexpected success, should get a security error.");
+ aReject();
+ },
+ function(aError) {
+ is(aError.name, "SecurityError", "Should get a security error.");
+ aResolve();
+ }
+ );
+ });
+}
+
+function testDefaultRequest() {
+ return new Promise(function(aResolve, aReject) {
+ navigator.presentation.defaultRequest = new PresentationRequest("http://example.com");
+ is(navigator.presentation.defaultRequest, null, "DefaultRequest shoud be null.");
+ aResolve();
+ });
+}
+
+function runTest() {
+ testGetAvailability()
+ .then(testStartRequest)
+ .then(testDefaultRequest)
+ .then(finish);
+}
+
+window.addEventListener("message", function onMessage(evt) {
+ window.removeEventListener("message", onMessage);
+ if (evt.data === "start") {
+ runTest();
+ }
+}, false);
+
+window.setTimeout(function() {
+ command("ready-to-start");
+}, 3000);
+
+</script>
+</head>
+<body>
+</body>
+</html>
--- a/dom/presentation/tests/mochitest/mochitest.ini
+++ b/dom/presentation/tests/mochitest/mochitest.ini
@@ -9,16 +9,17 @@ support-files =
file_presentation_non_receiver.html
file_presentation_receiver.html
file_presentation_receiver_establish_connection_error.html
file_presentation_receiver_inner_iframe.html
file_presentation_1ua_wentaway.html
test_presentation_1ua_connection_wentaway.js
file_presentation_receiver_auxiliary_navigation.html
test_presentation_receiver_auxiliary_navigation.js
+ file_presentation_sandboxed_presentation.html
[test_presentation_dc_sender.html]
[test_presentation_dc_receiver.html]
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
[test_presentation_dc_receiver_oop.html]
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
[test_presentation_1ua_sender_and_receiver.html]
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
@@ -47,8 +48,9 @@ skip-if = (e10s || toolkit == 'gonk' ||
[test_presentation_tcp_receiver.html]
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
[test_presentation_tcp_receiver_oop.html]
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
[test_presentation_receiver_auxiliary_navigation_inproc.html]
skip-if = (e10s || toolkit == 'gonk')
[test_presentation_receiver_auxiliary_navigation_oop.html]
skip-if = (e10s || toolkit == 'gonk')
+[test_presentation_sandboxed_presentation.html]
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_sandboxed_presentation.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test default request for B2G Presentation API at sender side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1268758">Test allow-presentation sandboxing flag</a>
+<iframe sandbox="allow-popups allow-scripts allow-same-origin" id="iframe" src="file_presentation_sandboxed_presentation.html"></iframe>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+var iframe = document.getElementById("iframe");
+var readyToStart = false;
+var testSetuped = false;
+function setup() {
+ return new Promise(function(aResolve, aReject) {
+ addEventListener("message", function listener(event) {
+ var message = event.data;
+ if (/^OK /.exec(message)) {
+ ok(true, message.replace(/^OK /, ""));
+ } else if (/^KO /.exec(message)) {
+ ok(false, message.replace(/^KO /, ""));
+ } else if (/^INFO /.exec(message)) {
+ info(message.replace(/^INFO /, ""));
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ""));
+ if (command === "ready-to-start") {
+ readyToStart = true;
+ startTest();
+ }
+ } else if (/^DONE$/.exec(message)) {
+ window.removeEventListener('message', listener);
+ SimpleTest.finish();
+ }
+ }, false);
+
+ testSetuped = true;
+ aResolve();
+ });
+}
+
+iframe.onload = startTest();
+
+function startTest() {
+ if (!(testSetuped && readyToStart)) {
+ return;
+ }
+ iframe.contentWindow.postMessage("start", "*");
+}
+
+function runTests() {
+ ok(navigator.presentation, "navigator.presentation should be available.");
+ setup().then(startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: "presentation-device-manage", allow: false, context: document},
+ {type: "presentation", allow: true, context: document},
+ {type: "presentation", allow: true, context: iframe.contentDocument},
+], function() {
+ SpecialPowers.pushPrefEnv({ "set": [["dom.presentation.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", false]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
--- a/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html
+++ b/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html
@@ -59,21 +59,21 @@ var policy = {
context, mimeTypeGuess, extra) {
// make sure we get the right amount of content policy calls
// e.g. about:blank also gets chrcked by content policies
if (contentLocation.asciiSpec === EXPECTED_URL) {
is(contentType, EXPECTED_CONTENT_TYPE,
"content policy type should TYPESUBDOCUMENT");
testCounter++;
- }
-
- if (testCounter === EXPECTED_RESULTS) {
- categoryManager.deleteCategoryEntry("content-policy", POLICYNAME, false);
- SimpleTest.finish();
+
+ if (testCounter === EXPECTED_RESULTS) {
+ categoryManager.deleteCategoryEntry("content-policy", POLICYNAME, false);
+ SimpleTest.finish();
+ }
}
return Ci.nsIContentPolicy.ACCEPT;
},
shouldProcess: function(contentType, contentLocation, requestOrigin,
context, mimeTypeGuess, extra) {
return Ci.nsIContentPolicy.ACCEPT;
}
--- a/dom/webidl/FormData.webidl
+++ b/dom/webidl/FormData.webidl
@@ -2,17 +2,17 @@
/* 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/.
*
* The origin of this IDL file is
* http://xhr.spec.whatwg.org
*/
-typedef (Blob or USVString) FormDataEntryValue;
+typedef (Blob or Directory or USVString) FormDataEntryValue;
[Constructor(optional HTMLFormElement form),
Exposed=(Window,Worker)]
interface FormData {
[Throws]
void append(USVString name, Blob value, optional USVString filename);
[Throws]
void append(USVString name, USVString value);
--- a/editor/libeditor/HTMLAnonymousNodeEditor.cpp
+++ b/editor/libeditor/HTMLAnonymousNodeEditor.cpp
@@ -24,17 +24,17 @@
#include "nsIDOMHTMLElement.h"
#include "nsIDOMNode.h"
#include "nsIDOMWindow.h"
#include "nsIDocument.h"
#include "nsIDocumentObserver.h"
#include "nsIHTMLAbsPosEditor.h"
#include "nsIHTMLInlineTableEditor.h"
#include "nsIHTMLObjectResizer.h"
-#include "nsIMutationObserver.h"
+#include "nsStubMutationObserver.h"
#include "nsINode.h"
#include "nsIPresShell.h"
#include "nsISupportsImpl.h"
#include "nsISupportsUtils.h"
#include "nsLiteralString.h"
#include "nsPresContext.h"
#include "nsReadableUtils.h"
#include "nsString.h"
@@ -87,46 +87,77 @@ static int32_t GetCSSFloatValue(nsIDOMCS
f = 5;
break;
}
}
return (int32_t) f;
}
-class ElementDeletionObserver final : public nsIMutationObserver
+class ElementDeletionObserver final : public nsStubMutationObserver
{
public:
- ElementDeletionObserver(nsINode* aNativeAnonNode, nsINode* aObservedNode)
+ ElementDeletionObserver(nsIContent* aNativeAnonNode,
+ nsIContent* aObservedNode)
: mNativeAnonNode(aNativeAnonNode)
, mObservedNode(aObservedNode)
{}
NS_DECL_ISUPPORTS
- NS_DECL_NSIMUTATIONOBSERVER
+ NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
protected:
~ElementDeletionObserver() {}
- nsINode* mNativeAnonNode;
- nsINode* mObservedNode;
+ nsIContent* mNativeAnonNode;
+ nsIContent* mObservedNode;
};
NS_IMPL_ISUPPORTS(ElementDeletionObserver, nsIMutationObserver)
-NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(ElementDeletionObserver)
+
+void
+ElementDeletionObserver::ParentChainChanged(nsIContent* aContent)
+{
+ // If the native anonymous content has been unbound already in
+ // DeleteRefToAnonymousNode, mNativeAnonNode's parentNode is null.
+ if (aContent == mObservedNode && mNativeAnonNode &&
+ mNativeAnonNode->GetParentNode() == aContent) {
+ // If the observed node has been moved to another document, there isn't much
+ // we can do easily. But at least be safe and unbind the native anonymous
+ // content and stop observing changes.
+ if (mNativeAnonNode->OwnerDoc() != mObservedNode->OwnerDoc()) {
+ mObservedNode->RemoveMutationObserver(this);
+ mObservedNode = nullptr;
+ mNativeAnonNode->RemoveMutationObserver(this);
+ mNativeAnonNode->UnbindFromTree();
+ mNativeAnonNode = nullptr;
+ NS_RELEASE_THIS();
+ return;
+ }
+
+ // We're staying in the same document, just rebind the native anonymous
+ // node so that the subtree root points to the right object etc.
+ mNativeAnonNode->UnbindFromTree();
+ mNativeAnonNode->BindToTree(mObservedNode->GetUncomposedDoc(), mObservedNode,
+ mObservedNode, true);
+ }
+}
void
ElementDeletionObserver::NodeWillBeDestroyed(const nsINode* aNode)
{
NS_ASSERTION(aNode == mNativeAnonNode || aNode == mObservedNode,
"Wrong aNode!");
if (aNode == mNativeAnonNode) {
mObservedNode->RemoveMutationObserver(this);
+ mObservedNode = nullptr;
} else {
mNativeAnonNode->RemoveMutationObserver(this);
- static_cast<nsIContent*>(mNativeAnonNode)->UnbindFromTree();
+ mNativeAnonNode->UnbindFromTree();
+ mNativeAnonNode = nullptr;
}
NS_RELEASE_THIS();
}
// Returns in *aReturn an anonymous nsDOMElement of type aTag,
// child of aParentNode. If aIsCreatedHidden is true, the class
// "hidden" is added to the created element. If aAnonClass is not
@@ -237,30 +268,30 @@ HTMLEditor::DeleteRefToAnonymousNode(nsI
if (aElement) {
nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
if (content) {
nsAutoScriptBlocker scriptBlocker;
// Need to check whether aShell has been destroyed (but not yet deleted).
// In that case presContext->GetPresShell() returns nullptr.
// See bug 338129.
- if (aShell && aShell->GetPresContext() &&
+ if (content->IsInComposedDoc() && aShell && aShell->GetPresContext() &&
aShell->GetPresContext()->GetPresShell() == aShell) {
nsCOMPtr<nsIDocumentObserver> docObserver = do_QueryInterface(aShell);
if (docObserver) {
// Call BeginUpdate() so that the nsCSSFrameConstructor/PresShell
// knows we're messing with the frame tree.
nsCOMPtr<nsIDocument> document = GetDocument();
if (document)
docObserver->BeginUpdate(document, UPDATE_CONTENT_MODEL);
// XXX This is wrong (bug 439258). Once it's fixed, the NS_WARNING
// in RestyleManager::RestyleForRemove should be changed back
// to an assertion.
- docObserver->ContentRemoved(content->GetUncomposedDoc(),
+ docObserver->ContentRemoved(content->GetComposedDoc(),
aParentContent, content, -1,
content->GetPreviousSibling());
if (document)
docObserver->EndUpdate(document, UPDATE_CONTENT_MODEL);
}
}
content->UnbindFromTree();
}
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -2240,16 +2240,46 @@ NS_IMETHODIMP nsPermissionManager::GetEn
permEntry.mExpireType,
permEntry.mExpireTime));
}
}
return NS_NewArrayEnumerator(aEnum, array);
}
+NS_IMETHODIMP nsPermissionManager::GetAllForURI(nsIURI* aURI, nsISimpleEnumerator **aEnum)
+{
+ nsCOMArray<nsIPermission> array;
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<PermissionKey> key = new PermissionKey(principal);
+ PermissionHashKey* entry = mPermissionTable.GetEntry(key);
+
+ if (entry) {
+ for (const auto& permEntry : entry->GetPermissions()) {
+ // Only return custom permissions
+ if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+ continue;
+ }
+
+ array.AppendObject(
+ new nsPermission(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime));
+ }
+ }
+
+ return NS_NewArrayEnumerator(aEnum, array);
+}
+
NS_IMETHODIMP nsPermissionManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
{
ENSURE_NOT_CHILD_PROCESS;
if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
// The profile is about to change,
// or is going away because the application is shutting down.
mIsShuttingDown = true;
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_getAllForURI.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function check_enumerator(uri, permissions) {
+ let pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+
+ let enumerator = pm.getAllForURI(uri);
+ for ([type, capability] of permissions) {
+ let perm = enumerator.getNext();
+ do_check_true(perm != null);
+ do_check_true(perm.principal.URI.equals(uri));
+ do_check_eq(perm.type, type);
+ do_check_eq(perm.capability, capability);
+ do_check_eq(perm.expireType, pm.EXPIRE_NEVER);
+ }
+ do_check_false(enumerator.hasMoreElements());
+}
+
+function run_test() {
+ let pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+
+ let uri = NetUtil.newURI("http://example.com");
+ let sub = NetUtil.newURI("http://sub.example.com");
+
+ check_enumerator(uri, [ ]);
+
+ pm.add(uri, "test/getallforuri", pm.ALLOW_ACTION);
+ check_enumerator(uri, [
+ [ "test/getallforuri", pm.ALLOW_ACTION ]
+ ]);
+
+ // check that uris are matched exactly
+ check_enumerator(sub, [ ]);
+
+ pm.add(sub, "test/getallforuri", pm.PROMPT_ACTION);
+ pm.add(sub, "test/getallforuri2", pm.DENY_ACTION);
+
+ check_enumerator(sub, [
+ [ "test/getallforuri", pm.PROMPT_ACTION ],
+ [ "test/getallforuri2", pm.DENY_ACTION ]
+ ]);
+
+ // check that the original uri list has not changed
+ check_enumerator(uri, [
+ [ "test/getallforuri", pm.ALLOW_ACTION ]
+ ]);
+
+ // check that UNKNOWN_ACTION permissions are ignored
+ pm.add(uri, "test/getallforuri2", pm.UNKNOWN_ACTION);
+ pm.add(uri, "test/getallforuri3", pm.DENY_ACTION);
+
+ check_enumerator(uri, [
+ [ "test/getallforuri", pm.ALLOW_ACTION ],
+ [ "test/getallforuri3", pm.DENY_ACTION ]
+ ]);
+
+ // check that permission updates are reflected
+ pm.add(uri, "test/getallforuri", pm.PROMPT_ACTION);
+
+ check_enumerator(uri, [
+ [ "test/getallforuri", pm.PROMPT_ACTION ],
+ [ "test/getallforuri3", pm.DENY_ACTION ]
+ ]);
+
+ // check that permission removals are reflected
+ pm.remove(uri, "test/getallforuri");
+
+ check_enumerator(uri, [
+ [ "test/getallforuri3", pm.DENY_ACTION ]
+ ]);
+
+ pm.removeAll();
+ check_enumerator(uri, [ ]);
+ check_enumerator(sub, [ ]);
+}
+
--- a/extensions/cookie/test/unit/xpcshell.ini
+++ b/extensions/cookie/test/unit/xpcshell.ini
@@ -17,16 +17,17 @@ skip-if = true # Bug 863738
[test_cookies_read.js]
[test_cookies_sync_failure.js]
[test_cookies_thirdparty.js]
[test_cookies_thirdparty_session.js]
[test_domain_eviction.js]
[test_eviction.js]
[test_permmanager_defaults.js]
[test_permmanager_expiration.js]
+[test_permmanager_getAllForURI.js]
[test_permmanager_getPermissionObject.js]
[test_permmanager_notifications.js]
[test_permmanager_removeall.js]
[test_permmanager_removesince.js]
[test_permmanager_removeforapp.js]
[test_permmanager_load_invalid_entries.js]
skip-if = debug == true
[test_permmanager_idn.js]
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1586,16 +1586,23 @@ public:
private:
#define GLYPH_BUFFER_SIZE (2048/sizeof(Glyph))
Glyph *AppendGlyph()
{
return &mGlyphBuffer[mNumGlyphs++];
}
+ static DrawMode
+ GetStrokeMode(DrawMode aMode)
+ {
+ return aMode & (DrawMode::GLYPH_STROKE |
+ DrawMode::GLYPH_STROKE_UNDERNEATH);
+ }
+
// Render the buffered glyphs to the draw target and clear the buffer.
// This actually flushes the glyphs only if the buffer is full, or if the
// aFinish parameter is true; otherwise it simply returns.
void Flush(bool aFinish)
{
// Ensure there's enough room for a glyph to be added to the buffer
if ((!aFinish && mNumGlyphs < GLYPH_BUFFER_SIZE) || !mNumGlyphs) {
return;
@@ -1674,41 +1681,79 @@ private:
mFontParams.renderingOptions);
} else {
mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
ColorPattern(state.color),
mFontParams.drawOptions,
mFontParams.renderingOptions);
}
}
- if ((mRunParams.drawMode &
- (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH)) ==
- DrawMode::GLYPH_STROKE) {
- state.color = gfx::Color::FromABGR(mRunParams.textStrokeColor);
- state.strokeOptions.mLineWidth = mRunParams.textStrokeWidth;
- FlushStroke(buf, state);
+ if (GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE &&
+ mRunParams.strokeOpts) {
+ Pattern *pat;
+ if (mRunParams.textStrokePattern) {
+ pat = mRunParams.textStrokePattern->GetPattern(
+ mRunParams.dt, state.patternTransformChanged
+ ? &state.patternTransform
+ : nullptr);
+
+ if (pat) {
+ Matrix saved;
+ Matrix *mat = nullptr;
+ if (mFontParams.passedInvMatrix) {
+ // The brush matrix needs to be multiplied with the
+ // inverted matrix as well, to move the brush into the
+ // space of the glyphs.
+
+ // This relies on the returned Pattern not to be reused
+ // by others, but regenerated on GetPattern calls. This
+ // is true!
+ if (pat->GetType() == PatternType::LINEAR_GRADIENT) {
+ mat = &static_cast<LinearGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::RADIAL_GRADIENT) {
+ mat = &static_cast<RadialGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::SURFACE) {
+ mat = &static_cast<SurfacePattern*>(pat)->mMatrix;
+ }
+
+ if (mat) {
+ saved = *mat;
+ *mat = (*mat) * (*mFontParams.passedInvMatrix);
+ }
+ }
+ FlushStroke(buf, *pat);
+
+ if (mat) {
+ *mat = saved;
+ }
+ }
+ } else {
+ FlushStroke(buf,
+ ColorPattern(
+ Color::FromABGR(mRunParams.textStrokeColor)));
+ }
}
if (mRunParams.drawMode & DrawMode::GLYPH_PATH) {
mRunParams.context->EnsurePathBuilder();
Matrix mat = mRunParams.dt->GetTransform();
mFontParams.scaledFont->CopyGlyphsToBuilder(
buf, mRunParams.context->mPathBuilder,
mRunParams.dt->GetBackendType(), &mat);
}
mNumGlyphs = 0;
}
- void FlushStroke(gfx::GlyphBuffer& aBuf, gfxContext::AzureState& aState)
+ void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern)
{
RefPtr<Path> path =
mFontParams.scaledFont->GetPathForGlyphs(aBuf, mRunParams.dt);
- mRunParams.dt->Stroke(path,
- ColorPattern(aState.color),
- aState.strokeOptions);
+ mRunParams.dt->Stroke(path, aPattern, *mRunParams.strokeOpts,
+ (mRunParams.drawOpts) ? *mRunParams.drawOpts
+ : DrawOptions());
}
Glyph mGlyphBuffer[GLYPH_BUFFER_SIZE];
unsigned int mNumGlyphs;
#undef GLYPH_BUFFER_SIZE
};
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -6,16 +6,17 @@
#ifndef GFX_FONT_H
#define GFX_FONT_H
#include "gfxTypes.h"
#include "gfxFontEntry.h"
#include "nsString.h"
#include "gfxPoint.h"
+#include "gfxPattern.h"
#include "nsTArray.h"
#include "nsTHashtable.h"
#include "nsHashKeys.h"
#include "gfxRect.h"
#include "nsExpirationTracker.h"
#include "gfxPlatform.h"
#include "nsIAtom.h"
#include "mozilla/HashFunctions.h"
@@ -2183,18 +2184,20 @@ struct TextRunDrawParams {
RefPtr<mozilla::gfx::DrawTarget> dt;
gfxContext *context;
gfxFont::Spacing *spacing;
gfxTextRunDrawCallbacks *callbacks;
gfxTextContextPaint *runContextPaint;
mozilla::gfx::Color fontSmoothingBGColor;
gfxFloat direction;
double devPerApp;
- float textStrokeWidth;
nscolor textStrokeColor;
+ gfxPattern *textStrokePattern;
+ const mozilla::gfx::StrokeOptions *strokeOpts;
+ const mozilla::gfx::DrawOptions *drawOpts;
DrawMode drawMode;
bool isVerticalRun;
bool isRTL;
bool paintSVGGlyphs;
};
struct FontDrawParams {
RefPtr<mozilla::gfx::ScaledFont> scaledFont;
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -624,18 +624,20 @@ gfxTextRun::Draw(Range aRange, gfxPoint
// Set up parameters that will be constant across all glyph runs we need
// to draw, regardless of the font used.
TextRunDrawParams params;
params.context = aParams.context;
params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit());
params.isVerticalRun = IsVertical();
params.isRTL = IsRightToLeft();
params.direction = direction;
- params.textStrokeWidth = aParams.textStrokeWidth;
+ params.strokeOpts = aParams.strokeOpts;
params.textStrokeColor = aParams.textStrokeColor;
+ params.textStrokePattern = aParams.textStrokePattern;
+ params.drawOpts = aParams.drawOpts;
params.drawMode = aParams.drawMode;
params.callbacks = aParams.callbacks;
params.runContextPaint = aParams.contextPaint;
params.paintSVGGlyphs = !aParams.callbacks ||
aParams.callbacks->mShouldPaintSVGGlyphs;
params.dt = aParams.context->GetDrawTarget();
params.fontSmoothingBGColor =
aParams.context->GetFontSmoothingBackgroundColor();
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -233,17 +233,19 @@ public:
uint32_t mCurrentChar;
};
struct DrawParams
{
gfxContext* context;
DrawMode drawMode = DrawMode::GLYPH_FILL;
nscolor textStrokeColor = 0;
- float textStrokeWidth = 0.0f;
+ gfxPattern* textStrokePattern = nullptr;
+ const mozilla::gfx::StrokeOptions *strokeOpts = nullptr;
+ const mozilla::gfx::DrawOptions *drawOpts = nullptr;
PropertyProvider* provider = nullptr;
// If non-null, the advance width of the substring is set.
gfxFloat* advanceWidth = nullptr;
gfxTextContextPaint* contextPaint = nullptr;
gfxTextRunDrawCallbacks* callbacks = nullptr;
explicit DrawParams(gfxContext* aContext) : context(aContext) {}
};
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -55,20 +55,16 @@ namespace mozilla {
using namespace gfx;
using namespace layers;
namespace image {
using std::ceil;
using std::min;
-// The maximum number of times any one RasterImage was decoded. This is only
-// used for statistics.
-static int32_t sMaxDecodeCount = 0;
-
#ifndef DEBUG
NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties)
#else
NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties,
imgIContainerDebug)
#endif
//******************************************************************************
@@ -107,23 +103,16 @@ RasterImage::~RasterImage()
mSourceBuffer->Complete(NS_ERROR_ABORT);
}
// Release all frames from the surface cache.
SurfaceCache::RemoveImage(ImageKey(this));
// Record Telemetry.
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_COUNT, mDecodeCount);
-
- if (mDecodeCount > sMaxDecodeCount) {
- sMaxDecodeCount = mDecodeCount;
- // Clear out any previously collected data first.
- Telemetry::ClearHistogram(Telemetry::IMAGE_MAX_DECODE_COUNT);
- Telemetry::Accumulate(Telemetry::IMAGE_MAX_DECODE_COUNT, sMaxDecodeCount);
- }
}
nsresult
RasterImage::Init(const char* aMimeType,
uint32_t aFlags)
{
// We don't support re-initialization
if (mInitialized) {
--- a/js/src/asmjs/WasmBaselineCompile.cpp
+++ b/js/src/asmjs/WasmBaselineCompile.cpp
@@ -24,23 +24,22 @@
*
* Unimplemented functionality:
*
* - This is not actually a baseline compiler, as it performs no
* profiling and does not trigger ion compilation and function
* replacement (duh)
* - int64 load and store
* - SIMD
- * - Atomics
+ * - Atomics (very simple now, we have range checking)
* - current_memory, grow_memory
* - non-signaling interrupts
* - non-signaling bounds checks
* - profiler support (devtools)
* - Platform support:
- * x86
* ARM-32
* ARM-64
*
* There are lots of machine dependencies here but they are pretty
* well isolated to a segment of the compiler. Many dependencies
* will eventually be factored into the MacroAssembler layer and shared
* with other code generators.
*
@@ -120,16 +119,17 @@
using mozilla::DebugOnly;
using mozilla::FloatingPoint;
using mozilla::SpecificNaN;
namespace js {
namespace wasm {
using namespace js::jit;
+using JS::GenericNaN;
struct BaseCompilePolicy : ExprIterPolicy
{
static const bool Output = true;
// The baseline compiler tracks values on a stack of its own -- it
// needs to scan that stack for spilling -- and thus has no need
// for the values maintained by the iterator.
@@ -299,20 +299,49 @@ class BaseCompiler
struct AnyReg
{
AnyReg() { tag = NONE; }
explicit AnyReg(RegI32 r) { tag = I32; i32_ = r; }
explicit AnyReg(RegI64 r) { tag = I64; i64_ = r; }
explicit AnyReg(RegF32 r) { tag = F32; f32_ = r; }
explicit AnyReg(RegF64 r) { tag = F64; f64_ = r; }
- RegI32 i32() { MOZ_ASSERT(tag == I32); return i32_; }
- RegI64 i64() { MOZ_ASSERT(tag == I64); return i64_; }
- RegF32 f32() { MOZ_ASSERT(tag == F32); return f32_; }
- RegF64 f64() { MOZ_ASSERT(tag == F64); return f64_; }
+ RegI32 i32() {
+ MOZ_ASSERT(tag == I32);
+ return i32_;
+ }
+ RegI64 i64() {
+ MOZ_ASSERT(tag == I64);
+ return i64_;
+ }
+ RegF32 f32() {
+ MOZ_ASSERT(tag == F32);
+ return f32_;
+ }
+ RegF64 f64() {
+ MOZ_ASSERT(tag == F64);
+ return f64_;
+ }
+ AnyRegister any() {
+ switch (tag) {
+ case F32: return AnyRegister(f32_.reg);
+ case F64: return AnyRegister(f64_.reg);
+ case I32: return AnyRegister(i32_.reg);
+ case I64:
+#ifdef JS_PUNBOX64
+ return AnyRegister(i64_.reg.reg);
+#else
+ MOZ_CRASH("WasmBaseline platform hook: AnyReg::any()");
+#endif
+ case NONE:
+ MOZ_CRASH("AnyReg::any() on NONE");
+ }
+ // Work around GCC 5 analysis/warning bug.
+ MOZ_CRASH("AnyReg::any(): impossible case");
+ }
union {
RegI32 i32_;
RegI64 i64_;
RegF32 f32_;
RegF64 f64_;
};
enum { NONE, I32, I64, F32, F64 } tag;
@@ -452,16 +481,20 @@ class BaseCompiler
#endif
#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
RegI32 specific_eax;
RegI32 specific_ecx;
RegI32 specific_edx;
#endif
+#if defined(JS_CODEGEN_X86)
+ AllocatableGeneralRegisterSet singleByteRegs_;
+#endif
+
// The join registers are used to carry values out of blocks.
// JoinRegI32 and joinRegI64 must overlap: emitBrIf and
// emitBrTable assume that.
RegI32 joinRegI32;
RegI64 joinRegI64;
RegF32 joinRegF32;
RegF64 joinRegF64;
@@ -1678,16 +1711,19 @@ class BaseCompiler
}
Control& controlItem(uint32_t relativeDepth) {
return ctl_[ctl_.length() - 1 - relativeDepth];
}
MOZ_MUST_USE
PooledLabel* newLabel() {
+ // TODO / INVESTIGATE: allocate() is fallible, but we can
+ // probably rely on an infallible allocator here. That would
+ // simplify code later.
PooledLabel* candidate = labelPool_.allocate();
if (!candidate)
return nullptr;
return new (candidate) PooledLabel(this);
}
void freeLabel(PooledLabel* label) {
label->~PooledLabel();
@@ -1855,17 +1891,17 @@ class BaseCompiler
#elif defined(JS_CODEGEN_X86)
// Nothing
#else
MOZ_CRASH("BaseCompiler platform hook: beginCall");
#endif
}
call.frameAlignAdjustment_ = ComputeByteAlignment(masm.framePushed() + sizeof(AsmJSFrame),
- ABIStackAlignment);
+ JitStackAlignment);
}
void endCall(FunctionCall& call)
{
if (call.machineStateAreaSize_ || call.frameAlignAdjustment_) {
int size = call.calleePopsArgs_ ? 0 : call.stackArgAreaSize_;
if (call.callSavesMachineState_) {
#if defined(JS_CODEGEN_X64)
@@ -2022,22 +2058,26 @@ class BaseCompiler
}
#if defined(JS_CODEGEN_X64)
// CodeGeneratorX64::visitAsmJSLoadFuncPtr()
{
ScratchI32 scratch(*this);
CodeOffset label = masm.loadRipRelativeInt64(scratch);
masm.append(AsmJSGlobalAccess(label, globalDataOffset));
- masm.loadPtr(Operand(scratch, ptrReg, TimesEight, 0), ptrReg);
+ masm.loadPtr(Operand(scratch, ptrReg, ScalePointer, 0), ptrReg);
}
#elif defined(JS_CODEGEN_X86)
// CodeGeneratorX86::visitAsmJSLoadFuncPtr()
- CodeOffset label = masm.movlWithPatch(PatchedAbsoluteAddress(), ptrReg, TimesFour, ptrReg);
- masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+ {
+ ScratchI32 scratch(*this);
+ CodeOffset label = masm.movlWithPatch(PatchedAbsoluteAddress(), scratch);
+ masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+ masm.loadPtr(Operand(scratch, ptrReg, ScalePointer), ptrReg);
+ }
#else
MOZ_CRASH("BaseCompiler platform hook: funcPtrCall");
#endif
callDynamic(ptrReg, call);
}
// Precondition: sync()
@@ -2614,167 +2654,343 @@ class BaseCompiler
masm.breakpoint();
#endif
}
//////////////////////////////////////////////////////////////////////
//
// Global variable access.
- // CodeGeneratorX64::visitAsmJSLoadGlobalVar()
+ // CodeGenerator{X86,X64}::visitAsmJSLoadGlobalVar()
void loadGlobalVarI32(unsigned globalDataOffset, RegI32 r)
{
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
CodeOffset label = masm.loadRipRelativeInt32(r.reg);
masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+ CodeOffset label = masm.movlWithPatch(PatchedAbsoluteAddress(), r.reg);
+ masm.append(AsmJSGlobalAccess(label, globalDataOffset));
#else
MOZ_CRASH("BaseCompiler platform hook: loadGlobalVarI32");
#endif
}
void loadGlobalVarI64(unsigned globalDataOffset, RegI64 r)
{
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
CodeOffset label = masm.loadRipRelativeInt64(r.reg.reg);
masm.append(AsmJSGlobalAccess(label, globalDataOffset));
#else
MOZ_CRASH("BaseCompiler platform hook: loadGlobalVarI64");
#endif
}
void loadGlobalVarF32(unsigned globalDataOffset, RegF32 r)
{
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
CodeOffset label = masm.loadRipRelativeFloat32(r.reg);
masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+ CodeOffset label = masm.vmovssWithPatch(PatchedAbsoluteAddress(), r.reg);
+ masm.append(AsmJSGlobalAccess(label, globalDataOffset));
#else
MOZ_CRASH("BaseCompiler platform hook: loadGlobalVarF32");
#endif
}
void loadGlobalVarF64(unsigned globalDataOffset, RegF64 r)
{
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
CodeOffset label = masm.loadRipRelativeDouble(r.reg);
masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+ CodeOffset label = masm.vmovsdWithPatch(PatchedAbsoluteAddress(), r.reg);
+ masm.append(AsmJSGlobalAccess(label, globalDataOffset));
#else
MOZ_CRASH("BaseCompiler platform hook: loadGlobalVarF32");
#endif
}
// CodeGeneratorX64::visitAsmJSStoreGlobalVar()
void storeGlobalVarI32(unsigned globalDataOffset, RegI32 r)
{
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
CodeOffset label = masm.storeRipRelativeInt32(r.reg);
masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+ CodeOffset label = masm.movlWithPatch(r.reg, PatchedAbsoluteAddress());
+ masm.append(AsmJSGlobalAccess(label, globalDataOffset));
#else
MOZ_CRASH("BaseCompiler platform hook: storeGlobalVarI32");
#endif
}
void storeGlobalVarI64(unsigned globalDataOffset, RegI64 r)
{
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
CodeOffset label = masm.storeRipRelativeInt64(r.reg.reg);
masm.append(AsmJSGlobalAccess(label, globalDataOffset));
#else
MOZ_CRASH("BaseCompiler platform hook: storeGlobalVarI64");
#endif
}
void storeGlobalVarF32(unsigned globalDataOffset, RegF32 r)
{
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
CodeOffset label = masm.storeRipRelativeFloat32(r.reg);
masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+ CodeOffset label = masm.vmovssWithPatch(r.reg, PatchedAbsoluteAddress());
+ masm.append(AsmJSGlobalAccess(label, globalDataOffset));
#else
MOZ_CRASH("BaseCompiler platform hook: storeGlobalVarF32");
#endif
}
void storeGlobalVarF64(unsigned globalDataOffset, RegF64 r)
{
-#ifdef JS_CODEGEN_X64
+#if defined(JS_CODEGEN_X64)
CodeOffset label = masm.storeRipRelativeDouble(r.reg);
masm.append(AsmJSGlobalAccess(label, globalDataOffset));
+#elif defined(JS_CODEGEN_X86)
+ CodeOffset label = masm.vmovsdWithPatch(r.reg, PatchedAbsoluteAddress());
+ masm.append(AsmJSGlobalAccess(label, globalDataOffset));
#else
MOZ_CRASH("BaseCompiler platform hook: storeGlobalVarF64");
#endif
}
//////////////////////////////////////////////////////////////////////
//
// Heap access.
-#if defined(JS_CODEGEN_X64)
- // Copied from CodeGenerator-x64.cpp
- // TODO / CLEANUP - share with the code generator.
- MemoryAccess
- WasmMemoryAccess(uint32_t before)
- {
- if (isCompilingAsmJS())
- return MemoryAccess(before, MemoryAccess::CarryOn, MemoryAccess::WrapOffset);
- return MemoryAccess(before, MemoryAccess::Throw, MemoryAccess::DontWrapOffset);
- }
-#endif
-
- void memoryBarrier(MemoryBarrierBits barrier) {
-#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
- if (barrier & MembarStoreLoad)
- masm.storeLoadFence();
-#else
- MOZ_CRASH("BaseCompiler platform hook: memoryBarrier");
-#endif
- }
-
- // Cloned from MIRGraph.cpp, merge somehow?
+ // TODO / CLEANUP - cloned from MIRGraph.cpp, should share.
bool needsBoundsCheckBranch(const MWasmMemoryAccess& access) const {
// A heap access needs a bounds-check branch if we're not relying on signal
// handlers to catch errors, and if it's not proven to be within bounds.
// We use signal-handlers on x64, but on x86 there isn't enough address
// space for a guard region. Also, on x64 the atomic loads and stores
// can't (yet) use the signal handlers.
#if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB)
if (mg_.usesSignal.forOOB && !access.isAtomicAccess())
return false;
#endif
return access.needsBoundsCheck();
}
-#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
- void verifyHeapAccessDisassembly(uint32_t before, uint32_t after, bool isLoad,
- Scalar::Type accessType, int nelems, Operand srcAddr,
- AnyReg dest)
+ bool throwOnOutOfBounds(const MWasmMemoryAccess& access) {
+ return access.isAtomicAccess() || !isCompilingAsmJS();
+ }
+
+ // For asm.js code only: If we have a non-zero offset, it's possible that
+ // |ptr| itself is out of bounds, while adding the offset computes an
+ // in-bounds address. To catch this case, we need a second branch, which we
+ // emit out of line since it's unlikely to be needed in normal programs.
+ // For this, we'll generate an OffsetBoundsCheck OOL stub.
+
+ bool needsOffsetBoundsCheck(const MWasmMemoryAccess& access) const {
+ return isCompilingAsmJS() && access.offset() != 0;
+ }
+
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+
+# if defined(JS_CODEGEN_X64)
+ // TODO / CLEANUP - copied from CodeGenerator-x64.cpp, should share.
+
+ MemoryAccess
+ WasmMemoryAccess(uint32_t before)
+ {
+ if (isCompilingAsmJS())
+ return MemoryAccess(before, MemoryAccess::CarryOn, MemoryAccess::WrapOffset);
+ return MemoryAccess(before, MemoryAccess::Throw, MemoryAccess::DontWrapOffset);
+ }
+# endif
+
+ class OffsetBoundsCheck : public OutOfLineCode
{
-#ifdef DEBUG
- // TODO / MISSING: this needs to be adapted from what's in the
- // platform's CodeGenerator; that code takes an LAllocation as
- // the last arg now.
-#endif
- }
-#endif
-
- void loadHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg dest) {
+ Label* maybeOutOfBounds;
+ Register ptrReg;
+ int32_t offset;
+
+ public:
+ OffsetBoundsCheck(Label* maybeOutOfBounds, Register ptrReg, int32_t offset)
+ : maybeOutOfBounds(maybeOutOfBounds),
+ ptrReg(ptrReg),
+ offset(offset)
+ {}
+
+ void generate(MacroAssembler& masm) {
+ // asm.js code only:
+ //
+ // The access is heap[ptr + offset]. The inline code checks that
+ // ptr < heap.length - offset. We get here when that fails. We need to check
+ // for the case where ptr + offset >= 0, in which case the access is still
+ // in bounds.
+
+ MOZ_ASSERT(offset != 0,
+ "An access without a constant offset doesn't need a separate "
+ "OffsetBoundsCheck");
+ masm.cmp32(ptrReg, Imm32(-uint32_t(offset)));
+ if (maybeOutOfBounds)
+ masm.j(Assembler::Below, maybeOutOfBounds);
+ else
+ masm.j(Assembler::Below, wasm::JumpTarget::OutOfBounds);
+
+# ifdef JS_CODEGEN_X64
+ // In order to get the offset to wrap properly, we must sign-extend the
+ // pointer to 32-bits. We'll zero out the sign extension immediately
+ // after the access to restore asm.js invariants.
+ masm.movslq(ptrReg, ptrReg);
+# endif
+
+ masm.jmp(rejoin());
+ }
+ };
+
+ // CodeGeneratorX86Shared::emitAsmJSBoundsCheckBranch()
+
+ MOZ_MUST_USE
+ bool emitBoundsCheckBranch(const MWasmMemoryAccess& access, RegI32 ptr, Label* maybeFail) {
+ Label* pass = nullptr;
+
+ if (needsOffsetBoundsCheck(access)) {
+ auto* oolCheck = new(alloc_) OffsetBoundsCheck(maybeFail, ptr.reg, access.offset());
+ maybeFail = oolCheck->entry();
+ pass = oolCheck->rejoin();
+ if (!addOutOfLineCode(oolCheck))
+ return false;
+ }
+
+ // The bounds check is a comparison with an immediate value. The asm.js
+ // module linking process will add the length of the heap to the immediate
+ // field, so -access->endOffset() will turn into
+ // (heapLength - access->endOffset()), allowing us to test whether the end
+ // of the access is beyond the end of the heap.
+ MOZ_ASSERT(access.endOffset() >= 1,
+ "need to subtract 1 to use JAE, see also AssemblerX86Shared::UpdateBoundsCheck");
+
+ uint32_t cmpOffset = masm.cmp32WithPatch(ptr.reg, Imm32(1 - access.endOffset())).offset();
+ if (maybeFail)
+ masm.j(Assembler::AboveOrEqual, maybeFail);
+ else
+ masm.j(Assembler::AboveOrEqual, wasm::JumpTarget::OutOfBounds);
+
+ if (pass)
+ masm.bind(pass);
+
+ masm.append(wasm::BoundsCheck(cmpOffset));
+ return true;
+ }
+
+ class OutOfLineLoadTypedArrayOOB : public OutOfLineCode
+ {
+ Scalar::Type viewType;
+ AnyRegister dest;
+ public:
+ OutOfLineLoadTypedArrayOOB(Scalar::Type viewType, AnyRegister dest)
+ : viewType(viewType),
+ dest(dest)
+ {}
+
+ void generate(MacroAssembler& masm) {
+ switch (viewType) {
+ case Scalar::Float32x4:
+ case Scalar::Int32x4:
+ case Scalar::Int8x16:
+ case Scalar::Int16x8:
+ case Scalar::MaxTypedArrayViewType:
+ MOZ_CRASH("unexpected array type");
+ case Scalar::Float32:
+ masm.loadConstantFloat32(float(GenericNaN()), dest.fpu());
+ break;
+ case Scalar::Float64:
+ masm.loadConstantDouble(GenericNaN(), dest.fpu());
+ break;
+ case Scalar::Int8:
+ case Scalar::Uint8:
+ case Scalar::Int16:
+ case Scalar::Uint16:
+ case Scalar::Int32:
+ case Scalar::Uint32:
+ case Scalar::Uint8Clamped:
+ masm.movePtr(ImmWord(0), dest.gpr());
+ break;
+ case Scalar::Int64:
+ MOZ_CRASH("unexpected array type");
+ }
+ masm.jump(rejoin());
+ }
+ };
+
+ MOZ_MUST_USE
+ bool maybeEmitLoadBoundsCheck(const MWasmMemoryAccess& access, RegI32 ptr, AnyRegister dest,
+ OutOfLineCode** ool)
+ {
+ *ool = nullptr;
+ if (!needsBoundsCheckBranch(access))
+ return true;
+
+ if (throwOnOutOfBounds(access))
+ return emitBoundsCheckBranch(access, ptr, nullptr);
+
+ // TODO / MEMORY: We'll allocate *a lot* of these OOL objects,
+ // thus risking OOM on a platform that is already
+ // memory-constrained. We could opt to allocate this path
+ // in-line instead.
+ *ool = new (alloc_) OutOfLineLoadTypedArrayOOB(access.accessType(), dest);
+ if (!addOutOfLineCode(*ool))
+ return false;
+
+ return emitBoundsCheckBranch(access, ptr, (*ool)->entry());
+ }
+
+ MOZ_MUST_USE
+ bool maybeEmitStoreBoundsCheck(const MWasmMemoryAccess& access, RegI32 ptr, Label** rejoin) {
+ *rejoin = nullptr;
+ if (!needsBoundsCheckBranch(access))
+ return true;
+
+ if (throwOnOutOfBounds(access))
+ return emitBoundsCheckBranch(access, ptr, nullptr);
+
+ *rejoin = newLabel();
+ if (!*rejoin)
+ return false;
+
+ return emitBoundsCheckBranch(access, ptr, *rejoin);
+ }
+
+ void cleanupAfterBoundsCheck(const MWasmMemoryAccess& access, RegI32 ptr) {
+# ifdef JS_CODEGEN_X64
+ if (needsOffsetBoundsCheck(access)) {
+ // Zero out the high 32 bits, in case the OffsetBoundsCheck code had to
+ // sign-extend (movslq) the pointer value to get wraparound to work.
+ masm.movl(ptr.reg, ptr.reg);
+ }
+# endif
+ }
+
+ MOZ_MUST_USE
+ bool loadHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg dest) {
if (access.offset() > INT32_MAX) {
masm.jump(wasm::JumpTarget::OutOfBounds);
- return;
+ return true;
}
-#if defined(JS_CODEGEN_X64)
- // CodeGeneratorX64::visitAsmJSLoadHeap()/visitWasmLoad()/visitWasmLoadI64()
-
- if (needsBoundsCheckBranch(access))
- MOZ_CRASH("BaseCompiler platform hook: bounds checking");
-
+ OutOfLineCode* ool = nullptr;
+ if (!maybeEmitLoadBoundsCheck(access, ptr, dest.any(), &ool))
+ return false;
+
+# if defined(JS_CODEGEN_X64)
Operand srcAddr(HeapReg, ptr.reg, TimesOne, access.offset());
uint32_t before = masm.size();
if (dest.tag == AnyReg::I64) {
Register out = dest.i64().reg.reg;
switch (access.accessType()) {
case Scalar::Int8: masm.movsbq(srcAddr, out); break;
case Scalar::Uint8: masm.movzbq(srcAddr, out); break;
@@ -2796,32 +3012,74 @@ class BaseCompiler
case Scalar::Int32:
case Scalar::Uint32: masm.movl(srcAddr, dest.i32().reg); break;
case Scalar::Float32: masm.loadFloat32(srcAddr, dest.f32().reg); break;
case Scalar::Float64: masm.loadDouble(srcAddr, dest.f64().reg); break;
default:
MOZ_CRASH("Compiler bug: Unexpected array type");
}
}
- uint32_t after = masm.size();
masm.append(WasmMemoryAccess(before));
- verifyHeapAccessDisassembly(before, after, IsLoad(true), access.accessType(), 0, srcAddr, dest);
-#else
- MOZ_CRASH("BaseCompiler platform hook: loadHeap");
-#endif
- }
-
- void storeHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg src) {
-#if defined(JS_CODEGEN_X64)
- // CodeGeneratorX64::visitAsmJSStoreHeap()
-
- if (needsBoundsCheckBranch(access))
- MOZ_CRASH("BaseCompiler platform hook: bounds checking");
-
+ // TODO: call verifyHeapAccessDisassembly somehow
+# elif defined(JS_CODEGEN_X86)
+ Operand srcAddr(ptr.reg, access.offset());
+
+ if (dest.tag == AnyReg::I64)
+ MOZ_CRASH("Not implemented: I64 support");
+
+ bool mustMove = access.byteSize() == 1 && !singleByteRegs_.has(dest.i32().reg);
+ switch (access.accessType()) {
+ case Scalar::Int8:
+ case Scalar::Uint8: {
+ Register rd = mustMove ? ScratchRegX86 : dest.i32().reg;
+ if (access.accessType() == Scalar::Int8)
+ masm.movsblWithPatch(srcAddr, rd);
+ else
+ masm.movzblWithPatch(srcAddr, rd);
+ break;
+ }
+ case Scalar::Int16: masm.movswlWithPatch(srcAddr, dest.i32().reg); break;
+ case Scalar::Uint16: masm.movzwlWithPatch(srcAddr, dest.i32().reg); break;
+ case Scalar::Int32:
+ case Scalar::Uint32: masm.movlWithPatch(srcAddr, dest.i32().reg); break;
+ case Scalar::Float32: masm.vmovssWithPatch(srcAddr, dest.f32().reg); break;
+ case Scalar::Float64: masm.vmovsdWithPatch(srcAddr, dest.f64().reg); break;
+ default:
+ MOZ_CRASH("Compiler bug: Unexpected array type");
+ }
+ uint32_t after = masm.size();
+ if (mustMove)
+ masm.mov(ScratchRegX86, dest.i32().reg);
+
+ masm.append(wasm::MemoryAccess(after));
+ // TODO: call verifyHeapAccessDisassembly somehow
+# else
+ MOZ_CRASH("Compiler bug: Unexpected platform.");
+# endif
+
+ if (ool) {
+ cleanupAfterBoundsCheck(access, ptr);
+ masm.bind(ool->rejoin());
+ }
+ return true;
+ }
+
+ MOZ_MUST_USE
+ bool storeHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg src) {
+ if (access.offset() > INT32_MAX) {
+ masm.jump(wasm::JumpTarget::OutOfBounds);
+ return true;
+ }
+
+ Label* rejoin = nullptr;
+ if (!maybeEmitStoreBoundsCheck(access, ptr, &rejoin))
+ return false;
+
+# if defined(JS_CODEGEN_X64)
Operand dstAddr(HeapReg, ptr.reg, TimesOne, access.offset());
Register intReg;
if (src.tag == AnyReg::I32)
intReg = src.i32().reg;
else if (src.tag == AnyReg::I64)
intReg = src.i64().reg.reg;
@@ -2834,24 +3092,75 @@ class BaseCompiler
case Scalar::Int32:
case Scalar::Uint32: masm.movl(intReg, dstAddr); break;
case Scalar::Int64: masm.movq(intReg, dstAddr); break;
case Scalar::Float32: masm.storeFloat32(src.f32().reg, dstAddr); break;
case Scalar::Float64: masm.storeDouble(src.f64().reg, dstAddr); break;
default:
MOZ_CRASH("Compiler bug: Unexpected array type");
}
- uint32_t after = masm.size();
masm.append(WasmMemoryAccess(before));
- verifyHeapAccessDisassembly(before, after, IsLoad(false), access.accessType(), 0, dstAddr, src);
+ // TODO: call verifyHeapAccessDisassembly somehow
+# elif defined(JS_CODEGEN_X86)
+ Operand dstAddr(ptr.reg, access.offset());
+
+ if (src.tag == AnyReg::I64)
+ MOZ_CRASH("Not implemented: I64 support");
+
+ bool didMove = false;
+ if (access.byteSize() == 1 && !singleByteRegs_.has(src.i32().reg)) {
+ didMove = true;
+ masm.mov(src.i32().reg, ScratchRegX86);
+ }
+ switch (access.accessType()) {
+ case Scalar::Int8:
+ case Scalar::Uint8: {
+ Register rs = src.i32().reg;
+ Register rt = didMove ? ScratchRegX86 : rs;
+ masm.movbWithPatch(rt, dstAddr);
+ break;
+ }
+ case Scalar::Int16:
+ case Scalar::Uint16: masm.movwWithPatch(src.i32().reg, dstAddr); break;
+ case Scalar::Int32:
+ case Scalar::Uint32: masm.movlWithPatch(src.i32().reg, dstAddr); break;
+ case Scalar::Float32: masm.vmovssWithPatch(src.f32().reg, dstAddr); break;
+ case Scalar::Float64: masm.vmovsdWithPatch(src.f64().reg, dstAddr); break;
+ default:
+ MOZ_CRASH("Compiler bug: Unexpected array type");
+ }
+ uint32_t after = masm.size();
+
+ masm.append(wasm::MemoryAccess(after));
+ // TODO: call verifyHeapAccessDisassembly somehow
+# else
+ MOZ_CRASH("Compiler bug: unexpected platform");
+# endif
+
+ if (rejoin) {
+ cleanupAfterBoundsCheck(access, ptr);
+ masm.bind(rejoin);
+ }
+ return true;
+ }
+
#else
+
+ MOZ_MUST_USE
+ bool loadHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg dest) {
+ MOZ_CRASH("BaseCompiler platform hook: loadHeap");
+ }
+
+ MOZ_MUST_USE
+ bool storeHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg src) {
MOZ_CRASH("BaseCompiler platform hook: storeHeap");
+ }
+
#endif
- }
////////////////////////////////////////////////////////////
// Generally speaking, ABOVE this point there should be no value
// stack manipulation (calls to popI32 etc).
// Generally speaking, BELOW this point there should be no
// platform dependencies. We make an exception for x86 register
@@ -5239,40 +5548,44 @@ BaseCompiler::emitLoad(ValType type, Sca
// TODO / OPTIMIZE: Disable bounds checking on constant accesses
// below the minimum heap length.
MWasmMemoryAccess access(viewType, addr.align, addr.offset);
switch (type) {
case ValType::I32: {
RegI32 rp = popI32();
- loadHeap(access, rp, AnyReg(rp));
+ if (!loadHeap(access, rp, AnyReg(rp)))
+ return false;
pushI32(rp);
break;
}
case ValType::I64: {
RegI32 rp = popI32();
RegI64 rv = needI64();
- loadHeap(access, rp, AnyReg(rv));
+ if (!loadHeap(access, rp, AnyReg(rv)))
+ return false;
pushI64(rv);
freeI32(rp);
break;
}
case ValType::F32: {
RegI32 rp = popI32();
RegF32 rv = needF32();
- loadHeap(access, rp, AnyReg(rv));
+ if (!loadHeap(access, rp, AnyReg(rv)))
+ return false;
pushF32(rv);
freeI32(rp);
break;
}
case ValType::F64: {
RegI32 rp = popI32();
RegF64 rv = needF64();
- loadHeap(access, rp, AnyReg(rv));
+ if (!loadHeap(access, rp, AnyReg(rv)))
+ return false;
pushF64(rv);
freeI32(rp);
break;
}
default:
MOZ_CRASH("loadHeap type");
break;
}
@@ -5294,41 +5607,45 @@ BaseCompiler::emitStore(ValType resultTy
// below the minimum heap length.
MWasmMemoryAccess access(viewType, addr.align, addr.offset);
switch (resultType) {
case ValType::I32: {
RegI32 rp, rv;
pop2xI32(&rp, &rv);
- storeHeap(access, rp, AnyReg(rv));
+ if (!storeHeap(access, rp, AnyReg(rv)))
+ return false;
freeI32(rp);
pushI32(rv);
break;
}
case ValType::I64: {
RegI64 rv = popI64();
RegI32 rp = popI32();
- storeHeap(access, rp, AnyReg(rv));
+ if (!storeHeap(access, rp, AnyReg(rv)))
+ return false;
freeI32(rp);
pushI64(rv);
break;
}
case ValType::F32: {
RegF32 rv = popF32();
RegI32 rp = popI32();
- storeHeap(access, rp, AnyReg(rv));
+ if (!storeHeap(access, rp, AnyReg(rv)))
+ return false;
freeI32(rp);
pushF32(rv);
break;
}
case ValType::F64: {
RegF64 rv = popF64();
RegI32 rp = popI32();
- storeHeap(access, rp, AnyReg(rv));
+ if (!storeHeap(access, rp, AnyReg(rv)))
+ return false;
freeI32(rp);
pushF64(rv);
break;
}
default:
MOZ_CRASH("storeHeap type");
break;
}
@@ -5579,27 +5896,29 @@ BaseCompiler::emitStoreWithCoercion(ValT
MWasmMemoryAccess access(viewType, addr.align, addr.offset);
if (resultType == ValType::F32 && viewType == Scalar::Float64) {
RegF32 rv = popF32();
RegF64 rw = needF64();
masm.convertFloat32ToDouble(rv.reg, rw.reg);
RegI32 rp = popI32();
- storeHeap(access, rp, AnyReg(rw));
+ if (!storeHeap(access, rp, AnyReg(rw)))
+ return false;
pushF32(rv);
freeI32(rp);
freeF64(rw);
}
else if (resultType == ValType::F64 && viewType == Scalar::Float32) {
RegF64 rv = popF64();
RegF32 rw = needF32();
masm.convertDoubleToFloat32(rv.reg, rw.reg);
RegI32 rp = popI32();
- storeHeap(access, rp, AnyReg(rw));
+ if (!storeHeap(access, rp, AnyReg(rw)))
+ return false;
pushF64(rv);
freeI32(rp);
freeF32(rw);
}
else
MOZ_CRASH("unexpected coerced store");
return true;
@@ -6253,16 +6572,19 @@ BaseCompiler::BaseCompiler(const ModuleG
specific_rcx(RegI64(Register64(rcx))),
specific_rdx(RegI64(Register64(rdx))),
#endif
#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
specific_eax(RegI32(eax)),
specific_ecx(RegI32(ecx)),
specific_edx(RegI32(edx)),
#endif
+#ifdef JS_CODEGEN_X86
+ singleByteRegs_(GeneralRegisterSet(Registers::SingleByteRegs)),
+#endif
joinRegI32(RegI32(ReturnReg)),
joinRegI64(RegI64(Register64(ReturnReg))),
joinRegF32(RegF32(ReturnFloat32Reg)),
joinRegF64(RegF64(ReturnDoubleReg))
{
// jit/RegisterAllocator.h: RegisterAllocator::RegisterAllocator()
#if defined(JS_CODEGEN_X64)
@@ -6394,17 +6716,17 @@ volatileReturnGPR()
LiveRegisterSet BaseCompiler::VolatileReturnGPR = volatileReturnGPR();
} // wasm
} // js
bool
js::wasm::BaselineCanCompile(const FunctionGenerator* fg)
{
-#if defined(JS_CODEGEN_X64)
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
if (!fg->usesSignalsForInterrupts())
return false;
if (fg->usesAtomics())
return false;
if (fg->usesSimd())
return false;
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2473,17 +2473,16 @@ IsMarkedInternal(T* thingp)
*thingp = DispatchTyped(IsMarkedFunctor<T>(), *thingp, &rv);
return rv;
}
bool
js::gc::IsAboutToBeFinalizedDuringSweep(TenuredCell& tenured)
{
MOZ_ASSERT(!IsInsideNursery(&tenured));
- MOZ_ASSERT(!tenured.runtimeFromAnyThread()->isHeapMinorCollecting());
MOZ_ASSERT(tenured.zoneFromAnyThread()->isGCSweeping());
if (tenured.arena()->allocatedDuringIncremental)
return false;
return !tenured.isMarked();
}
template <typename T>
static bool
@@ -2493,21 +2492,19 @@ IsAboutToBeFinalizedInternal(T** thingp)
T* thing = *thingp;
JSRuntime* rt = thing->runtimeFromAnyThread();
/* Permanent atoms are never finalized by non-owning runtimes. */
if (ThingIsPermanentAtomOrWellKnownSymbol(thing) && !TlsPerThreadData.get()->associatedWith(rt))
return false;
Nursery& nursery = rt->gc.nursery;
- MOZ_ASSERT_IF(!rt->isHeapMinorCollecting(), !IsInsideNursery(thing));
- if (rt->isHeapMinorCollecting()) {
- if (IsInsideNursery(thing))
- return !nursery.getForwardedPointer(reinterpret_cast<JSObject**>(thingp));
- return false;
+ if (IsInsideNursery(thing)) {
+ MOZ_ASSERT(rt->isHeapMinorCollecting());
+ return !nursery.getForwardedPointer(reinterpret_cast<JSObject**>(thingp));
}
Zone* zone = thing->asTenured().zoneFromAnyThread();
if (zone->isGCSweeping()) {
return IsAboutToBeFinalizedDuringSweep(thing->asTenured());
} else if (zone->isGCCompacting() && IsForwarded(thing)) {
*thingp = Forwarded(thing);
return false;
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -496,22 +496,25 @@ js::Nursery::collect(JSRuntime* rt, JS::
TIME_START(checkHashTables);
#ifdef JS_GC_ZEAL
if (rt->hasZealMode(ZealMode::CheckHashTablesOnMinorGC))
CheckHashTablesAfterMovingGC(rt);
#endif
TIME_END(checkHashTables);
// Resize the nursery.
+ static const double GrowThreshold = 0.05;
+ static const double ShrinkThreshold = 0.01;
TIME_START(resize);
double promotionRate = mover.tenuredSize / double(allocationEnd() - start());
- if (promotionRate > 0.05)
+ if (promotionRate > GrowThreshold)
growAllocableSpace();
- else if (promotionRate < 0.01)
+ else if (promotionRate < ShrinkThreshold && previousPromotionRate_ < ShrinkThreshold)
shrinkAllocableSpace();
+ previousPromotionRate_ = promotionRate;
TIME_END(resize);
// If we are promoting the nursery, or exhausted the store buffer with
// pointers to nursery things, which will force a collection well before
// the nursery is full, look for object groups that are getting promoted
// excessively and try to pretenure them.
TIME_START(pretenure);
if (pretenureGroups && (promotionRate > 0.8 || reason == JS::gcreason::FULL_STORE_BUFFER)) {
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -99,16 +99,17 @@ class Nursery
position_(0),
currentStart_(0),
currentEnd_(0),
heapStart_(0),
heapEnd_(0),
currentChunk_(0),
numActiveChunks_(0),
numNurseryChunks_(0),
+ previousPromotionRate_(0),
profileThreshold_(0),
enableProfiling_(false),
freeMallocedBuffersTask(nullptr)
{}
~Nursery();
MOZ_MUST_USE bool init(uint32_t maxNurseryBytes);
@@ -253,16 +254,19 @@ class Nursery
int currentChunk_;
/* The index after the last chunk that we will allocate from. */
int numActiveChunks_;
/* Number of chunks allocated for the nursery. */
int numNurseryChunks_;
+ /* Promotion rate for the previous minor collection. */
+ double previousPromotionRate_;
+
/* Report minor collections taking more than this many us, if enabled. */
int64_t profileThreshold_;
bool enableProfiling_;
/*
* The set of externally malloced buffers potentially kept live by objects
* stored in the nursery. Any external buffers that do not belong to a
* tenured thing at the end of a minor GC must be freed.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1285227.js
@@ -0,0 +1,5 @@
+if (helperThreadCount() === 0)
+ quit();
+evalInWorker(`
+ (new WeakMap).set(FakeDOMObject.prototype, this)
+`);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/bug1285186.js
@@ -0,0 +1,6 @@
+if (helperThreadCount() === 0)
+ quit();
+gczeal(10);
+newGlobal();
+offThreadCompileScript("let x = 1;");
+abortgc();
--- a/js/src/jit/none/LIR-none.h
+++ b/js/src/jit/none/LIR-none.h
@@ -82,24 +82,16 @@ class LModI : public LBinaryMath<1>
const LDefinition&)
{
MOZ_CRASH();
}
const LDefinition* callTemp() { MOZ_CRASH(); }
MMod* mir() const { MOZ_CRASH(); }
};
-class LAsmJSLoadFuncPtr : public LInstructionHelper<1, 1, 1>
-{
- public:
- LAsmJSLoadFuncPtr(const LAllocation&, const LDefinition&) { MOZ_CRASH(); }
- const MAsmJSLoadFuncPtr* mir() const { MOZ_CRASH(); }
- const LAllocation* index() { MOZ_CRASH(); }
- const LDefinition* temp() { MOZ_CRASH(); }
-};
class LAsmJSUInt32ToDouble : public LInstructionHelper<1, 1, 0>
{
public:
LAsmJSUInt32ToDouble(const LAllocation&) { MOZ_CRASH(); }
};
class LModPowTwoI : public LInstructionHelper<1, 1, 0>
{
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6325,17 +6325,17 @@ GCRuntime::collect(bool nonincrementalBy
gcstats::AutoPhase ap(rt->gc.stats, gcstats::PHASE_TRACE_HEAP);
CheckHeapAfterMovingGC(rt);
}
#endif
}
js::AutoEnqueuePendingParseTasksAfterGC::~AutoEnqueuePendingParseTasksAfterGC()
{
- if (!gc_.isIncrementalGCInProgress())
+ if (!OffThreadParsingMustWaitForGC(gc_.rt))
EnqueuePendingParseTasksAfterGC(gc_.rt);
}
SliceBudget
GCRuntime::defaultBudget(JS::gcreason::Reason reason, int64_t millis)
{
if (millis == 0) {
if (reason == JS::gcreason::ALLOC_TRIGGER)
@@ -6393,16 +6393,17 @@ GCRuntime::finishGC(JS::gcreason::Reason
void
GCRuntime::abortGC()
{
checkCanCallAPI();
MOZ_ASSERT(!rt->mainThread.suppressGC);
AutoStopVerifyingBarriers av(rt, false);
+ AutoEnqueuePendingParseTasksAfterGC aept(*this);
gcstats::AutoGCSlice agc(stats, scanZonesBeforeGC(), invocationKind,
SliceBudget::unlimited(), JS::gcreason::ABORT_GC);
evictNursery(JS::gcreason::ABORT_GC);
AutoTraceSession session(rt, JS::HeapState::MajorCollecting);
number++;
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2637,16 +2637,23 @@ DisassWithSrc(JSContext* cx, unsigned ar
fclose(file);
}
args.rval().setUndefined();
return ok;
}
#endif /* DEBUG */
+/* Pretend we can always preserve wrappers for dummy DOM objects. */
+static bool
+DummyPreserveWrapperCallback(JSContext* cx, JSObject* obj)
+{
+ return true;
+}
+
static bool
Intern(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JSString* str = JS::ToString(cx, args.get(0));
if (!str)
return false;
@@ -2948,16 +2955,17 @@ WorkerMain(void* arg)
}
JSContext* cx = JS_GetContext(rt);
sr->isWorker = true;
JS_SetRuntimePrivate(rt, sr.get());
JS_SetFutexCanWait(cx);
JS::SetWarningReporter(cx, WarningReporter);
+ js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
SetWorkerContextOptions(cx);
if (!JS::InitSelfHostedCode(cx)) {
JS_DestroyRuntime(rt);
js_delete(input);
return;
}
@@ -7171,23 +7179,16 @@ SetOutputFile(const char* const envVar,
outFile = js_new<RCFile>(newfp);
else
outFile = defaultOut;
outFile->acquire();
*outFileP = outFile;
}
-/* Pretend we can always preserve wrappers for dummy DOM objects. */
-static bool
-DummyPreserveWrapperCallback(JSContext* cx, JSObject* obj)
-{
- return true;
-}
-
static void
PreInit()
{
#ifdef XP_WIN
const char* crash_option = getenv("XRE_NO_WINDOWS_CRASH_DIALOG");
if (crash_option && crash_option[0] == '1') {
// Disable the segfault dialog. We want to fail the tests immediately
// instead of hanging automation.
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -285,8 +285,11 @@ support-files = bug1226904.html
[test_bug1278021.html]
[test_transformed_scrolling_repaints.html]
skip-if = buildapp == 'b2g'
[test_transformed_scrolling_repaints_2.html]
skip-if = buildapp == 'b2g'
[test_transformed_scrolling_repaints_3.html]
skip-if = buildapp == 'b2g'
[test_frame_reconstruction_scroll_restore.html]
+[test_resize_flush.html]
+support-files =
+ resize_flush_iframe.html
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/resize_flush_iframe.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+ <style>
+ body {
+ background-color: red;
+ }
+
+ @media (min-width: 250px) {
+ body {
+ background-color: green;
+ }
+ }
+ </style>
+</head>
+<body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/test_resize_flush.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1279202
+-->
+<head>
+ <title>Test for Bug 1279202</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1279202">Mozilla Bug 1279202</a>
+<iframe src="resize_flush_iframe.html" id="iframe" height="200" width="200" style="border:none"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1279202 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+
+ var iframe = document.getElementById("iframe");
+ var doc = iframe.contentDocument.documentElement;
+ var win = iframe.contentWindow;
+ var body = iframe.contentDocument.body;
+
+ // Flush any pending layout changes before we start.
+ var width = doc.clientWidth;
+
+ // Resize the iframe
+ iframe.width = '300px';
+
+ // Flush pending style changes, but not layout ones. We do this twice because the first flush
+ // does a partial flush of the resize (setting the size on the pres context) which sets the
+ // need style flush flag again. The second call makes sure mNeedStyleFlush is false.
+ var color = win.getComputedStyle(body).getPropertyValue("background-color");
+ color = win.getComputedStyle(body).getPropertyValue("background-color");
+ is(color, "rgb(0, 128, 0)", "Style flush not completed when resizing an iframe!");
+
+ // Query the size of the inner document and make sure it has had a layout flush.
+ width = doc.clientWidth;
+
+ is(width, 300, "Layout flush not completed when resizing an iframe!");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -6686,22 +6686,25 @@ DrawTextRun(gfxTextRun* aTextRun,
// Default drawMode is DrawMode::GLYPH_FILL
aParams.context->SetColor(Color::FromABGR(aParams.textColor));
} else {
params.drawMode = DrawMode::GLYPH_STROKE;
}
if (NS_GET_A(aParams.textStrokeColor) != 0 &&
aParams.textStrokeWidth != 0.0f) {
+ StrokeOptions strokeOpts;
params.drawMode |= DrawMode::GLYPH_STROKE;
- params.textStrokeWidth = aParams.textStrokeWidth;
params.textStrokeColor = aParams.textStrokeColor;
- }
-
- aTextRun->Draw(aRange, aTextBaselinePt, params);
+ strokeOpts.mLineWidth = aParams.textStrokeWidth;
+ params.strokeOpts = &strokeOpts;
+ aTextRun->Draw(aRange, aTextBaselinePt, params);
+ } else {
+ aTextRun->Draw(aRange, aTextBaselinePt, params);
+ }
}
}
void
nsTextFrame::DrawTextRun(Range aRange, const gfxPoint& aTextBaselinePt,
const DrawTextRunParams& aParams)
{
MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth");
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -13,16 +13,17 @@
#include "nsDOMTokenList.h"
#include "nsIDOMNode.h"
#include "nsIDocument.h"
#include "nsINode.h"
#include "nsIPrincipal.h"
#include "nsNameSpaceManager.h"
#include "nsString.h"
#include "nsStyleStruct.h"
+#include "nsTArray.h"
#include "nsStyleUtil.h"
#include "StyleStructContext.h"
#include "mozilla/EventStates.h"
#include "mozilla/dom/Element.h"
uint32_t
Gecko_ChildrenCount(RawGeckoNode* aNode)
@@ -555,16 +556,31 @@ Gecko_CreateGradient(uint8_t aShape,
for (uint32_t i = 0; i < aStopCount; i++) {
result->mStops.AppendElement(dummyStop);
}
return result;
}
+void
+Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity, size_t aElemSize) {
+ auto base = reinterpret_cast<nsTArray_base<nsTArrayInfallibleAllocator, nsTArray_CopyWithMemutils> *>(aArray);
+ base->EnsureCapacity<nsTArrayInfallibleAllocator>(aCapacity, aElemSize);
+}
+
+void Gecko_EnsureImageLayersLength(nsStyleImageLayers* aLayers, size_t aLen) {
+ aLayers->mLayers.EnsureLengthAtLeast(aLen);
+}
+
+void Gecko_InitializeImageLayer(nsStyleImageLayers::Layer* aLayer,
+ nsStyleImageLayers::LayerType aLayerType) {
+ aLayer->Initialize(aLayerType);
+}
+
#define STYLE_STRUCT(name, checkdata_cb) \
\
void \
Gecko_Construct_nsStyle##name(nsStyle##name* ptr) \
{ \
new (ptr) nsStyle##name(StyleStructContext::ServoContext()); \
} \
\
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -4,16 +4,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_ServoBindings_h
#define mozilla_ServoBindings_h
#include "stdint.h"
#include "nsColor.h"
+#include "nsStyleStruct.h"
#include "mozilla/css/SheetParsingMode.h"
#include "nsProxyRelease.h"
/*
* API for Servo to access Gecko data structures. This file must compile as valid
* C code in order for the binding generator to parse it.
*
* Functions beginning with Gecko_ are implemented in Gecko and invoked from Servo.
@@ -176,16 +177,27 @@ void Gecko_SetMozBinding(nsStyleDisplay*
ThreadSafePrincipalHolder* principal);
void Gecko_CopyMozBindingFrom(nsStyleDisplay* des, const nsStyleDisplay* src);
// Dirtiness tracking.
uint32_t Gecko_GetNodeFlags(RawGeckoNode* node);
void Gecko_SetNodeFlags(RawGeckoNode* node, uint32_t flags);
void Gecko_UnsetNodeFlags(RawGeckoNode* node, uint32_t flags);
+// `array` must be an nsTArray
+// If changing this signature, please update the
+// friend function declaration in nsTArray.h
+void Gecko_EnsureTArrayCapacity(void* array, size_t capacity, size_t elem_size);
+
+
+void Gecko_EnsureImageLayersLength(nsStyleImageLayers* layers, size_t len);
+
+void Gecko_InitializeImageLayer(nsStyleImageLayers::Layer* layer,
+ nsStyleImageLayers::LayerType layer_type);
+
// Styleset and Stylesheet management.
//
// TODO: Make these return already_AddRefed and UniquePtr when the binding
// generator is smart enough to handle them.
RawServoStyleSheet* Servo_StylesheetFromUTF8Bytes(
const uint8_t* bytes, uint32_t length,
mozilla::css::SheetParsingMode parsing_mode,
ThreadSafeURIHolder* base,
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -191,16 +191,97 @@ public abstract class GeckoApp
private View mFullScreenPluginView;
private final HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
protected boolean mLastSessionCrashed;
protected boolean mShouldRestore;
private boolean mSessionRestoreParsingFinished = false;
+ private static final class LastSessionParser extends SessionParser {
+ private JSONArray tabs;
+ private JSONObject windowObject;
+ private boolean isExternalURL;
+
+ private boolean selectNextTab;
+ private boolean tabsWereSkipped;
+ private boolean tabsWereProcessed;
+
+ public LastSessionParser(JSONArray tabs, JSONObject windowObject, boolean isExternalURL) {
+ this.tabs = tabs;
+ this.windowObject = windowObject;
+ this.isExternalURL = isExternalURL;
+ }
+
+ public boolean allTabsSkipped() {
+ return tabsWereSkipped && !tabsWereProcessed;
+ }
+
+ @Override
+ public void onTabRead(final SessionTab sessionTab) {
+ if (sessionTab.isAboutHomeWithoutHistory()) {
+ // This is a tab pointing to about:home with no history. We won't restore
+ // this tab. If we end up restoring no tabs then the browser will decide
+ // whether it needs to open about:home or a different 'homepage'. If we'd
+ // always restore about:home only tabs then we'd never open the homepage.
+ // See bug 1261008.
+
+ if (sessionTab.isSelected()) {
+ // Unfortunately this tab is the selected tab. Let's just try to select
+ // the first tab. If we haven't restored any tabs so far then remember
+ // to select the next tab that gets restored.
+
+ if (!Tabs.getInstance().selectLastTab()) {
+ selectNextTab = true;
+ }
+ }
+
+ // Do not restore this tab.
+ tabsWereSkipped = true;
+ return;
+ }
+
+ tabsWereProcessed = true;
+
+ JSONObject tabObject = sessionTab.getTabObject();
+
+ int flags = Tabs.LOADURL_NEW_TAB;
+ flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
+ flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
+ flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
+
+ final Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags);
+
+ if (selectNextTab) {
+ // We did not restore the selected tab previously. Now let's select this tab.
+ Tabs.getInstance().selectTab(tab.getId());
+ selectNextTab = false;
+ }
+
+ ThreadUtils.postToUiThread(new Runnable() {
+ @Override
+ public void run() {
+ tab.updateTitle(sessionTab.getTitle());
+ }
+ });
+
+ try {
+ tabObject.put("tabId", tab.getId());
+ } catch (JSONException e) {
+ Log.e(LOGTAG, "JSON error", e);
+ }
+ tabs.put(tabObject);
+ }
+
+ @Override
+ public void onClosedTabsRead(final JSONArray closedTabData) throws JSONException {
+ windowObject.put("closedTabs", closedTabData);
+ }
+ };
+
protected boolean mInitialized;
protected boolean mWindowFocusInitialized;
private Telemetry.Timer mJavaUiStartupTimer;
private Telemetry.Timer mGeckoReadyStartupTimer;
private String mPrivateBrowsingSession;
private volatile HealthRecorder mHealthRecorder;
@@ -1283,16 +1364,27 @@ public abstract class GeckoApp
// of the tab stubs into the JSON data (which holds the session
// history). This JSON data is then sent to Gecko so session
// history can be restored for each tab.
final SafeIntent intent = new SafeIntent(getIntent());
restoreMessage = restoreSessionTabs(invokedWithExternalURL(getIntentURI(intent)));
} catch (SessionRestoreException e) {
// If restore failed, do a normal startup
Log.e(LOGTAG, "An error occurred during restore", e);
+ // If mShouldRestore was already set to false in restoreSessionTabs(),
+ // this means that we intentionally skipped all tabs read from the
+ // session file, so we don't have to report this exception in telemetry
+ // and can ignore the following bit.
+ if (mShouldRestore && getProfile().sessionFileExistsAndNotEmptyWindow()) {
+ // If we got a SessionRestoreException even though the file exists and its
+ // length doesn't match the known length of an intentionally empty file,
+ // it's very likely we've encountered a damaged/corrupt session store file.
+ Log.d(LOGTAG, "Suspecting a damaged session store file.");
+ Telemetry.addToHistogram("FENNEC_SESSIONSTORE_DAMAGED_SESSION_FILE", 1);
+ }
mShouldRestore = false;
}
}
synchronized (GeckoApp.this) {
mSessionRestoreParsingFinished = true;
GeckoApp.this.notifyAll();
}
@@ -1675,88 +1767,35 @@ public abstract class GeckoApp
}
// If we are doing an OOM restore, parse the session data and
// stub the restored tabs immediately. This allows the UI to be
// updated before Gecko has restored.
if (mShouldRestore) {
final JSONArray tabs = new JSONArray();
final JSONObject windowObject = new JSONObject();
- SessionParser parser = new SessionParser() {
- private boolean selectNextTab;
-
- @Override
- public void onTabRead(final SessionTab sessionTab) {
- if (sessionTab.isAboutHomeWithoutHistory()) {
- // This is a tab pointing to about:home with no history. We won't restore
- // this tab. If we end up restoring no tabs then the browser will decide
- // whether it needs to open about:home or a different 'homepage'. If we'd
- // always restore about:home only tabs then we'd never open the homepage.
- // See bug 1261008.
-
- if (sessionTab.isSelected()) {
- // Unfortunately this tab is the selected tab. Let's just try to select
- // the first tab. If we haven't restored any tabs so far then remember
- // to select the next tab that gets restored.
-
- if (!Tabs.getInstance().selectLastTab()) {
- selectNextTab = true;
- }
- }
-
- // Do not restore this tab.
- return;
- }
-
- JSONObject tabObject = sessionTab.getTabObject();
-
- int flags = Tabs.LOADURL_NEW_TAB;
- flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
- flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
- flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
-
- final Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags);
-
- if (selectNextTab) {
- // We did not restore the selected tab previously. Now let's select this tab.
- Tabs.getInstance().selectTab(tab.getId());
- selectNextTab = false;
- }
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- tab.updateTitle(sessionTab.getTitle());
- }
- });
-
- try {
- tabObject.put("tabId", tab.getId());
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSON error", e);
- }
- tabs.put(tabObject);
- }
-
- @Override
- public void onClosedTabsRead(final JSONArray closedTabData) throws JSONException {
- windowObject.put("closedTabs", closedTabData);
- }
- };
+
+ LastSessionParser parser = new LastSessionParser(tabs, windowObject, isExternalURL);
if (mPrivateBrowsingSession == null) {
parser.parse(sessionString);
} else {
parser.parse(sessionString, mPrivateBrowsingSession);
}
if (tabs.length() > 0) {
windowObject.put("tabs", tabs);
sessionString = new JSONObject().put("windows", new JSONArray().put(windowObject)).toString();
} else {
+ if (parser.allTabsSkipped()) {
+ // If we intentionally skipped all tabs we've read from the session file, we
+ // set mShouldRestore back to false at this point already, so the calling code
+ // can infer that the exception wasn't due to a damaged session store file.
+ mShouldRestore = false;
+ }
throw new SessionRestoreException("No tabs could be read from session file");
}
}
JSONObject restoreData = new JSONObject();
restoreData.put("sessionString", sessionString);
return restoreData.toString();
} catch (JSONException e) {
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoProfile.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoProfile.java
@@ -71,16 +71,17 @@ public final class GeckoProfile {
// Profile is using a custom directory outside of the Mozilla directory.
public static final String CUSTOM_PROFILE = "";
public static final String GUEST_PROFILE_DIR = "guest";
// Session store
private static final String SESSION_FILE = "sessionstore.js";
private static final String SESSION_FILE_BACKUP = "sessionstore.bak";
private static final long MAX_BACKUP_FILE_AGE = 1000 * 3600 * 24; // 24 hours
+ private static final int SESSION_STORE_EMPTY_JSON_LENGTH = 14; // length of {"windows":[]}
private boolean mOldSessionDataProcessed = false;
private static final ConcurrentHashMap<String, GeckoProfile> sProfileCache =
new ConcurrentHashMap<String, GeckoProfile>(
/* capacity */ 4, /* load factor */ 0.75f, /* concurrency */ 2);
private static String sDefaultProfileName;
@@ -649,16 +650,29 @@ public final class GeckoProfile {
}
} catch (IOException ioe) {
Log.e(LOGTAG, "Unable to read session file", ioe);
}
return null;
}
/**
+ * Checks whether the session store file exists and that its length
+ * doesn't match the known length of a session store file containing
+ * only an empty window.
+ */
+ public boolean sessionFileExistsAndNotEmptyWindow() {
+ File sessionFile = getFile(SESSION_FILE);
+
+ return sessionFile != null &&
+ sessionFile.exists() &&
+ sessionFile.length() != SESSION_STORE_EMPTY_JSON_LENGTH;
+ }
+
+ /**
* Ensures the parent director(y|ies) of the given filename exist by making them
* if they don't already exist..
*
* @param filename The path to the file whose parents should be made directories
* @return true if the parent directory exists, false otherwise
*/
@WorkerThread
protected boolean ensureParentDirs(final String filename) {
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoProfileDirectories.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoProfileDirectories.java
@@ -133,24 +133,24 @@ public class GeckoProfileDirectories {
*
* @return null if there is no "Default" entry in profiles.ini, or the profile
* name if there is.
* @throws NoMozillaDirectoryException
* if the Mozilla directory did not exist and could not be created.
*/
static String findDefaultProfileName(final Context context) throws NoMozillaDirectoryException {
final INIParser parser = GeckoProfileDirectories.getProfilesINI(getMozillaDirectory(context));
-
- for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
- final INISection section = e.nextElement();
- if (section.getIntProperty("Default") == 1) {
- return section.getStringProperty("Name");
+ if (parser.getSections() != null) {
+ for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements(); ) {
+ final INISection section = e.nextElement();
+ if (section.getIntProperty("Default") == 1) {
+ return section.getStringProperty("Name");
+ }
}
}
-
return null;
}
static Map<String, String> getDefaultProfile(final File mozillaDir) {
return getMatchingProfiles(mozillaDir, sectionIsDefault, true);
}
static Map<String, String> getProfilesNamed(final File mozillaDir, final String name) {
@@ -186,43 +186,45 @@ public class GeckoProfileDirectories {
* matches the predicate; if false, all matching results are
* included.
* @return a {@link Map} from name to path.
*/
public static Map<String, String> getMatchingProfiles(final File mozillaDir, INISectionPredicate predicate, boolean stopOnSuccess) {
final HashMap<String, String> result = new HashMap<String, String>();
final INIParser parser = GeckoProfileDirectories.getProfilesINI(mozillaDir);
- for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
- final INISection section = e.nextElement();
- if (predicate == null || predicate.matches(section)) {
- final String name = section.getStringProperty("Name");
- final String pathString = section.getStringProperty("Path");
- final boolean isRelative = section.getIntProperty("IsRelative") == 1;
- final File path = isRelative ? new File(mozillaDir, pathString) : new File(pathString);
- result.put(name, path.getAbsolutePath());
+ if (parser.getSections() != null) {
+ for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements(); ) {
+ final INISection section = e.nextElement();
+ if (predicate == null || predicate.matches(section)) {
+ final String name = section.getStringProperty("Name");
+ final String pathString = section.getStringProperty("Path");
+ final boolean isRelative = section.getIntProperty("IsRelative") == 1;
+ final File path = isRelative ? new File(mozillaDir, pathString) : new File(pathString);
+ result.put(name, path.getAbsolutePath());
- if (stopOnSuccess) {
- return result;
+ if (stopOnSuccess) {
+ return result;
+ }
}
}
}
return result;
}
public static File findProfileDir(final File mozillaDir, final String profileName) throws NoSuchProfileException {
// Open profiles.ini to find the correct path.
final INIParser parser = GeckoProfileDirectories.getProfilesINI(mozillaDir);
-
- for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
- final INISection section = e.nextElement();
- final String name = section.getStringProperty("Name");
- if (name != null && name.equals(profileName)) {
- if (section.getIntProperty("IsRelative") == 1) {
- return new File(mozillaDir, section.getStringProperty("Path"));
+ if (parser.getSections() != null) {
+ for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements(); ) {
+ final INISection section = e.nextElement();
+ final String name = section.getStringProperty("Name");
+ if (name != null && name.equals(profileName)) {
+ if (section.getIntProperty("IsRelative") == 1) {
+ return new File(mozillaDir, section.getStringProperty("Path"));
+ }
+ return new File(section.getStringProperty("Path"));
}
- return new File(section.getStringProperty("Path"));
}
}
-
throw new NoSuchProfileException("No profile " + profileName);
}
}
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -271,17 +271,17 @@
<!ENTITY pref_whats_new_notification "What\'s new in &brandShortName;">
<!ENTITY pref_whats_new_notification_summary "Learn about new features after an update">
<!-- Custom Tabs is an Android API for allowing third-party apps to open URLs in a customized UI.
Instead of switching to the browser it appears as if the user stays in the third-party app.
For more see: https://developer.chrome.com/multidevice/android/customtabs -->
<!ENTITY pref_custom_tabs "Custom Tabs">
-<!ENTITY pref_custom_tabs_summary "Allow third-party apps to open URLs with a customized look and feel. ">
+<!ENTITY pref_custom_tabs_summary "Allow third-party apps to open URLs with a customized look and feel.">
<!ENTITY tracking_protection_prompt_title "Now with Tracking Protection">
<!ENTITY tracking_protection_prompt_text "Actively block tracking elements so you don\'t have to worry.">
<!ENTITY tracking_protection_prompt_tip_text "Visit Privacy settings to learn more">
<!ENTITY tracking_protection_prompt_action_button "Got it!">
<!ENTITY tab_queue_toast_message3 "Tab saved in &brandShortName;">
<!ENTITY tab_queue_toast_action "Open now">
--- a/netwerk/base/nsIPermissionManager.idl
+++ b/netwerk/base/nsIPermissionManager.idl
@@ -89,16 +89,25 @@ interface nsIPermissionManager : nsISupp
*/
void add(in nsIURI uri,
in string type,
in uint32_t permission,
[optional] in uint32_t expireType,
[optional] in int64_t expireTime);
/**
+ * Get all custom permissions for a given URI. This will return
+ * an enumerator of all permissions which are not set to default
+ * and which belong to the matching prinicpal of the given URI.
+ *
+ * @param uri the URI to get all permissions for
+ */
+ nsISimpleEnumerator getAllForURI(in nsIURI uri);
+
+ /**
* Add permission information for a given principal.
* It is internally calling the other add() method using the nsIURI from the
* principal.
* Passing a system principal will be a no-op because they will always be
* granted permissions.
*/
void addFromPrincipal(in nsIPrincipal principal, in string typed,
in uint32_t permission,
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -462,16 +462,17 @@ public:
// access. But the graphics layer might not be using them
// anymore; this needs to be studied.
case SHMGET:
case SHMCTL:
case SHMAT:
case SHMDT:
case SEMGET:
case SEMCTL:
+ case SEMOP:
return Some(Allow());
default:
return SandboxPolicyCommon::EvaluateIpcCall(aCall);
}
}
#endif
virtual ResultExpr EvaluateSyscall(int sysno) const override {
@@ -518,17 +519,17 @@ public:
CASES_FOR_fstatfs:
case __NR_chmod:
case __NR_rename:
case __NR_symlink:
case __NR_quotactl:
case __NR_utimes:
case __NR_link:
case __NR_unlink:
- case __NR_fchown:
+ CASES_FOR_fchown:
case __NR_fchmod:
#endif
return Allow();
case __NR_readlink:
case __NR_readlinkat:
// Workaround for bug 964455:
return Error(EINVAL);
--- a/security/sandbox/linux/SandboxFilterUtil.h
+++ b/security/sandbox/linux/SandboxFilterUtil.h
@@ -69,16 +69,22 @@ public:
// cases; see, e.g., the handling of RT vs. non-RT signal syscalls.
#ifdef __NR_mmap2
#define CASES_FOR_mmap case __NR_mmap2
#else
#define CASES_FOR_mmap case __NR_mmap
#endif
+#ifdef __NR_fchown32
+#define CASES_FOR_fchown case __NR_fchown32: case __NR_fchown
+#else
+#define CASES_FOR_fchown case __NR_fchown
+#endif
+
#ifdef __NR_getuid32
#define CASES_FOR_getuid case __NR_getuid32
#define CASES_FOR_getgid case __NR_getgid32
#define CASES_FOR_geteuid case __NR_geteuid32
#define CASES_FOR_getegid case __NR_getegid32
#define CASES_FOR_getresuid case __NR_getresuid32: case __NR_getresuid
#define CASES_FOR_getresgid case __NR_getresgid32: case __NR_getresgid
// The set*id syscalls are omitted; we'll probably never need to allow them.
--- a/testing/mozharness/configs/balrog/production.py
+++ b/testing/mozharness/configs/balrog/production.py
@@ -11,24 +11,24 @@ config = {
'firefox': 'ffxbld',
'thunderbird': 'tbirdbld',
'mobile': 'ffxbld',
'Fennec': 'ffxbld',
'graphene': 'ffxbld',
'horizon': 'ffxbld',
}
},
- # Bug 1275911 - get releng automation posting to cloudops balrog stage instance
- {
- 'balrog_api_root': 'https://balrog-admin.stage.mozaws.net/api',
- 'ignore_failures': True,
- 'balrog_usernames': {
- 'b2g': 'stage-b2gbld',
- 'firefox': 'stage-ffxbld',
- 'thunderbird': 'stage-tbirdbld',
- 'mobile': 'stage-ffxbld',
- 'Fennec': 'stage-ffxbld',
- 'graphene': 'stage-ffxbld',
- 'horizon': 'stage-ffxbld',
- }
- }
+ # Bug 1261346 - temporarily disable staging balrog submissions
+ # {
+ # 'balrog_api_root': 'https://aus4-admin-dev.allizom.org/api',
+ # 'ignore_failures': True,
+ # 'balrog_usernames': {
+ # 'b2g': 'stage-b2gbld',
+ # 'firefox': 'stage-ffxbld',
+ # 'thunderbird': 'stage-tbirdbld',
+ # 'mobile': 'stage-ffxbld',
+ # 'Fennec': 'stage-ffxbld',
+ # 'graphene': 'stage-ffxbld',
+ # 'horizon': 'stage-ffxbld',
+ # }
+ # }
]
}
--- a/toolkit/components/reader/AboutReader.jsm
+++ b/toolkit/components/reader/AboutReader.jsm
@@ -609,39 +609,50 @@ AboutReader.prototype = {
_loadArticle: Task.async(function* () {
let url = this._getOriginalUrl();
this._showProgressDelayed();
let article;
if (this._articlePromise) {
article = yield this._articlePromise;
} else {
- article = yield this._getArticle(url);
+ try {
+ article = yield this._getArticle(url);
+ } catch (e) {
+ if (e && e.newURL) {
+ let readerURL = "about:reader?url=" + encodeURIComponent(e.newURL);
+ this._win.location.replace(readerURL);
+ return;
+ }
+ }
}
if (this._windowUnloaded) {
return;
}
- if (article) {
- this._showContent(article);
- } else if (this._articlePromise) {
- // If we were promised an article, show an error message if there's a failure.
+ // Replace the loading message with an error message if there's a failure.
+ // Users are supposed to navigate away by themselves (because we cannot
+ // remove ourselves from session history.)
+ if (!article) {
this._showError();
- } else {
- // Otherwise, just load the original URL. We can encounter this case when
- // loading an about:reader URL directly (e.g. opening a reading list item).
- this._win.location.href = url;
+ return;
}
+
+ this._showContent(article);
}),
_getArticle: function(url) {
return new Promise((resolve, reject) => {
let listener = (message) => {
this._mm.removeMessageListener("Reader:ArticleData", listener);
+ if (message.data.newURL) {
+ reject({ newURL: message.data.newURL });
+ return;
+ }
resolve(message.data.article);
};
this._mm.addMessageListener("Reader:ArticleData", listener);
this._mm.sendAsyncMessage("Reader:ArticleGet", { url: url });
});
},
_requestFavicon: function() {
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -1618,88 +1618,83 @@ Engine.prototype = {
Services.ww.getNewPrompter(null).alert(title, text);
}
if (!aBytes) {
promptError();
return;
}
- var engineToUpdate = null;
- if (aEngine._engineToUpdate) {
- engineToUpdate = aEngine._engineToUpdate.wrappedJSObject;
-
- // Make this new engine use the old engine's shortName,
- // to preserve user-set metadata.
- aEngine._shortName = engineToUpdate._shortName;
- }
-
var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
createInstance(Ci.nsIDOMParser);
var doc = parser.parseFromBuffer(aBytes, aBytes.length, "text/xml");
aEngine._data = doc.documentElement;
try {
// Initialize the engine from the obtained data
aEngine._initFromData();
} catch (ex) {
LOG("_onLoad: Failed to init engine!\n" + ex);
// Report an error to the user
promptError();
return;
}
- // Check that when adding a new engine (e.g., not updating an
- // existing one), a duplicate engine does not already exist.
- if (!engineToUpdate) {
+ if (aEngine._engineToUpdate) {
+ let engineToUpdate = aEngine._engineToUpdate.wrappedJSObject;
+
+ // Make this new engine use the old engine's shortName, and preserve
+ // metadata.
+ aEngine._shortName = engineToUpdate._shortName;
+ Object.keys(engineToUpdate._metaData).forEach(key => {
+ aEngine.setAttr(key, engineToUpdate.getAttr(key));
+ });
+ aEngine._loadPath = engineToUpdate._loadPath;
+
+ // Keep track of the last modified date, so that we can make conditional
+ // requests for future updates.
+ aEngine.setAttr("updatelastmodified", (new Date()).toUTCString());
+
+ // Set the new engine's icon, if it doesn't yet have one.
+ if (!aEngine._iconURI && engineToUpdate._iconURI)
+ aEngine._iconURI = engineToUpdate._iconURI;
+ } else {
+ // Check that when adding a new engine (e.g., not updating an
+ // existing one), a duplicate engine does not already exist.
if (Services.search.getEngineByName(aEngine.name)) {
// If we're confirming the engine load, then display a "this is a
// duplicate engine" prompt; otherwise, fail silently.
if (aEngine._confirm) {
promptError({ error: "error_duplicate_engine_msg",
title: "error_invalid_engine_title"
}, Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE);
} else {
onError(Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE);
}
LOG("_onLoad: duplicate engine found, bailing");
return;
}
- }
-
- // If requested, confirm the addition now that we have the title.
- // This property is only ever true for engines added via
- // nsIBrowserSearchService::addEngine.
- if (aEngine._confirm) {
- var confirmation = aEngine._confirmAddEngine();
- LOG("_onLoad: confirm is " + confirmation.confirmed +
- "; useNow is " + confirmation.useNow);
- if (!confirmation.confirmed) {
- onError();
- return;
+
+ // If requested, confirm the addition now that we have the title.
+ // This property is only ever true for engines added via
+ // nsIBrowserSearchService::addEngine.
+ if (aEngine._confirm) {
+ var confirmation = aEngine._confirmAddEngine();
+ LOG("_onLoad: confirm is " + confirmation.confirmed +
+ "; useNow is " + confirmation.useNow);
+ if (!confirmation.confirmed) {
+ onError();
+ return;
+ }
+ aEngine._useNow = confirmation.useNow;
}
- aEngine._useNow = confirmation.useNow;
- }
-
- // If we don't yet have a shortName, get one now. We would already have one
- // if this is an update and _file was set above.
- if (!aEngine._shortName)
+
aEngine._shortName = sanitizeName(aEngine.name);
-
- aEngine._loadPath = aEngine.getAnonymizedLoadPath(null, aEngine._uri);
- aEngine.setAttr("loadPathHash", getVerificationHash(aEngine._loadPath));
-
- if (engineToUpdate) {
- // Keep track of the last modified date, so that we can make conditional
- // requests for future updates.
- aEngine.setAttr("updatelastmodified", (new Date()).toUTCString());
-
- // Set the new engine's icon, if it doesn't yet have one.
- if (!aEngine._iconURI && engineToUpdate._iconURI)
- aEngine._iconURI = engineToUpdate._iconURI;
+ aEngine._loadPath = aEngine.getAnonymizedLoadPath(null, aEngine._uri);
+ aEngine.setAttr("loadPathHash", getVerificationHash(aEngine._loadPath));
}
// Notify the search service of the successful load. It will deal with
// updates by checking aEngine._engineToUpdate.
notifyAction(aEngine, SEARCH_ENGINE_LOADED);
// Notify the callback if needed
if (aEngine._installCallback) {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_engineUpdate.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Test that user-set metadata isn't lost on engine update */