зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to m-c, a=merge
MozReview-Commit-ID: 3nuTAHtJkkD
This commit is contained in:
Коммит
7fd865b93f
|
@ -172,10 +172,6 @@ var gTests = [
|
||||||
content.location.reload();
|
content.location.reload();
|
||||||
yield promise;
|
yield promise;
|
||||||
|
|
||||||
if ((yield promiseTodoObserverNotCalled("recording-device-events")) == 1) {
|
|
||||||
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
|
|
||||||
}
|
|
||||||
|
|
||||||
yield expectObserverCalled("recording-window-ended");
|
yield expectObserverCalled("recording-window-ended");
|
||||||
yield expectNoObserverCalled();
|
yield expectNoObserverCalled();
|
||||||
yield checkNotSharing();
|
yield checkNotSharing();
|
||||||
|
|
|
@ -58,6 +58,10 @@ var gTests = [
|
||||||
|
|
||||||
// And finally verify the attribute is removed when closing the stream.
|
// And finally verify the attribute is removed when closing the stream.
|
||||||
yield closeStream();
|
yield closeStream();
|
||||||
|
|
||||||
|
// TODO(Bug 1304997): Fix the race in closeStream() and remove this
|
||||||
|
// promiseWaitForCondition().
|
||||||
|
yield promiseWaitForCondition(() => !tab.getAttribute("sharing"));
|
||||||
is(tab.getAttribute("sharing"), "",
|
is(tab.getAttribute("sharing"), "",
|
||||||
"the tab no longer has the 'sharing' attribute after closing the stream");
|
"the tab no longer has the 'sharing' attribute after closing the stream");
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,9 +110,6 @@ var gTests = [
|
||||||
yield promiseReloadFrame("frame1");
|
yield promiseReloadFrame("frame1");
|
||||||
yield promise;
|
yield promise;
|
||||||
|
|
||||||
if ((yield promiseTodoObserverNotCalled("recording-device-events")) == 1) {
|
|
||||||
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
|
|
||||||
}
|
|
||||||
yield expectObserverCalled("recording-window-ended");
|
yield expectObserverCalled("recording-window-ended");
|
||||||
yield expectNoObserverCalled();
|
yield expectNoObserverCalled();
|
||||||
yield checkNotSharing();
|
yield checkNotSharing();
|
||||||
|
@ -188,9 +185,6 @@ var gTests = [
|
||||||
|
|
||||||
yield expectObserverCalled("recording-window-ended");
|
yield expectObserverCalled("recording-window-ended");
|
||||||
yield checkSharingUI({video: false, audio: true});
|
yield checkSharingUI({video: false, audio: true});
|
||||||
if ((yield promiseTodoObserverNotCalled("recording-device-events")) == 1) {
|
|
||||||
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
|
|
||||||
}
|
|
||||||
yield expectNoObserverCalled();
|
yield expectNoObserverCalled();
|
||||||
|
|
||||||
yield closeStream(false, "frame1");
|
yield closeStream(false, "frame1");
|
||||||
|
@ -225,9 +219,6 @@ var gTests = [
|
||||||
content.location.reload();
|
content.location.reload();
|
||||||
yield promise;
|
yield promise;
|
||||||
|
|
||||||
if ((yield promiseTodoObserverNotCalled("recording-device-events")) == 1) {
|
|
||||||
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
|
|
||||||
}
|
|
||||||
yield expectObserverCalled("recording-window-ended");
|
yield expectObserverCalled("recording-window-ended");
|
||||||
yield expectNoObserverCalled();
|
yield expectNoObserverCalled();
|
||||||
yield checkNotSharing();
|
yield checkNotSharing();
|
||||||
|
|
|
@ -51,11 +51,8 @@ var gTests = [
|
||||||
gBrowser.selectedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
|
gBrowser.selectedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
|
||||||
yield BrowserTestUtils.closeWindow(win);
|
yield BrowserTestUtils.closeWindow(win);
|
||||||
|
|
||||||
if ((yield promiseTodoObserverNotCalled("recording-device-events")) == 1) {
|
|
||||||
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
|
|
||||||
}
|
|
||||||
|
|
||||||
yield expectObserverCalled("recording-window-ended");
|
yield expectObserverCalled("recording-window-ended");
|
||||||
|
yield expectObserverCalled("recording-device-events");
|
||||||
yield expectNoObserverCalled();
|
yield expectNoObserverCalled();
|
||||||
yield checkNotSharing();
|
yield checkNotSharing();
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,17 +35,18 @@ function requestDevice(aAudio, aVideo, aShare) {
|
||||||
opts.fake = true;
|
opts.fake = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.navigator.mozGetUserMedia(opts, function(stream) {
|
window.navigator.mediaDevices.getUserMedia(opts)
|
||||||
gStream = stream;
|
.then(stream => {
|
||||||
message("ok");
|
gStream = stream;
|
||||||
}, function(err) { message("error: " + err); });
|
message("ok");
|
||||||
|
}, err => message("error: " + err));
|
||||||
}
|
}
|
||||||
message("pending");
|
message("pending");
|
||||||
|
|
||||||
function closeStream() {
|
function closeStream() {
|
||||||
if (!gStream)
|
if (!gStream)
|
||||||
return;
|
return;
|
||||||
gStream.stop();
|
gStream.getTracks().forEach(t => t.stop());
|
||||||
gStream = null;
|
gStream = null;
|
||||||
message("closed");
|
message("closed");
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,13 +34,6 @@ addMessageListener("Test:ExpectObserverCalled", ({data}) => {
|
||||||
--gObservedTopics[data];
|
--gObservedTopics[data];
|
||||||
});
|
});
|
||||||
|
|
||||||
addMessageListener("Test:TodoObserverNotCalled", ({data}) => {
|
|
||||||
sendAsyncMessage("Test:TodoObserverNotCalled:Reply",
|
|
||||||
{count: gObservedTopics[data]});
|
|
||||||
if (gObservedTopics[data] == 1)
|
|
||||||
gObservedTopics[data] = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
addMessageListener("Test:ExpectNoObserverCalled", data => {
|
addMessageListener("Test:ExpectNoObserverCalled", data => {
|
||||||
sendAsyncMessage("Test:ExpectNoObserverCalled:Reply", gObservedTopics);
|
sendAsyncMessage("Test:ExpectNoObserverCalled:Reply", gObservedTopics);
|
||||||
gObservedTopics = {};
|
gObservedTopics = {};
|
||||||
|
|
|
@ -233,18 +233,6 @@ function expectNoObserverCalled() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function promiseTodoObserverNotCalled(aTopic) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
let mm = _mm();
|
|
||||||
mm.addMessageListener("Test:TodoObserverNotCalled:Reply",
|
|
||||||
function listener({data}) {
|
|
||||||
mm.removeMessageListener("Test:TodoObserverNotCalled:Reply", listener);
|
|
||||||
resolve(data.count);
|
|
||||||
});
|
|
||||||
mm.sendAsyncMessage("Test:TodoObserverNotCalled", aTopic);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function promiseMessage(aMessage, aAction) {
|
function promiseMessage(aMessage, aAction) {
|
||||||
let promise = new Promise((resolve, reject) => {
|
let promise = new Promise((resolve, reject) => {
|
||||||
let mm = _mm();
|
let mm = _mm();
|
||||||
|
@ -357,11 +345,6 @@ function* stopSharing(aType = "camera") {
|
||||||
yield promiseRecordingEvent;
|
yield promiseRecordingEvent;
|
||||||
yield expectObserverCalled("getUserMedia:revoke");
|
yield expectObserverCalled("getUserMedia:revoke");
|
||||||
yield expectObserverCalled("recording-window-ended");
|
yield expectObserverCalled("recording-window-ended");
|
||||||
|
|
||||||
if ((yield promiseTodoObserverNotCalled("recording-device-events")) == 1) {
|
|
||||||
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
|
|
||||||
}
|
|
||||||
|
|
||||||
yield expectNoObserverCalled();
|
yield expectNoObserverCalled();
|
||||||
yield* checkNotSharing();
|
yield* checkNotSharing();
|
||||||
}
|
}
|
||||||
|
@ -398,12 +381,6 @@ function* closeStream(aAlreadyClosed, aFrameId) {
|
||||||
if (promises)
|
if (promises)
|
||||||
yield Promise.all(promises);
|
yield Promise.all(promises);
|
||||||
|
|
||||||
// If a GC occurs before MediaStream.stop() is dispatched, we'll receive
|
|
||||||
// recording-device-events for each track instead of one for the stream.
|
|
||||||
if ((yield promiseTodoObserverNotCalled("recording-device-events")) == 1) {
|
|
||||||
todo(false, "Stream was GC'd before MediaStream.stop() was dispatched (bug 1284038)");
|
|
||||||
}
|
|
||||||
|
|
||||||
yield* assertWebRTCIndicatorStatus(null);
|
yield* assertWebRTCIndicatorStatus(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,13 +93,15 @@ exports.getHighlighterUtils = function (toolbox) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start/stop the element picker on the debuggee target.
|
* Start/stop the element picker on the debuggee target.
|
||||||
|
* @param {Boolean} doFocus - Optionally focus the content area once the picker is
|
||||||
|
* activated.
|
||||||
* @return A promise that resolves when done
|
* @return A promise that resolves when done
|
||||||
*/
|
*/
|
||||||
let togglePicker = exported.togglePicker = function () {
|
let togglePicker = exported.togglePicker = function (doFocus) {
|
||||||
if (isPicking) {
|
if (isPicking) {
|
||||||
return stopPicker();
|
return cancelPicker();
|
||||||
} else {
|
} else {
|
||||||
return startPicker();
|
return startPicker(doFocus);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -109,10 +111,12 @@ exports.getHighlighterUtils = function (toolbox) {
|
||||||
* on the target page to highlight the hovered/picked element.
|
* on the target page to highlight the hovered/picked element.
|
||||||
* Depending on the server-side capabilities, this may fire events when nodes
|
* Depending on the server-side capabilities, this may fire events when nodes
|
||||||
* are hovered.
|
* are hovered.
|
||||||
|
* @param {Boolean} doFocus - Optionally focus the content area once the picker is
|
||||||
|
* activated.
|
||||||
* @return A promise that resolves when the picker has started or immediately
|
* @return A promise that resolves when the picker has started or immediately
|
||||||
* if it is already started
|
* if it is already started
|
||||||
*/
|
*/
|
||||||
let startPicker = exported.startPicker = requireInspector(function* () {
|
let startPicker = exported.startPicker = requireInspector(function* (doFocus = false) {
|
||||||
if (isPicking) {
|
if (isPicking) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -120,14 +124,14 @@ exports.getHighlighterUtils = function (toolbox) {
|
||||||
|
|
||||||
toolbox.pickerButtonChecked = true;
|
toolbox.pickerButtonChecked = true;
|
||||||
yield toolbox.selectTool("inspector");
|
yield toolbox.selectTool("inspector");
|
||||||
toolbox.on("select", stopPicker);
|
toolbox.on("select", cancelPicker);
|
||||||
|
|
||||||
if (isRemoteHighlightable()) {
|
if (isRemoteHighlightable()) {
|
||||||
toolbox.walker.on("picker-node-hovered", onPickerNodeHovered);
|
toolbox.walker.on("picker-node-hovered", onPickerNodeHovered);
|
||||||
toolbox.walker.on("picker-node-picked", onPickerNodePicked);
|
toolbox.walker.on("picker-node-picked", onPickerNodePicked);
|
||||||
toolbox.walker.on("picker-node-canceled", onPickerNodeCanceled);
|
toolbox.walker.on("picker-node-canceled", onPickerNodeCanceled);
|
||||||
|
|
||||||
yield toolbox.highlighter.pick();
|
yield toolbox.highlighter.pick(doFocus);
|
||||||
toolbox.emit("picker-started");
|
toolbox.emit("picker-started");
|
||||||
} else {
|
} else {
|
||||||
// If the target doesn't have the highlighter actor, we can use the
|
// If the target doesn't have the highlighter actor, we can use the
|
||||||
|
@ -164,10 +168,18 @@ exports.getHighlighterUtils = function (toolbox) {
|
||||||
yield toolbox.walker.cancelPick();
|
yield toolbox.walker.cancelPick();
|
||||||
}
|
}
|
||||||
|
|
||||||
toolbox.off("select", stopPicker);
|
toolbox.off("select", cancelPicker);
|
||||||
toolbox.emit("picker-stopped");
|
toolbox.emit("picker-stopped");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the picker, but also emit an event that the picker was canceled.
|
||||||
|
*/
|
||||||
|
let cancelPicker = exported.cancelPicker = Task.async(function* () {
|
||||||
|
yield stopPicker();
|
||||||
|
toolbox.emit("picker-canceled");
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a node is hovered by the mouse when the highlighter is in picker mode
|
* When a node is hovered by the mouse when the highlighter is in picker mode
|
||||||
* @param {Object} data Information about the node being hovered
|
* @param {Object} data Information about the node being hovered
|
||||||
|
@ -190,7 +202,7 @@ exports.getHighlighterUtils = function (toolbox) {
|
||||||
* gets the focus.
|
* gets the focus.
|
||||||
*/
|
*/
|
||||||
function onPickerNodeCanceled() {
|
function onPickerNodeCanceled() {
|
||||||
stopPicker();
|
cancelPicker();
|
||||||
toolbox.win.focus();
|
toolbox.win.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,10 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
|
||||||
this._toggleMinimizeMode = this._toggleMinimizeMode.bind(this);
|
this._toggleMinimizeMode = this._toggleMinimizeMode.bind(this);
|
||||||
this._onTabbarFocus = this._onTabbarFocus.bind(this);
|
this._onTabbarFocus = this._onTabbarFocus.bind(this);
|
||||||
this._onTabbarArrowKeypress = this._onTabbarArrowKeypress.bind(this);
|
this._onTabbarArrowKeypress = this._onTabbarArrowKeypress.bind(this);
|
||||||
|
this._onPickerClick = this._onPickerClick.bind(this);
|
||||||
|
this._onPickerKeypress = this._onPickerKeypress.bind(this);
|
||||||
|
this._onPickerStarted = this._onPickerStarted.bind(this);
|
||||||
|
this._onPickerStopped = this._onPickerStopped.bind(this);
|
||||||
|
|
||||||
this._target.on("close", this.destroy);
|
this._target.on("close", this.destroy);
|
||||||
|
|
||||||
|
@ -149,6 +153,9 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
|
||||||
|
|
||||||
gDevTools.on("tool-registered", this._toolRegistered);
|
gDevTools.on("tool-registered", this._toolRegistered);
|
||||||
gDevTools.on("tool-unregistered", this._toolUnregistered);
|
gDevTools.on("tool-unregistered", this._toolUnregistered);
|
||||||
|
|
||||||
|
this.on("picker-started", this._onPickerStarted);
|
||||||
|
this.on("picker-stopped", this._onPickerStopped);
|
||||||
}
|
}
|
||||||
exports.Toolbox = Toolbox;
|
exports.Toolbox = Toolbox;
|
||||||
|
|
||||||
|
@ -984,8 +991,39 @@ Toolbox.prototype = {
|
||||||
let container = this.doc.querySelector("#toolbox-picker-container");
|
let container = this.doc.querySelector("#toolbox-picker-container");
|
||||||
container.appendChild(this._pickerButton);
|
container.appendChild(this._pickerButton);
|
||||||
|
|
||||||
this._togglePicker = this.highlighterUtils.togglePicker.bind(this.highlighterUtils);
|
this._pickerButton.addEventListener("click", this._onPickerClick, false);
|
||||||
this._pickerButton.addEventListener("click", this._togglePicker, false);
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the picker, but also decide whether or not the highlighter should
|
||||||
|
* focus the window. This is only desirable when the toolbox is mounted to the
|
||||||
|
* window. When devtools is free floating, then the target window should not
|
||||||
|
* pop in front of the viewer when the picker is clicked.
|
||||||
|
*/
|
||||||
|
_onPickerClick: function () {
|
||||||
|
let focus = this.hostType === Toolbox.HostType.BOTTOM ||
|
||||||
|
this.hostType === Toolbox.HostType.SIDE;
|
||||||
|
this.highlighterUtils.togglePicker(focus);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the picker is activated, then allow the Escape key to deactivate the
|
||||||
|
* functionality instead of the default behavior of toggling the console.
|
||||||
|
*/
|
||||||
|
_onPickerKeypress: function (event) {
|
||||||
|
if (event.keyCode === KeyCodes.DOM_VK_ESCAPE) {
|
||||||
|
this.highlighterUtils.cancelPicker();
|
||||||
|
// Stop the console from toggling.
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPickerStarted: function () {
|
||||||
|
this.doc.addEventListener("keypress", this._onPickerKeypress, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPickerStopped: function () {
|
||||||
|
this.doc.removeEventListener("keypress", this._onPickerKeypress, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
<link rel="stylesheet" href="resource://devtools/client/shared/components/sidebar-toggle.css"/>
|
<link rel="stylesheet" href="resource://devtools/client/shared/components/sidebar-toggle.css"/>
|
||||||
<link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/tabs.css"/>
|
<link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/tabs.css"/>
|
||||||
<link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/tabbar.css"/>
|
<link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/tabbar.css"/>
|
||||||
<link rel="stylesheet" href="resource://devtools/client/inspector/components/side-panel.css"/>
|
|
||||||
<link rel="stylesheet" href="resource://devtools/client/inspector/components/inspector-tab-panel.css"/>
|
<link rel="stylesheet" href="resource://devtools/client/inspector/components/inspector-tab-panel.css"/>
|
||||||
<link rel="stylesheet" href="resource://devtools/client/shared/components/splitter/split-box.css"/>
|
<link rel="stylesheet" href="resource://devtools/client/shared/components/splitter/split-box.css"/>
|
||||||
<link rel="stylesheet" href="resource://devtools/client/inspector/layout/components/Accordion.css"/>
|
<link rel="stylesheet" href="resource://devtools/client/inspector/layout/components/Accordion.css"/>
|
||||||
|
|
|
@ -108,6 +108,7 @@ function MarkupView(inspector, frame, controllerWindow) {
|
||||||
this._onFocus = this._onFocus.bind(this);
|
this._onFocus = this._onFocus.bind(this);
|
||||||
this._onMouseMove = this._onMouseMove.bind(this);
|
this._onMouseMove = this._onMouseMove.bind(this);
|
||||||
this._onMouseOut = this._onMouseOut.bind(this);
|
this._onMouseOut = this._onMouseOut.bind(this);
|
||||||
|
this._onToolboxPickerCanceled = this._onToolboxPickerCanceled.bind(this);
|
||||||
this._onToolboxPickerHover = this._onToolboxPickerHover.bind(this);
|
this._onToolboxPickerHover = this._onToolboxPickerHover.bind(this);
|
||||||
this._onCollapseAttributesPrefChange =
|
this._onCollapseAttributesPrefChange =
|
||||||
this._onCollapseAttributesPrefChange.bind(this);
|
this._onCollapseAttributesPrefChange.bind(this);
|
||||||
|
@ -127,6 +128,7 @@ function MarkupView(inspector, frame, controllerWindow) {
|
||||||
this.walker.on("mutations", this._mutationObserver);
|
this.walker.on("mutations", this._mutationObserver);
|
||||||
this.walker.on("display-change", this._onDisplayChange);
|
this.walker.on("display-change", this._onDisplayChange);
|
||||||
this.inspector.selection.on("new-node-front", this._onNewSelection);
|
this.inspector.selection.on("new-node-front", this._onNewSelection);
|
||||||
|
this.toolbox.on("picker-canceled", this._onToolboxPickerCanceled);
|
||||||
this.toolbox.on("picker-node-hovered", this._onToolboxPickerHover);
|
this.toolbox.on("picker-node-hovered", this._onToolboxPickerHover);
|
||||||
|
|
||||||
this._onNewSelection();
|
this._onNewSelection();
|
||||||
|
@ -189,6 +191,16 @@ MarkupView.prototype = {
|
||||||
}, e => console.error(e));
|
}, e => console.error(e));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the element picker gets canceled, make sure and re-center the view on the
|
||||||
|
* current selected element.
|
||||||
|
*/
|
||||||
|
_onToolboxPickerCanceled: function () {
|
||||||
|
if (this._selectedContainer) {
|
||||||
|
scrollIntoViewIfNeeded(this._selectedContainer.editor.elt);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
|
|
||||||
_onMouseMove: function (event) {
|
_onMouseMove: function (event) {
|
||||||
|
@ -375,6 +387,9 @@ MarkupView.prototype = {
|
||||||
|
|
||||||
this.getContainer(nodeFront).hovered = true;
|
this.getContainer(nodeFront).hovered = true;
|
||||||
this._hoveredNode = nodeFront;
|
this._hoveredNode = nodeFront;
|
||||||
|
// Emit an event that the container view is actually hovered now, as this function
|
||||||
|
// can be called by an asynchronous caller.
|
||||||
|
this.emit("showcontainerhovered");
|
||||||
},
|
},
|
||||||
|
|
||||||
_onMouseOut: function (event) {
|
_onMouseOut: function (event) {
|
||||||
|
|
|
@ -26,6 +26,7 @@ support-files =
|
||||||
doc_inspector_infobar_02.html
|
doc_inspector_infobar_02.html
|
||||||
doc_inspector_infobar_03.html
|
doc_inspector_infobar_03.html
|
||||||
doc_inspector_infobar_textnode.html
|
doc_inspector_infobar_textnode.html
|
||||||
|
doc_inspector_long-divs.html
|
||||||
doc_inspector_menu.html
|
doc_inspector_menu.html
|
||||||
doc_inspector_outerhtml.html
|
doc_inspector_outerhtml.html
|
||||||
doc_inspector_remove-iframe-during-load.html
|
doc_inspector_remove-iframe-during-load.html
|
||||||
|
@ -67,6 +68,7 @@ skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keybo
|
||||||
[browser_inspector_highlighter-03.js]
|
[browser_inspector_highlighter-03.js]
|
||||||
[browser_inspector_highlighter-04.js]
|
[browser_inspector_highlighter-04.js]
|
||||||
[browser_inspector_highlighter-by-type.js]
|
[browser_inspector_highlighter-by-type.js]
|
||||||
|
[browser_inspector_highlighter-cancel.js]
|
||||||
[browser_inspector_highlighter-comments.js]
|
[browser_inspector_highlighter-comments.js]
|
||||||
[browser_inspector_highlighter-cssgrid_01.js]
|
[browser_inspector_highlighter-cssgrid_01.js]
|
||||||
[browser_inspector_highlighter-csstransform_01.js]
|
[browser_inspector_highlighter-csstransform_01.js]
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/* 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";
|
||||||
|
|
||||||
|
// Test that canceling the element picker zooms back on the focused element. Bug 1224304.
|
||||||
|
|
||||||
|
const TEST_URL = URL_ROOT + "doc_inspector_long-divs.html";
|
||||||
|
|
||||||
|
add_task(function* () {
|
||||||
|
let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL);
|
||||||
|
|
||||||
|
yield selectAndHighlightNode("#focus-here", inspector);
|
||||||
|
ok((yield testActor.assertHighlightedNode("#focus-here")),
|
||||||
|
"The highlighter focuses on div#focus-here");
|
||||||
|
ok(isSelectedMarkupNodeInView(),
|
||||||
|
"The currently selected node is on the screen.");
|
||||||
|
|
||||||
|
// Start the picker but skip focusing manually focusing on the target, let the element
|
||||||
|
// picker do the focusing.
|
||||||
|
yield startPicker(toolbox, true);
|
||||||
|
yield moveMouseOver("#zoom-here");
|
||||||
|
ok(!isSelectedMarkupNodeInView(),
|
||||||
|
"The currently selected node is off the screen.");
|
||||||
|
|
||||||
|
yield cancelPickerByShortcut();
|
||||||
|
ok(isSelectedMarkupNodeInView(),
|
||||||
|
"The currently selected node is focused back on the screen.");
|
||||||
|
|
||||||
|
function cancelPickerByShortcut() {
|
||||||
|
info("Key pressed. Waiting for picker to be canceled.");
|
||||||
|
testActor.synthesizeKey({key: "VK_ESCAPE", options: {}});
|
||||||
|
return inspector.toolbox.once("picker-canceled");
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveMouseOver(selector) {
|
||||||
|
info(`Waiting for element ${selector} to be hovered in the markup view`);
|
||||||
|
testActor.synthesizeMouse({
|
||||||
|
options: {type: "mousemove"},
|
||||||
|
center: true,
|
||||||
|
selector: selector
|
||||||
|
});
|
||||||
|
return inspector.markup.once("showcontainerhovered");
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelectedMarkupNodeInView() {
|
||||||
|
const selectedNodeContainer = inspector.markup._selectedContainer.elt;
|
||||||
|
const bounds = selectedNodeContainer.getBoundingClientRect();
|
||||||
|
return bounds.top > 0 && bounds.bottom > 0;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,104 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Inspector Long Div Listing</title>
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
background-color: #0002;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div id="focus-here">focus here</div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div id="zoom-here">zoom-here</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -69,15 +69,19 @@ var navigateTo = function (toolbox, url) {
|
||||||
/**
|
/**
|
||||||
* Start the element picker and focus the content window.
|
* Start the element picker and focus the content window.
|
||||||
* @param {Toolbox} toolbox
|
* @param {Toolbox} toolbox
|
||||||
|
* @param {Boolean} skipFocus - Allow tests to bypass the focus event.
|
||||||
*/
|
*/
|
||||||
var startPicker = Task.async(function* (toolbox) {
|
var startPicker = Task.async(function* (toolbox, skipFocus) {
|
||||||
info("Start the element picker");
|
info("Start the element picker");
|
||||||
|
toolbox.win.focus();
|
||||||
yield toolbox.highlighterUtils.startPicker();
|
yield toolbox.highlighterUtils.startPicker();
|
||||||
// Make sure the content window is focused since the picker does not focus
|
if (!skipFocus) {
|
||||||
// the content window by default.
|
// By default make sure the content window is focused since the picker may not focus
|
||||||
yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
|
// the content window by default.
|
||||||
content.focus();
|
yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
|
||||||
});
|
content.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -59,6 +59,11 @@ responsive.screenshotGeneratedFilename=Screen Shot %1$S at %2$S
|
||||||
# non-remote tab.
|
# non-remote tab.
|
||||||
responsive.remoteOnly=Responsive Design Mode is only available for remote browser tabs, such as those used for web content in multi-process Firefox.
|
responsive.remoteOnly=Responsive Design Mode is only available for remote browser tabs, such as those used for web content in multi-process Firefox.
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (responsive.noContainerTabs): Message displayed in the tab's
|
||||||
|
# notification box if a user tries to open Responsive Design Mode in a
|
||||||
|
# container tab.
|
||||||
|
responsive.noContainerTabs=Responsive Design Mode is currently unavailable in container tabs.
|
||||||
|
|
||||||
# LOCALIZATION NOTE (responsive.noThrottling): UI option in a menu to configure
|
# LOCALIZATION NOTE (responsive.noThrottling): UI option in a menu to configure
|
||||||
# network throttling. This option is the default and disables throttling so you
|
# network throttling. This option is the default and disables throttling so you
|
||||||
# just have normal network conditions. There is not very much room in the UI
|
# just have normal network conditions. There is not very much room in the UI
|
||||||
|
|
|
@ -76,6 +76,11 @@ const ResponsiveUIManager = exports.ResponsiveUIManager = {
|
||||||
this.showRemoteOnlyNotification(window, tab, options);
|
this.showRemoteOnlyNotification(window, tab, options);
|
||||||
return promise.reject(new Error("RDM only available for remote tabs."));
|
return promise.reject(new Error("RDM only available for remote tabs."));
|
||||||
}
|
}
|
||||||
|
// Remove this once we support this case in bug 1306975.
|
||||||
|
if (tab.linkedBrowser.hasAttribute("usercontextid")) {
|
||||||
|
this.showNoContainerTabsNotification(window, tab, options);
|
||||||
|
return promise.reject(new Error("RDM not available for container tabs."));
|
||||||
|
}
|
||||||
if (!this.isActiveForTab(tab)) {
|
if (!this.isActiveForTab(tab)) {
|
||||||
this.initMenuCheckListenerFor(window);
|
this.initMenuCheckListenerFor(window);
|
||||||
|
|
||||||
|
@ -213,7 +218,16 @@ const ResponsiveUIManager = exports.ResponsiveUIManager = {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
showRemoteOnlyNotification(window, tab, { command } = {}) {
|
showRemoteOnlyNotification(window, tab, options) {
|
||||||
|
this.showErrorNotification(window, tab, options, getStr("responsive.remoteOnly"));
|
||||||
|
},
|
||||||
|
|
||||||
|
showNoContainerTabsNotification(window, tab, options) {
|
||||||
|
this.showErrorNotification(window, tab, options,
|
||||||
|
getStr("responsive.noContainerTabs"));
|
||||||
|
},
|
||||||
|
|
||||||
|
showErrorNotification(window, tab, { command } = {}, msg) {
|
||||||
// Default to using the browser's per-tab notification box
|
// Default to using the browser's per-tab notification box
|
||||||
let nbox = window.gBrowser.getNotificationBox(tab.linkedBrowser);
|
let nbox = window.gBrowser.getNotificationBox(tab.linkedBrowser);
|
||||||
|
|
||||||
|
@ -228,14 +242,14 @@ const ResponsiveUIManager = exports.ResponsiveUIManager = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = "devtools-responsive-remote-only";
|
let value = "devtools-responsive-error";
|
||||||
if (nbox.getNotificationWithValue(value)) {
|
if (nbox.getNotificationWithValue(value)) {
|
||||||
// Notification already displayed
|
// Notification already displayed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nbox.appendNotification(
|
nbox.appendNotification(
|
||||||
getStr("responsive.remoteOnly"),
|
msg,
|
||||||
value,
|
value,
|
||||||
null,
|
null,
|
||||||
nbox.PRIORITY_CRITICAL_MEDIUM,
|
nbox.PRIORITY_CRITICAL_MEDIUM,
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/* -*- 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";
|
||||||
|
|
||||||
|
// Make this available to both AMD and CJS environments
|
||||||
|
define(function (require, exports, module) {
|
||||||
|
// Dependencies
|
||||||
|
const React = require("devtools/client/shared/vendor/react");
|
||||||
|
|
||||||
|
// Shortcuts
|
||||||
|
const { span } = React.DOM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a Infinity object
|
||||||
|
*/
|
||||||
|
const InfinityRep = React.createClass({
|
||||||
|
displayName: "Infinity",
|
||||||
|
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
span({className: "objectBox objectBox-number"},
|
||||||
|
this.props.object.type
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function supportsObject(object, type) {
|
||||||
|
return type == "Infinity" || type == "-Infinity";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exports from this module
|
||||||
|
exports.InfinityRep = {
|
||||||
|
rep: InfinityRep,
|
||||||
|
supportsObject: supportsObject
|
||||||
|
};
|
||||||
|
});
|
|
@ -15,6 +15,8 @@ DevToolsModules(
|
||||||
'grip-array.js',
|
'grip-array.js',
|
||||||
'grip-map.js',
|
'grip-map.js',
|
||||||
'grip.js',
|
'grip.js',
|
||||||
|
'infinity.js',
|
||||||
|
'nan.js',
|
||||||
'null.js',
|
'null.js',
|
||||||
'number.js',
|
'number.js',
|
||||||
'object-with-text.js',
|
'object-with-text.js',
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/* -*- 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";
|
||||||
|
|
||||||
|
// Make this available to both AMD and CJS environments
|
||||||
|
define(function (require, exports, module) {
|
||||||
|
// Dependencies
|
||||||
|
const React = require("devtools/client/shared/vendor/react");
|
||||||
|
|
||||||
|
// Shortcuts
|
||||||
|
const { span } = React.DOM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a NaN object
|
||||||
|
*/
|
||||||
|
const NaNRep = React.createClass({
|
||||||
|
displayName: "NaN",
|
||||||
|
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
span({className: "objectBox objectBox-nan"},
|
||||||
|
"NaN"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function supportsObject(object, type) {
|
||||||
|
return type == "NaN";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exports from this module
|
||||||
|
exports.NaNRep = {
|
||||||
|
rep: NaNRep,
|
||||||
|
supportsObject: supportsObject
|
||||||
|
};
|
||||||
|
});
|
|
@ -39,8 +39,7 @@ define(function (require, exports, module) {
|
||||||
});
|
});
|
||||||
|
|
||||||
function supportsObject(object, type) {
|
function supportsObject(object, type) {
|
||||||
return type == "boolean" || type == "number" ||
|
return ["boolean", "number", "-0"].includes(type);
|
||||||
(type == "object" && object.type == "-0");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exports from this module
|
// Exports from this module
|
||||||
|
|
|
@ -21,6 +21,8 @@ define(function (require, exports, module) {
|
||||||
const { ArrayRep } = require("./array");
|
const { ArrayRep } = require("./array");
|
||||||
const { Obj } = require("./object");
|
const { Obj } = require("./object");
|
||||||
const { SymbolRep } = require("./symbol");
|
const { SymbolRep } = require("./symbol");
|
||||||
|
const { InfinityRep } = require("./infinity");
|
||||||
|
const { NaNRep } = require("./nan");
|
||||||
|
|
||||||
// DOM types (grips)
|
// DOM types (grips)
|
||||||
const { Attribute } = require("./attribute");
|
const { Attribute } = require("./attribute");
|
||||||
|
@ -62,6 +64,8 @@ define(function (require, exports, module) {
|
||||||
StringRep,
|
StringRep,
|
||||||
Number,
|
Number,
|
||||||
SymbolRep,
|
SymbolRep,
|
||||||
|
InfinityRep,
|
||||||
|
NaNRep,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,8 +106,8 @@ define(function (require, exports, module) {
|
||||||
let type = typeof object;
|
let type = typeof object;
|
||||||
if (type == "object" && object instanceof String) {
|
if (type == "object" && object instanceof String) {
|
||||||
type = "string";
|
type = "string";
|
||||||
} else if (object && type == "object" && object.type === "symbol") {
|
} else if (object && type == "object" && object.type) {
|
||||||
type = "symbol";
|
type = object.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGrip(object)) {
|
if (isGrip(object)) {
|
||||||
|
|
|
@ -16,6 +16,8 @@ support-files =
|
||||||
[test_reps_grip.html]
|
[test_reps_grip.html]
|
||||||
[test_reps_grip-array.html]
|
[test_reps_grip-array.html]
|
||||||
[test_reps_grip-map.html]
|
[test_reps_grip-map.html]
|
||||||
|
[test_reps_infinity.html]
|
||||||
|
[test_reps_nan.html]
|
||||||
[test_reps_null.html]
|
[test_reps_null.html]
|
||||||
[test_reps_number.html]
|
[test_reps_number.html]
|
||||||
[test_reps_object.html]
|
[test_reps_object.html]
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
Test Infinity rep
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Rep test - Infinity</title>
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<pre id="test">
|
||||||
|
<script src="head.js" type="application/javascript;version=1.8"></script>
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
window.onload = Task.async(function* () {
|
||||||
|
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
|
||||||
|
let { InfinityRep } = browserRequire("devtools/client/shared/components/reps/infinity");
|
||||||
|
|
||||||
|
try {
|
||||||
|
yield testInfinity();
|
||||||
|
yield testNegativeInfinity();
|
||||||
|
} catch (e) {
|
||||||
|
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||||
|
} finally {
|
||||||
|
SimpleTest.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testInfinity() {
|
||||||
|
const stub = getGripStub("testInfinity");
|
||||||
|
const renderedRep = shallowRenderComponent(Rep, { object: stub });
|
||||||
|
is(renderedRep.type, InfinityRep.rep,
|
||||||
|
`Rep correctly selects ${InfinityRep.rep.displayName} for Infinity value`);
|
||||||
|
|
||||||
|
const renderedComponent = renderComponent(InfinityRep.rep, { object: stub });
|
||||||
|
is(renderedComponent.textContent, "Infinity",
|
||||||
|
"Infinity rep has expected text content for Infinity");
|
||||||
|
}
|
||||||
|
|
||||||
|
function testNegativeInfinity() {
|
||||||
|
const stub = getGripStub("testNegativeInfinity");
|
||||||
|
const renderedRep = shallowRenderComponent(Rep, { object: stub });
|
||||||
|
is(renderedRep.type, InfinityRep.rep,
|
||||||
|
`Rep correctly selects ${InfinityRep.rep.displayName} for negative Infinity value`);
|
||||||
|
|
||||||
|
const renderedComponent = renderComponent(InfinityRep.rep, { object: stub });
|
||||||
|
is(renderedComponent.textContent, "-Infinity",
|
||||||
|
"Infinity rep has expected text content for negative Infinity");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGripStub(name) {
|
||||||
|
switch (name) {
|
||||||
|
case "testInfinity":
|
||||||
|
return {
|
||||||
|
type: "Infinity"
|
||||||
|
};
|
||||||
|
case "testNegativeInfinity":
|
||||||
|
return {
|
||||||
|
type: "-Infinity"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
Test NaN rep
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Rep test - NaN</title>
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<pre id="test">
|
||||||
|
<script src="head.js" type="application/javascript;version=1.8"></script>
|
||||||
|
<script type="application/javascript;version=1.8">
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
window.onload = Task.async(function* () {
|
||||||
|
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
|
||||||
|
let { NaNRep } = browserRequire("devtools/client/shared/components/reps/nan");
|
||||||
|
|
||||||
|
try {
|
||||||
|
yield testNaN();
|
||||||
|
} catch (e) {
|
||||||
|
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||||
|
} finally {
|
||||||
|
SimpleTest.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testNaN() {
|
||||||
|
const stub = {
|
||||||
|
type: "NaN"
|
||||||
|
};
|
||||||
|
const renderedRep = shallowRenderComponent(Rep, {object: stub});
|
||||||
|
is(renderedRep.type, NaNRep.rep,
|
||||||
|
`Rep correctly selects ${NaNRep.rep.displayName} for NaN value`);
|
||||||
|
|
||||||
|
const renderedComponent = renderComponent(NaNRep.rep, {object: stub});
|
||||||
|
is(renderedComponent.textContent, "NaN", "NaN rep has expected text content");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -43,14 +43,7 @@ add_task(function* () {
|
||||||
|
|
||||||
info("Run tests for a Tooltip with a XUL panel");
|
info("Run tests for a Tooltip with a XUL panel");
|
||||||
useXulWrapper = true;
|
useXulWrapper = true;
|
||||||
|
yield runTests(doc);
|
||||||
let isLinux = Services.appinfo.OS === "Linux";
|
|
||||||
if (!isLinux) {
|
|
||||||
// Skip these tests on linux when using a XUL Panel wrapper because some linux window
|
|
||||||
// manager don't support nicely XUL Panels with noautohide _and_ noautofocus.
|
|
||||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1301342#c11
|
|
||||||
yield runTests(doc);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function* runTests(doc) {
|
function* runTests(doc) {
|
||||||
|
|
|
@ -229,6 +229,7 @@ function HTMLTooltip(toolboxDoc, {
|
||||||
this._position = null;
|
this._position = null;
|
||||||
|
|
||||||
this._onClick = this._onClick.bind(this);
|
this._onClick = this._onClick.bind(this);
|
||||||
|
this._onXulPanelHidden = this._onXulPanelHidden.bind(this);
|
||||||
|
|
||||||
this._toggle = new TooltipToggle(this);
|
this._toggle = new TooltipToggle(this);
|
||||||
this.startTogglingOnHover = this._toggle.start.bind(this._toggle);
|
this.startTogglingOnHover = this._toggle.start.bind(this._toggle);
|
||||||
|
@ -539,6 +540,12 @@ HTMLTooltip.prototype = {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onXulPanelHidden: function () {
|
||||||
|
if (this.isVisible()) {
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the tootlip is configured to autofocus and a focusable element can be found,
|
* If the tootlip is configured to autofocus and a focusable element can be found,
|
||||||
* focus it.
|
* focus it.
|
||||||
|
@ -572,7 +579,6 @@ HTMLTooltip.prototype = {
|
||||||
panel.setAttribute("animate", false);
|
panel.setAttribute("animate", false);
|
||||||
panel.setAttribute("consumeoutsideclicks", false);
|
panel.setAttribute("consumeoutsideclicks", false);
|
||||||
panel.setAttribute("noautofocus", true);
|
panel.setAttribute("noautofocus", true);
|
||||||
panel.setAttribute("noautohide", true);
|
|
||||||
panel.setAttribute("ignorekeys", true);
|
panel.setAttribute("ignorekeys", true);
|
||||||
panel.setAttribute("tooltip", "aHTMLTooltip");
|
panel.setAttribute("tooltip", "aHTMLTooltip");
|
||||||
|
|
||||||
|
@ -586,12 +592,20 @@ HTMLTooltip.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
_showXulWrapperAt: function (left, top) {
|
_showXulWrapperAt: function (left, top) {
|
||||||
|
this.xulPanelWrapper.addEventListener("popuphidden", this._onXulPanelHidden);
|
||||||
let onPanelShown = listenOnce(this.xulPanelWrapper, "popupshown");
|
let onPanelShown = listenOnce(this.xulPanelWrapper, "popupshown");
|
||||||
this.xulPanelWrapper.openPopupAtScreen(left, top, false);
|
this.xulPanelWrapper.openPopupAtScreen(left, top, false);
|
||||||
return onPanelShown;
|
return onPanelShown;
|
||||||
},
|
},
|
||||||
|
|
||||||
_hideXulWrapper: function () {
|
_hideXulWrapper: function () {
|
||||||
|
this.xulPanelWrapper.removeEventListener("popuphidden", this._onXulPanelHidden);
|
||||||
|
|
||||||
|
if (this.xulPanelWrapper.state === "closed") {
|
||||||
|
// XUL panel is already closed, resolve immediately.
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
let onPanelHidden = listenOnce(this.xulPanelWrapper, "popuphidden");
|
let onPanelHidden = listenOnce(this.xulPanelWrapper, "popuphidden");
|
||||||
this.xulPanelWrapper.hidePopup();
|
this.xulPanelWrapper.hidePopup();
|
||||||
return onPanelHidden;
|
return onPanelHidden;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*|*:root[platform="mac"] > scrollbar,
|
||||||
*|*:root[platform="mac"] *|*:not(html|select) > scrollbar {
|
*|*:root[platform="mac"] *|*:not(html|select) > scrollbar {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
@ -40,10 +41,15 @@
|
||||||
border-radius: 3px !important;
|
border-radius: 3px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*|*:root[platform="mac"] > scrollbar slider,
|
||||||
*|*:root[platform="mac"] *|*:not(html|select) > scrollbar slider {
|
*|*:root[platform="mac"] *|*:not(html|select) > scrollbar slider {
|
||||||
-moz-appearance: none !important;
|
-moz-appearance: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*|*:root[platform="win"] > scrollbar scrollbarbutton,
|
||||||
|
*|*:root[platform="linux"] > scrollbar scrollbarbutton,
|
||||||
|
*|*:root[platform="win"] > scrollbar gripper,
|
||||||
|
*|*:root[platform="linux"] > scrollbar gripper,
|
||||||
*|*:root[platform="win"] *|*:not(html|select) > scrollbar scrollbarbutton,
|
*|*:root[platform="win"] *|*:not(html|select) > scrollbar scrollbarbutton,
|
||||||
*|*:root[platform="linux"] *|*:not(html|select) > scrollbar scrollbarbutton,
|
*|*:root[platform="linux"] *|*:not(html|select) > scrollbar scrollbarbutton,
|
||||||
*|*:root[platform="win"] *|*:not(html|select) > scrollbar gripper,
|
*|*:root[platform="win"] *|*:not(html|select) > scrollbar gripper,
|
||||||
|
|
|
@ -353,6 +353,16 @@ var HighlighterActor = exports.HighlighterActor = protocol.ActorClassWithSpec(hi
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This pick method also focuses the highlighter's target window.
|
||||||
|
*/
|
||||||
|
pickAndFocus: function() {
|
||||||
|
// Go ahead and pass on the results to help future-proof this method.
|
||||||
|
let pickResults = this.pick();
|
||||||
|
this._highlighterEnv.window.focus();
|
||||||
|
return pickResults;
|
||||||
|
},
|
||||||
|
|
||||||
_findAndAttachElement: function (event) {
|
_findAndAttachElement: function (event) {
|
||||||
// originalTarget allows access to the "real" element before any retargeting
|
// originalTarget allows access to the "real" element before any retargeting
|
||||||
// is applied, such as in the case of XBL anonymous elements. See also
|
// is applied, such as in the case of XBL anonymous elements. See also
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { FrontClassWithSpec } = require("devtools/shared/protocol");
|
const { FrontClassWithSpec, custom } = require("devtools/shared/protocol");
|
||||||
const {
|
const {
|
||||||
customHighlighterSpec,
|
customHighlighterSpec,
|
||||||
highlighterSpec
|
highlighterSpec
|
||||||
|
@ -15,7 +15,16 @@ const HighlighterFront = FrontClassWithSpec(highlighterSpec, {
|
||||||
this.actorID = json.actor;
|
this.actorID = json.actor;
|
||||||
// FF42+ HighlighterActors starts exposing custom form, with traits object
|
// FF42+ HighlighterActors starts exposing custom form, with traits object
|
||||||
this.traits = json.traits || {};
|
this.traits = json.traits || {};
|
||||||
}
|
},
|
||||||
|
|
||||||
|
pick: custom(function (doFocus) {
|
||||||
|
if (doFocus && this.pickAndFocus) {
|
||||||
|
return this.pickAndFocus();
|
||||||
|
}
|
||||||
|
return this._pick();
|
||||||
|
}, {
|
||||||
|
impl: "_pick"
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
exports.HighlighterFront = HighlighterFront;
|
exports.HighlighterFront = HighlighterFront;
|
||||||
|
|
|
@ -28,6 +28,7 @@ const highlighterSpec = generateActorSpec({
|
||||||
request: {}
|
request: {}
|
||||||
},
|
},
|
||||||
pick: {},
|
pick: {},
|
||||||
|
pickAndFocus: {},
|
||||||
cancelPick: {}
|
cancelPick: {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2908,9 +2908,13 @@ CanvasRenderingContext2D::UpdateFilter()
|
||||||
// with.
|
// with.
|
||||||
presShell->FlushPendingNotifications(Flush_Frames);
|
presShell->FlushPendingNotifications(Flush_Frames);
|
||||||
|
|
||||||
|
bool sourceGraphicIsTainted =
|
||||||
|
(mCanvasElement && mCanvasElement->IsWriteOnly());
|
||||||
|
|
||||||
CurrentState().filter =
|
CurrentState().filter =
|
||||||
nsFilterInstance::GetFilterDescription(mCanvasElement,
|
nsFilterInstance::GetFilterDescription(mCanvasElement,
|
||||||
CurrentState().filterChain,
|
CurrentState().filterChain,
|
||||||
|
sourceGraphicIsTainted,
|
||||||
CanvasUserSpaceMetrics(GetSize(),
|
CanvasUserSpaceMetrics(GetSize(),
|
||||||
CurrentState().fontFont,
|
CurrentState().fontFont,
|
||||||
CurrentState().fontLanguage,
|
CurrentState().fontLanguage,
|
||||||
|
@ -2918,8 +2922,7 @@ CanvasRenderingContext2D::UpdateFilter()
|
||||||
presShell->GetPresContext()),
|
presShell->GetPresContext()),
|
||||||
gfxRect(0, 0, mWidth, mHeight),
|
gfxRect(0, 0, mWidth, mHeight),
|
||||||
CurrentState().filterAdditionalImages);
|
CurrentState().filterAdditionalImages);
|
||||||
CurrentState().filterSourceGraphicTainted =
|
CurrentState().filterSourceGraphicTainted = sourceGraphicIsTainted;
|
||||||
(mCanvasElement && mCanvasElement->IsWriteOnly());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -264,12 +264,6 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1040965
|
||||||
[test_filter.html]
|
[test_filter.html]
|
||||||
skip-if = (e10s && debug && os == 'win')
|
skip-if = (e10s && debug && os == 'win')
|
||||||
[test_filter_tainted.html]
|
[test_filter_tainted.html]
|
||||||
skip-if = (e10s && debug && os == 'win')
|
|
||||||
[test_filter_tainted_source_graphics.html]
|
|
||||||
skip-if = (e10s && debug && os == 'win')
|
|
||||||
[test_filter_tainted_displacement_map.html]
|
|
||||||
skip-if = (e10s && debug && os == 'win')
|
|
||||||
[test_filter_tainted_displacement_map_source_graphics.html]
|
|
||||||
[test_offscreencanvas_toblob.html]
|
[test_offscreencanvas_toblob.html]
|
||||||
subsuite = gpu
|
subsuite = gpu
|
||||||
tags = offscreencanvas
|
tags = offscreencanvas
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
<!DOCTYPE HTML>
|
|
||||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
||||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
|
||||||
<body onload="runTest()">
|
|
||||||
<svg style="display: block; width: 0; height: 0">
|
|
||||||
<defs>
|
|
||||||
<filter id="tainted">
|
|
||||||
<feImage xlink:href="http://example.com/tests/dom/canvas/test/crossorigin/image.png" />
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
function runTest() {
|
|
||||||
|
|
||||||
SpecialPowers.pushPrefEnv({ 'set': [['canvas.filters.enabled', true]] }, function () {
|
|
||||||
|
|
||||||
var canvas = document.createElement('canvas');
|
|
||||||
var ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
ctx.filter = 'url(#tainted)';
|
|
||||||
ctx.rect(0, 0, 16, 16);
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
var expected_error = 'SecurityError';
|
|
||||||
|
|
||||||
var data;
|
|
||||||
try {
|
|
||||||
data = ctx.getImageData(0, 0, 16, 16);
|
|
||||||
actual_error = "";
|
|
||||||
} catch (e) {
|
|
||||||
actual_error = e.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
is(actual_error, expected_error, 'Canvas should have been tainted and throw a SecurityError');
|
|
||||||
|
|
||||||
SimpleTest.finish();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
|
||||||
|
|
||||||
</script>
|
|
|
@ -4,53 +4,329 @@
|
||||||
<body onload="runTest()" style="margin: 0; padding: 0">
|
<body onload="runTest()" style="margin: 0; padding: 0">
|
||||||
<svg style="display: block; width: 0; height: 0">
|
<svg style="display: block; width: 0; height: 0">
|
||||||
<defs>
|
<defs>
|
||||||
<filter id="tainted">
|
<filter id="colormatrix-make-green">
|
||||||
|
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0
|
||||||
|
1 1 1 1 0
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 0 1 0"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="use-feFlood-as-map-on-SourceGraphic">
|
||||||
|
<feFlood flood-color="lime" result='green'/>
|
||||||
|
<!-- should shift everything up and to the left by 8 pixels (only respects A channel) -->
|
||||||
|
<feDisplacementMap in="SourceGraphic" in2="green" scale="16"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="render-cross-origin-red-feImage">
|
||||||
<feImage xlink:href="http://example.com/tests/dom/canvas/test/image_red-16x16.png"/>
|
<feImage xlink:href="http://example.com/tests/dom/canvas/test/image_red-16x16.png"/>
|
||||||
</filter>
|
</filter>
|
||||||
|
<filter id="generate-green">
|
||||||
|
<feFlood flood-color="lime" result='green'/>
|
||||||
|
</filter>
|
||||||
|
<filter id="use-SourceGraphic-as-map-on-red">
|
||||||
|
<feImage xlink:href='image_red-16x16.png' result='img'/>
|
||||||
|
<feDisplacementMap in="img" in2="SourceGraphic" scale="20"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="use-cross-origin-green-feImage-as-map-on-same-origin-red">
|
||||||
|
<feImage xlink:href='image_red-16x16.png' result='img'/>
|
||||||
|
<feImage xlink:href='http://example.com/tests/dom/canvas/test/image_green-16x16.png' result='map'/>
|
||||||
|
<feDisplacementMap in="img" in2="map" scale="20"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="use-cross-origin-red-feImage-as-map-on-SourceGraphic">
|
||||||
|
<feImage xlink:href='http://example.com/tests/dom/canvas/test/image_red-16x16.png' result='img'/>
|
||||||
|
<feDisplacementMap in="SourceGraphic" in2="img" scale="20"/>
|
||||||
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
<canvas id="c" width="16" height="16"></canvas>
|
<div id="canvasContainer" style="height:100px;"></div>
|
||||||
|
<img id="same-origin-green" src="image_green-16x16.png"/>
|
||||||
|
<img id="same-origin-red" src="image_red-16x16.png"/>
|
||||||
|
<img id="cross-origin-green" src="http://example.com/tests/dom/canvas/test/image_green-16x16.png"/>
|
||||||
|
<img id="cross-origin-red" src="http://example.com/tests/dom/canvas/test/image_red-16x16.png"/>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
function isPixel(ctx, x,y, r,g,b,a, pos, color, d) {
|
function isPixel(ctx, x, y, r, g, b, a, d) {
|
||||||
var pixel = ctx.getImageData(x, y, 1, 1);
|
var pixel = ctx.getImageData(x, y, 1, 1);
|
||||||
var pr = pixel.data[0],
|
var pr = pixel.data[0],
|
||||||
pg = pixel.data[1],
|
pg = pixel.data[1],
|
||||||
pb = pixel.data[2],
|
pb = pixel.data[2],
|
||||||
pa = pixel.data[3];
|
pa = pixel.data[3];
|
||||||
ok(r - d <= pr && pr <= r + d &&
|
var checkSucceeded = r - d <= pr && pr <= r + d &&
|
||||||
g - d <= pg && pg <= g + d &&
|
g - d <= pg && pg <= g + d &&
|
||||||
b - d <= pb && pb <= b + d &&
|
b - d <= pb && pb <= b + d &&
|
||||||
a - d <= pa && pa <= a + d,
|
a - d <= pa && pa <= a + d;
|
||||||
'pixel ' + pos + ' is ' + pr + ',' + pg + ',' + pb + ',' + pa + '; expected ' + color + ' +/- ' + d);
|
ok(checkSucceeded,
|
||||||
|
`pixel ${x},${y} is ${pr},${pg},${pb},${pa}; expected ${r},${g},${b},${a} +/- ${d}`);
|
||||||
|
|
||||||
|
return checkSucceeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectCanvasCtxToBeTainted(ctx) {
|
||||||
|
try {
|
||||||
|
ctx.getImageData(0, 0, 16, 16);
|
||||||
|
ok(false, 'Canvas should have been tainted and should throw a SecurityError when calling getImageData.');
|
||||||
|
} catch (e) {
|
||||||
|
is(e.name, 'SecurityError', 'Canvas should have been tainted and should throw a SecurityError when calling getImageData.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectCanvasCtxToBeUntainted(ctx) {
|
||||||
|
try {
|
||||||
|
var data = ctx.getImageData(0, 0, 16, 16);
|
||||||
|
ok(data, 'Canvas should have returned some ImageData from getImageData, and not thrown an error.');
|
||||||
|
} catch (e) {
|
||||||
|
ok(false, 'Canvas should not have prevented getImageData.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function newCanvas() {
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 16;
|
||||||
|
canvas.height = 16;
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This uses SpecialPowers + drawWindow and can check pixels even in tainted canvases.
|
||||||
|
function checkCanvasPixel(canvas, x, y, r, g, b, a, fuzz) {
|
||||||
|
var container = document.getElementById('canvasContainer');
|
||||||
|
container.appendChild(canvas);
|
||||||
|
|
||||||
|
var captureCanvas = newCanvas();
|
||||||
|
var captureCtx = SpecialPowers.wrap(captureCanvas.getContext('2d'));
|
||||||
|
captureCtx.drawWindow(window, 0, 0, 16, 16, 'rgb(255, 255, 255)', 0);
|
||||||
|
if (!isPixel(captureCtx, x, y, r, g, b, a, fuzz)) {
|
||||||
|
info(captureCanvas.toDataURL('image/png'));
|
||||||
|
}
|
||||||
|
|
||||||
|
container.removeChild(canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
function runTest() {
|
function runTest() {
|
||||||
|
|
||||||
SpecialPowers.pushPrefEnv({ 'set': [['canvas.filters.enabled', true]] }, function () {
|
SpecialPowers.pushPrefEnv({ 'set': [['canvas.filters.enabled', true]] }, function () {
|
||||||
|
var sameOriginGreenImage = document.getElementById('same-origin-green');
|
||||||
var canvas = document.getElementById('c');
|
var sameOriginRedImage = document.getElementById('same-origin-red');
|
||||||
|
var crossOriginGreenImage = document.getElementById('cross-origin-green');
|
||||||
|
var crossOriginRedImage = document.getElementById('cross-origin-red');
|
||||||
|
|
||||||
|
var canvas = newCanvas();
|
||||||
var ctx = canvas.getContext('2d');
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
ctx.filter = 'url(#tainted)';
|
// A CSS filter should not taint the canvas.
|
||||||
|
ctx.filter = 'grayscale(100%)';
|
||||||
|
ctx.fillStyle = 'green';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
checkCanvasPixel(canvas, 8,8, 92,92,92,255, 5);
|
||||||
|
expectCanvasCtxToBeUntainted(ctx);
|
||||||
|
ctx.clearRect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
// An SVG filter should not taint the canvas.
|
||||||
|
ctx.filter = 'url(#colormatrix-make-green)';
|
||||||
|
ctx.fillStyle = 'red';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
checkCanvasPixel(canvas, 8,8, 0,255,0,255, 5);
|
||||||
|
expectCanvasCtxToBeUntainted(ctx);
|
||||||
|
ctx.clearRect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
// A CSS -> SVG -> CSS filter chain should not taint the canvas.
|
||||||
|
ctx.filter = 'grayscale(100%) url(#colormatrix-make-green) grayscale(100%)';
|
||||||
|
is(ctx.filter, 'grayscale(100%) url(#colormatrix-make-green) grayscale(100%)', 'filter chain should parse correctly');
|
||||||
|
ctx.fillStyle = 'red';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
checkCanvasPixel(canvas, 8,8, 183,183,183,255, 5);
|
||||||
|
expectCanvasCtxToBeUntainted(ctx);
|
||||||
|
ctx.clearRect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
// An SVG -> CSS -> SVG filter chain should not taint the canvas.
|
||||||
|
ctx.filter = 'url(#colormatrix-make-green) grayscale(100%) url(#colormatrix-make-green)';
|
||||||
|
is(ctx.filter, 'url(#colormatrix-make-green) grayscale(100%) url(#colormatrix-make-green)', 'filter chain should parse correctly');
|
||||||
|
ctx.fillStyle = 'red';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
checkCanvasPixel(canvas, 8,8, 0,255,0,255, 5);
|
||||||
|
expectCanvasCtxToBeUntainted(ctx);
|
||||||
|
ctx.clearRect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
// feDisplacementMap with untainted map input from feFlood should work and not taint
|
||||||
|
ctx.filter = 'url(#use-feFlood-as-map-on-SourceGraphic)';
|
||||||
|
ctx.fillStyle = 'blue';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
checkCanvasPixel(canvas, 4,4, 0,0,255,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,4, 255,255,255,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 4,12, 255,255,255,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,12, 255,255,255,255, 1);
|
||||||
|
expectCanvasCtxToBeUntainted(ctx);
|
||||||
|
ctx.clearRect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
// feDisplacementMap with untainted map input from different SVG filter should work and not taint
|
||||||
|
ctx.filter = 'url(#generate-green) url(#use-SourceGraphic-as-map-on-red)';
|
||||||
|
is(ctx.filter, 'url(#generate-green) url(#use-SourceGraphic-as-map-on-red)', 'filter chain should parse correctly');
|
||||||
|
ctx.fillStyle = 'blue';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,4, 255,255,255,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 4,12, 255,255,255,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,12, 255,255,255,255, 1);
|
||||||
|
expectCanvasCtxToBeUntainted(ctx);
|
||||||
|
ctx.clearRect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
// feDisplacementMap with untainted map input from different CSS filter should work and not taint
|
||||||
|
ctx.filter = 'url(#generate-green) grayscale(100%) url(#use-SourceGraphic-as-map-on-red)';
|
||||||
|
is(ctx.filter, 'url(#generate-green) grayscale(100%) url(#use-SourceGraphic-as-map-on-red)', 'filter chain should parse correctly');
|
||||||
|
ctx.fillStyle = 'blue';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,4, 255,255,255,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 4,12, 255,255,255,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,12, 255,255,255,255, 1);
|
||||||
|
expectCanvasCtxToBeUntainted(ctx);
|
||||||
|
ctx.clearRect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
// drawImage with cross-origin image and SVG filter should apply the filter and taint the canvas.
|
||||||
|
ctx.filter = 'url(#colormatrix-make-green)';
|
||||||
|
ctx.drawImage(crossOriginRedImage, 0, 0);
|
||||||
|
checkCanvasPixel(canvas, 8,8, 0,255,0,255, 1);
|
||||||
|
expectCanvasCtxToBeTainted(ctx);
|
||||||
|
ctx.clearRect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
// Create new untainted canvas.
|
||||||
|
canvas = newCanvas();
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// drawImage with cross-origin image and CSS filter should apply the filter and taint the canvas.
|
||||||
|
ctx.filter = 'grayscale(100%)';
|
||||||
|
ctx.drawImage(crossOriginRedImage, 0, 0);
|
||||||
|
checkCanvasPixel(canvas, 8,8, 53,53,53,255, 1);
|
||||||
|
expectCanvasCtxToBeTainted(ctx);
|
||||||
|
ctx.clearRect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
// feDisplacementMap with untainted map input from different CSS filter should work even on tainted canvas
|
||||||
|
ctx.filter = 'url(#generate-green) grayscale(100%) url(#use-SourceGraphic-as-map-on-red)';
|
||||||
|
is(ctx.filter, 'url(#generate-green) grayscale(100%) url(#use-SourceGraphic-as-map-on-red)', 'filter chain should parse correctly');
|
||||||
|
ctx.fillStyle = 'blue';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,4, 255,255,255,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 4,12, 255,255,255,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,12, 255,255,255,255, 1);
|
||||||
|
expectCanvasCtxToBeTainted(ctx);
|
||||||
|
ctx.clearRect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
// feDisplacementMap with untainted map input from feFlood should work even on tainted canvas
|
||||||
|
ctx.filter = 'url(#use-feFlood-as-map-on-SourceGraphic)';
|
||||||
|
ctx.fillStyle = 'blue';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
checkCanvasPixel(canvas, 4,4, 0,0,255,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,4, 255,255,255,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 4,12, 255,255,255,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,12, 255,255,255,255, 1);
|
||||||
|
expectCanvasCtxToBeTainted(ctx);
|
||||||
|
ctx.clearRect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
// Create new untainted canvas.
|
||||||
|
canvas = newCanvas();
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// cross-origin feImage should render correctly and taint the canvas
|
||||||
|
ctx.filter = 'url(#render-cross-origin-red-feImage)';
|
||||||
ctx.rect(0, 0, 16, 16);
|
ctx.rect(0, 0, 16, 16);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
checkCanvasPixel(canvas, 8,8, 255,0,0,255, 5);
|
||||||
|
expectCanvasCtxToBeTainted(ctx);
|
||||||
|
|
||||||
|
// Create new untainted canvas.
|
||||||
|
canvas = newCanvas();
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
var canvas2 = document.createElement('canvas');
|
// cross-origin feImage should be ignored as map input to a displacement map, and taint the canvas.
|
||||||
var ctx2 = SpecialPowers.wrap(canvas2.getContext('2d'));
|
ctx.filter = 'url(#use-cross-origin-green-feImage-as-map-on-same-origin-red)';
|
||||||
ctx2.drawWindow(window, 0, 0, 16, 16, 'rgb(255,255,255)', 0);
|
ctx.rect(0, 0, 16, 16);
|
||||||
isPixel(ctx2, 8,8, 255,0,0,255, '8,8', "255,0,0,255", 5);
|
ctx.fill();
|
||||||
|
checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
|
||||||
var expected_error = 'SecurityError';
|
checkCanvasPixel(canvas, 12,4, 255,0,0,255, 1);
|
||||||
var data;
|
checkCanvasPixel(canvas, 4,12, 255,0,0,255, 1);
|
||||||
try {
|
checkCanvasPixel(canvas, 12,12, 255,0,0,255, 1);
|
||||||
data = ctx.getImageData(0, 0, 16, 16);
|
expectCanvasCtxToBeTainted(ctx);
|
||||||
actual_error = "";
|
|
||||||
} catch (e) {
|
// Create new untainted canvas.
|
||||||
actual_error = e.name;
|
canvas = newCanvas();
|
||||||
}
|
ctx = canvas.getContext('2d');
|
||||||
is(actual_error, expected_error, 'canvas should have been tainted and throw a SecurityError');
|
|
||||||
|
expectCanvasCtxToBeUntainted(ctx);
|
||||||
|
|
||||||
|
// cross-origin SourceGraphic should be ignored as map input to a displacement map, and taint the canvas.
|
||||||
|
// SourceGraphic: cross-origin image_green-16x16.png,
|
||||||
|
// gets used as map to shift same-origin image_red-16x16.png,
|
||||||
|
// but should get ignored so that image_red-16x16.png gets drawn unshifted.
|
||||||
|
ctx.filter = 'url(#use-SourceGraphic-as-map-on-red)';
|
||||||
|
ctx.drawImage(crossOriginGreenImage, 0, 0);
|
||||||
|
// expect to see red because cross-origin image_green-16x16.png should not have displaced the red away
|
||||||
|
checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,4, 255,0,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 4,12, 255,0,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,12, 255,0,0,255, 1);
|
||||||
|
expectCanvasCtxToBeTainted(ctx);
|
||||||
|
|
||||||
|
// cross-origin feImage should be ignored as map to displacement map, and taint the canvas.
|
||||||
|
// SourceGraphic: cross-origin image_green-16x16.png,
|
||||||
|
// Cross-origin feImage image_red-16x16.png gets used as map to shift SourceGraphic,
|
||||||
|
// but should get ignored so that image_green-16x16.png gets drawn unshifted.
|
||||||
|
ctx.filter = 'url(#use-cross-origin-red-feImage-as-map-on-SourceGraphic)';
|
||||||
|
ctx.drawImage(crossOriginGreenImage, 0, 0);
|
||||||
|
// expect to see green because cross-origin image_red-16x16.png should not have displaced the green away
|
||||||
|
checkCanvasPixel(canvas, 4,4, 0,255,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,4, 0,255,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 4,12, 0,255,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,12, 0,255,0,255, 1);
|
||||||
|
expectCanvasCtxToBeTainted(ctx);
|
||||||
|
|
||||||
|
// Create new untainted canvas.
|
||||||
|
canvas = newCanvas();
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// feDisplacementMap with tainted map input from different CSS filter should be ignored
|
||||||
|
ctx.filter = 'grayscale(100%) url(#use-SourceGraphic-as-map-on-red)';
|
||||||
|
is(ctx.filter, 'grayscale(100%) url(#use-SourceGraphic-as-map-on-red)', 'filter chain should parse correctly');
|
||||||
|
ctx.drawImage(crossOriginGreenImage, 0, 0);
|
||||||
|
checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,4, 255,0,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 4,12, 255,0,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,12, 255,0,0,255, 1);
|
||||||
|
expectCanvasCtxToBeTainted(ctx);
|
||||||
|
ctx.clearRect(0, 0, 16, 16);
|
||||||
|
|
||||||
|
// Create new untainted canvas.
|
||||||
|
canvas = newCanvas();
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// feDisplacementMap with tainted feImage map input from different SVG filter should be ignored
|
||||||
|
ctx.filter = 'url(#render-cross-origin-red-feImage) url(#use-SourceGraphic-as-map-on-red)';
|
||||||
|
is(ctx.filter, 'url(#render-cross-origin-red-feImage) url(#use-SourceGraphic-as-map-on-red)', 'filter chain should parse correctly');
|
||||||
|
ctx.fillStyle = 'blue';
|
||||||
|
ctx.fillRect(0, 0, 16, 16);
|
||||||
|
checkCanvasPixel(canvas, 4,4, 255,0,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,4, 255,0,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 4,12, 255,0,0,255, 1);
|
||||||
|
checkCanvasPixel(canvas, 12,12, 255,0,0,255, 1);
|
||||||
|
expectCanvasCtxToBeTainted(ctx);
|
||||||
|
|
||||||
|
// Paint 'canvas' (which is red and tainted) into a different canvas and makes sure it taints that other canvas.
|
||||||
|
var secondCanvas = newCanvas();
|
||||||
|
var secondCtx = secondCanvas.getContext('2d');
|
||||||
|
secondCtx.filter = 'grayscale(100%)';
|
||||||
|
secondCtx.drawImage(canvas, 0, 0);
|
||||||
|
checkCanvasPixel(secondCanvas, 4,4, 53,53,53,255, 1);
|
||||||
|
checkCanvasPixel(secondCanvas, 12,4, 53,53,53,255, 1);
|
||||||
|
checkCanvasPixel(secondCanvas, 4,12, 53,53,53,255, 1);
|
||||||
|
checkCanvasPixel(secondCanvas, 12,12, 53,53,53,255, 1);
|
||||||
|
expectCanvasCtxToBeTainted(secondCtx);
|
||||||
|
|
||||||
|
// Fill the left half with blue (i.e. an untainted SourceGraphic) and make sure the canvas is still tainted.
|
||||||
|
secondCtx.fillStyle = "blue";
|
||||||
|
secondCtx.fillRect(0, 0, 8, 16);
|
||||||
|
checkCanvasPixel(secondCanvas, 4,4, 17,17,17,255, 1);
|
||||||
|
checkCanvasPixel(secondCanvas, 12,4, 53,53,53,255, 1);
|
||||||
|
checkCanvasPixel(secondCanvas, 4,12, 17,17,17,255, 1);
|
||||||
|
checkCanvasPixel(secondCanvas, 12,12, 53,53,53,255, 1);
|
||||||
|
expectCanvasCtxToBeTainted(secondCtx);
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
<!DOCTYPE HTML>
|
|
||||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
||||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
|
||||||
<body onload="runTest()" style="margin: 0; padding: 0">
|
|
||||||
<svg style="display: block; width: 0; height: 0">
|
|
||||||
<defs>
|
|
||||||
<filter id="tainted">
|
|
||||||
<feImage xlink:href='image_red-16x16.png' result='img'/>
|
|
||||||
<feImage xlink:href='http://example.com/tests/dom/canvas/test/image_green-16x16.png' result='map'/>
|
|
||||||
<feDisplacementMap in="img" in2="map" scale="20"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
<canvas id="c" width="16" height="16"></canvas>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
function isPixel(ctx, x,y, r,g,b,a, pos, color, d) {
|
|
||||||
var pixel = ctx.getImageData(x, y, 1, 1);
|
|
||||||
var pr = pixel.data[0],
|
|
||||||
pg = pixel.data[1],
|
|
||||||
pb = pixel.data[2],
|
|
||||||
pa = pixel.data[3];
|
|
||||||
ok(r - d <= pr && pr <= r + d &&
|
|
||||||
g - d <= pg && pg <= g + d &&
|
|
||||||
b - d <= pb && pb <= b + d &&
|
|
||||||
a - d <= pa && pa <= a + d,
|
|
||||||
'pixel ' + pos + ' is ' + pr + ',' + pg + ',' + pb + ',' + pa + '; expected ' + color + ' +/- ' + d);
|
|
||||||
}
|
|
||||||
|
|
||||||
function runTest() {
|
|
||||||
|
|
||||||
SpecialPowers.pushPrefEnv({ 'set': [['canvas.filters.enabled', true]] }, function () {
|
|
||||||
|
|
||||||
var canvas = document.getElementById('c');
|
|
||||||
var ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
ctx.filter = 'url(#tainted)';
|
|
||||||
ctx.rect(0, 0, 16, 16);
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
var canvas2 = document.createElement('canvas');
|
|
||||||
var ctx2 = SpecialPowers.wrap(canvas2.getContext('2d'));
|
|
||||||
ctx2.drawWindow(window, 0, 0, 16, 16, 'rgb(255,255,255)', 0);
|
|
||||||
isPixel(ctx2, 8,8, 255,0,0,255, '8,8', "255,0,0,255", 5);
|
|
||||||
|
|
||||||
var expected_error = 'SecurityError';
|
|
||||||
var data;
|
|
||||||
try {
|
|
||||||
data = ctx.getImageData(0, 0, 16, 16);
|
|
||||||
actual_error = "";
|
|
||||||
} catch (e) {
|
|
||||||
actual_error = e.name;
|
|
||||||
}
|
|
||||||
is(actual_error, expected_error, 'canvas should have been tainted and throw a SecurityError');
|
|
||||||
|
|
||||||
SimpleTest.finish();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
|
||||||
|
|
||||||
</script>
|
|
|
@ -1,73 +0,0 @@
|
||||||
<!DOCTYPE HTML>
|
|
||||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
||||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
|
||||||
<body onload="runTest()" style="margin: 0; padding: 0">
|
|
||||||
<svg style="display: block; width: 0; height: 0">
|
|
||||||
<defs>
|
|
||||||
<filter id="tainted1">
|
|
||||||
<feImage xlink:href='image_red-16x16.png' result='img'/>
|
|
||||||
<feDisplacementMap in="img" in2="SourceGraphic" scale="20"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="tainted2">
|
|
||||||
<feImage xlink:href='http://example.com/tests/dom/canvas/test/image_red-16x16.png' result='img'/>
|
|
||||||
<feDisplacementMap in="SourceGraphic" in2="img" scale="20"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
<canvas id="c" width="16" height="16"></canvas>
|
|
||||||
<img id="i" src="http://example.com/tests/dom/canvas/test/image_green-16x16.png"/>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
function isPixel(ctx, x,y, r,g,b,a, pos, color, d) {
|
|
||||||
var pixel = ctx.getImageData(x, y, 1, 1);
|
|
||||||
var pr = pixel.data[0],
|
|
||||||
pg = pixel.data[1],
|
|
||||||
pb = pixel.data[2],
|
|
||||||
pa = pixel.data[3];
|
|
||||||
ok(r - d <= pr && pr <= r + d &&
|
|
||||||
g - d <= pg && pg <= g + d &&
|
|
||||||
b - d <= pb && pb <= b + d &&
|
|
||||||
a - d <= pa && pa <= a + d,
|
|
||||||
'pixel ' + pos + ' is ' + pr + ',' + pg + ',' + pb + ',' + pa + '; expected ' + color + ' +/- ' + d);
|
|
||||||
}
|
|
||||||
|
|
||||||
function runTest() {
|
|
||||||
|
|
||||||
SpecialPowers.pushPrefEnv({ 'set': [['canvas.filters.enabled', true]] }, function () {
|
|
||||||
|
|
||||||
var canvas = document.getElementById('c');
|
|
||||||
var ctx = canvas.getContext('2d');
|
|
||||||
var img = document.getElementById('i');
|
|
||||||
ctx.filter = 'url(#tainted1)';
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
|
|
||||||
var canvas2 = document.createElement('canvas');
|
|
||||||
var ctx2 = SpecialPowers.wrap(canvas2.getContext('2d'));
|
|
||||||
ctx2.drawWindow(window, 0, 0, 16, 16, 'rgb(255, 255, 255)', 0);
|
|
||||||
isPixel(ctx2, 8,8, 255,0,0,255, '8,8', "255,0,0,255", 5);
|
|
||||||
|
|
||||||
ctx.filter = 'url(#tainted2)';
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
|
|
||||||
ctx2.drawWindow(window, 0, 0, 16, 16, 'rgb(255, 255, 255)', 0);
|
|
||||||
isPixel(ctx2, 8,8, 0,255,0,255, '8,8', "0,255,0,255", 5);
|
|
||||||
|
|
||||||
var expected_error = 'SecurityError';
|
|
||||||
var data;
|
|
||||||
try {
|
|
||||||
data = ctx.getImageData(0, 0, 16, 16);
|
|
||||||
actual_error = "";
|
|
||||||
} catch (e) {
|
|
||||||
actual_error = e.name;
|
|
||||||
}
|
|
||||||
is(actual_error, expected_error, 'canvas should have been tainted and throw a SecurityError');
|
|
||||||
|
|
||||||
SimpleTest.finish();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
|
||||||
|
|
||||||
</script>
|
|
|
@ -1,65 +0,0 @@
|
||||||
<!DOCTYPE HTML>
|
|
||||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
||||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
|
||||||
<body onload="runTest()" style="margin: 0; padding: 0">
|
|
||||||
<svg style="display: block; width: 0; height: 0">
|
|
||||||
<defs>
|
|
||||||
<filter id="tainted">
|
|
||||||
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0
|
|
||||||
1 1 1 1 0
|
|
||||||
0 0 0 0 0
|
|
||||||
0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
<canvas id="c" width="16" height="16"></canvas>
|
|
||||||
<img id="i" src="http://example.com/tests/dom/canvas/test/image_red-16x16.png"/>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
function isPixel(ctx, x,y, r,g,b,a, pos, color, d) {
|
|
||||||
var pixel = ctx.getImageData(x, y, 1, 1);
|
|
||||||
var pr = pixel.data[0],
|
|
||||||
pg = pixel.data[1],
|
|
||||||
pb = pixel.data[2],
|
|
||||||
pa = pixel.data[3];
|
|
||||||
ok(r - d <= pr && pr <= r + d &&
|
|
||||||
g - d <= pg && pg <= g + d &&
|
|
||||||
b - d <= pb && pb <= b + d &&
|
|
||||||
a - d <= pa && pa <= a + d,
|
|
||||||
'pixel ' + pos + ' is ' + pr + ',' + pg + ',' + pb + ',' + pa + '; expected ' + color + ' +/- ' + d);
|
|
||||||
}
|
|
||||||
|
|
||||||
function runTest() {
|
|
||||||
|
|
||||||
SpecialPowers.pushPrefEnv({ 'set': [['canvas.filters.enabled', true]] }, function () {
|
|
||||||
|
|
||||||
var canvas = document.getElementById('c');
|
|
||||||
var ctx = canvas.getContext('2d');
|
|
||||||
var img = document.getElementById('i');
|
|
||||||
ctx.filter = 'url(#tainted)';
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
|
|
||||||
var canvas2 = document.createElement('canvas');
|
|
||||||
var ctx2 = SpecialPowers.wrap(canvas2.getContext('2d'));
|
|
||||||
ctx2.drawWindow(window, 0, 0, 16, 16, 'rgb(255,255,255)', 0);
|
|
||||||
isPixel(ctx2, 8,8, 0,255,0,255, '8,8', "0,255,0,255", 5);
|
|
||||||
|
|
||||||
var expected_error = 'SecurityError';
|
|
||||||
var data;
|
|
||||||
try {
|
|
||||||
data = ctx.getImageData(0, 0, 16, 16);
|
|
||||||
actual_error = "";
|
|
||||||
} catch (e) {
|
|
||||||
actual_error = e.name;
|
|
||||||
}
|
|
||||||
is(actual_error, expected_error, 'canvas should have been tainted and throw a SecurityError');
|
|
||||||
|
|
||||||
SimpleTest.finish();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
|
||||||
|
|
||||||
</script>
|
|
|
@ -263,7 +263,11 @@ public:
|
||||||
, mFinished(false)
|
, mFinished(false)
|
||||||
, mRemoved(false)
|
, mRemoved(false)
|
||||||
, mAudioStopped(false)
|
, mAudioStopped(false)
|
||||||
, mVideoStopped(false) {}
|
, mAudioStopPending(false)
|
||||||
|
, mVideoStopped(false)
|
||||||
|
, mVideoStopPending(false)
|
||||||
|
, mChromeNotificationTaskPosted(false)
|
||||||
|
{}
|
||||||
|
|
||||||
~GetUserMediaCallbackMediaStreamListener()
|
~GetUserMediaCallbackMediaStreamListener()
|
||||||
{
|
{
|
||||||
|
@ -302,6 +306,8 @@ public:
|
||||||
|
|
||||||
void StopTrack(TrackID aID);
|
void StopTrack(TrackID aID);
|
||||||
|
|
||||||
|
void NotifyChromeOfTrackStops();
|
||||||
|
|
||||||
typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
|
typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
|
||||||
|
|
||||||
already_AddRefed<PledgeVoid>
|
already_AddRefed<PledgeVoid>
|
||||||
|
@ -494,10 +500,22 @@ private:
|
||||||
// MainThread only.
|
// MainThread only.
|
||||||
bool mAudioStopped;
|
bool mAudioStopped;
|
||||||
|
|
||||||
|
// true if we have scheduled MEDIA_STOP or MEDIA_STOP_TRACK for mAudioDevice.
|
||||||
|
// MainThread only.
|
||||||
|
bool mAudioStopPending;
|
||||||
|
|
||||||
// true if we have sent MEDIA_STOP or MEDIA_STOP_TRACK for mVideoDevice.
|
// true if we have sent MEDIA_STOP or MEDIA_STOP_TRACK for mVideoDevice.
|
||||||
// MainThread only.
|
// MainThread only.
|
||||||
bool mVideoStopped;
|
bool mVideoStopped;
|
||||||
|
|
||||||
|
// true if we have scheduled MEDIA_STOP or MEDIA_STOP_TRACK for mVideoDevice.
|
||||||
|
// MainThread only.
|
||||||
|
bool mVideoStopPending;
|
||||||
|
|
||||||
|
// true if we have scheduled a task to notify chrome in the next stable state.
|
||||||
|
// The task will reset this to false. MainThread only.
|
||||||
|
bool mChromeNotificationTaskPosted;
|
||||||
|
|
||||||
// Set at Activate on MainThread
|
// Set at Activate on MainThread
|
||||||
|
|
||||||
// Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
|
// Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
|
||||||
|
@ -743,7 +761,8 @@ protected:
|
||||||
NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
|
NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
|
||||||
|
|
||||||
MediaDevice::MediaDevice(MediaEngineSource* aSource, bool aIsVideo)
|
MediaDevice::MediaDevice(MediaEngineSource* aSource, bool aIsVideo)
|
||||||
: mMediaSource(aSource->GetMediaSource())
|
: mScary(aSource->GetScary())
|
||||||
|
, mMediaSource(aSource->GetMediaSource())
|
||||||
, mSource(aSource)
|
, mSource(aSource)
|
||||||
, mIsVideo(aIsVideo)
|
, mIsVideo(aIsVideo)
|
||||||
{
|
{
|
||||||
|
@ -881,6 +900,13 @@ MediaDevice::GetRawId(nsAString& aID)
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
MediaDevice::GetScary(bool* aScary)
|
||||||
|
{
|
||||||
|
*aScary = mScary;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaDevice::SetId(const nsAString& aID)
|
MediaDevice::SetId(const nsAString& aID)
|
||||||
{
|
{
|
||||||
|
@ -3546,9 +3572,9 @@ GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID)
|
||||||
{
|
{
|
||||||
LOG(("Can't stop gUM track %d (%s), exists=%d, stopped=%d",
|
LOG(("Can't stop gUM track %d (%s), exists=%d, stopped=%d",
|
||||||
aTrackID,
|
aTrackID,
|
||||||
aTrackID == kAudioTrack ? "audio" : "video",
|
stopAudio ? "audio" : "video",
|
||||||
aTrackID == kAudioTrack ? !!mAudioDevice : !!mVideoDevice,
|
stopAudio ? !!mAudioDevice : !!mVideoDevice,
|
||||||
aTrackID == kAudioTrack ? mAudioStopped : mVideoStopped));
|
stopAudio ? mAudioStopped : mVideoStopped));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3558,6 +3584,59 @@ GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We wait until stable state before notifying chrome so chrome only does one
|
||||||
|
// update if more tracks are stopped in this event loop.
|
||||||
|
|
||||||
|
mAudioStopPending |= stopAudio;
|
||||||
|
mVideoStopPending |= stopVideo;
|
||||||
|
|
||||||
|
if (mChromeNotificationTaskPosted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsIRunnable> runnable =
|
||||||
|
NewRunnableMethod(this, &GetUserMediaCallbackMediaStreamListener::NotifyChromeOfTrackStops);
|
||||||
|
nsContentUtils::RunInStableState(runnable.forget());
|
||||||
|
mChromeNotificationTaskPosted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GetUserMediaCallbackMediaStreamListener::NotifyChromeOfTrackStops()
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(mChromeNotificationTaskPosted);
|
||||||
|
mChromeNotificationTaskPosted = false;
|
||||||
|
|
||||||
|
// We make sure these are always reset.
|
||||||
|
bool stopAudio = mAudioStopPending;
|
||||||
|
bool stopVideo = mVideoStopPending;
|
||||||
|
mAudioStopPending = false;
|
||||||
|
mVideoStopPending = false;
|
||||||
|
|
||||||
|
if (mStopped) {
|
||||||
|
// The entire capture was stopped while we were waiting for stable state.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MOZ_ASSERT(stopAudio || stopVideo);
|
||||||
|
MOZ_ASSERT(!stopAudio || !mAudioStopped,
|
||||||
|
"If there's a pending stop for audio, audio must not have been stopped");
|
||||||
|
MOZ_ASSERT(!stopAudio || mAudioDevice,
|
||||||
|
"If there's a pending stop for audio, there must be an audio device");
|
||||||
|
MOZ_ASSERT(!stopVideo || !mVideoStopped,
|
||||||
|
"If there's a pending stop for video, video must not have been stopped");
|
||||||
|
MOZ_ASSERT(!stopVideo || mVideoDevice,
|
||||||
|
"If there's a pending stop for video, there must be a video device");
|
||||||
|
|
||||||
|
if ((stopAudio || mAudioStopped || !mAudioDevice) &&
|
||||||
|
(stopAudio || mVideoStopped || !mVideoDevice)) {
|
||||||
|
// All tracks stopped.
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mAudioStopped |= stopAudio;
|
||||||
|
mVideoStopped |= stopVideo;
|
||||||
|
|
||||||
RefPtr<MediaOperationTask> mediaOperation =
|
RefPtr<MediaOperationTask> mediaOperation =
|
||||||
new MediaOperationTask(MEDIA_STOP_TRACK,
|
new MediaOperationTask(MEDIA_STOP_TRACK,
|
||||||
this, nullptr, nullptr,
|
this, nullptr, nullptr,
|
||||||
|
@ -3565,8 +3644,6 @@ GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID)
|
||||||
stopVideo ? mVideoDevice.get() : nullptr,
|
stopVideo ? mVideoDevice.get() : nullptr,
|
||||||
false , mWindowID, nullptr);
|
false , mWindowID, nullptr);
|
||||||
MediaManager::PostTask(mediaOperation.forget());
|
MediaManager::PostTask(mediaOperation.forget());
|
||||||
mAudioStopped |= stopAudio;
|
|
||||||
mVideoStopped |= stopVideo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -94,6 +94,7 @@ protected:
|
||||||
nsString mName;
|
nsString mName;
|
||||||
nsString mID;
|
nsString mID;
|
||||||
nsString mRawID;
|
nsString mRawID;
|
||||||
|
bool mScary;
|
||||||
dom::MediaSourceEnum mMediaSource;
|
dom::MediaSourceEnum mMediaSource;
|
||||||
RefPtr<MediaEngineSource> mSource;
|
RefPtr<MediaEngineSource> mSource;
|
||||||
RefPtr<MediaEngineSource::AllocationHandle> mAllocationHandle;
|
RefPtr<MediaEngineSource::AllocationHandle> mAllocationHandle;
|
||||||
|
|
|
@ -13,6 +13,7 @@ interface nsIMediaDevice : nsISupports
|
||||||
readonly attribute DOMString id;
|
readonly attribute DOMString id;
|
||||||
readonly attribute DOMString mediaSource;
|
readonly attribute DOMString mediaSource;
|
||||||
readonly attribute DOMString rawId;
|
readonly attribute DOMString rawId;
|
||||||
|
readonly attribute boolean scary;
|
||||||
};
|
};
|
||||||
|
|
||||||
[scriptable, function, uuid(24544878-d35e-4962-8c5f-fb84e97bdfee)]
|
[scriptable, function, uuid(24544878-d35e-4962-8c5f-fb84e97bdfee)]
|
||||||
|
|
|
@ -214,6 +214,9 @@ public:
|
||||||
/* Populate the UUID of this device in the nsACString */
|
/* Populate the UUID of this device in the nsACString */
|
||||||
virtual void GetUUID(nsACString&) const = 0;
|
virtual void GetUUID(nsACString&) const = 0;
|
||||||
|
|
||||||
|
/* Override w/true if source does end-run around cross origin restrictions. */
|
||||||
|
virtual bool GetScary() const { return false; };
|
||||||
|
|
||||||
class AllocationHandle
|
class AllocationHandle
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -249,9 +252,14 @@ public:
|
||||||
return a.get() == b.get();
|
return a.get() == b.get();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
MOZ_ASSERT(mRegisteredHandles.Contains(handle, Comparator()));
|
|
||||||
mRegisteredHandles.RemoveElementAt(mRegisteredHandles.IndexOf(handle, 0,
|
auto ix = mRegisteredHandles.IndexOf(handle, 0, Comparator());
|
||||||
Comparator()));
|
if (ix == mRegisteredHandles.NoIndex) {
|
||||||
|
MOZ_ASSERT(false);
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
mRegisteredHandles.RemoveElementAt(ix);
|
||||||
if (mRegisteredHandles.Length() && !mInShutdown) {
|
if (mRegisteredHandles.Length() && !mInShutdown) {
|
||||||
// Whenever constraints are removed, other parties may get closer to ideal.
|
// Whenever constraints are removed, other parties may get closer to ideal.
|
||||||
auto& first = mRegisteredHandles[0];
|
auto& first = mRegisteredHandles[0];
|
||||||
|
|
|
@ -29,10 +29,11 @@ NS_IMPL_ISUPPORTS0(MediaEngineRemoteVideoSource)
|
||||||
|
|
||||||
MediaEngineRemoteVideoSource::MediaEngineRemoteVideoSource(
|
MediaEngineRemoteVideoSource::MediaEngineRemoteVideoSource(
|
||||||
int aIndex, mozilla::camera::CaptureEngine aCapEngine,
|
int aIndex, mozilla::camera::CaptureEngine aCapEngine,
|
||||||
dom::MediaSourceEnum aMediaSource, const char* aMonitorName)
|
dom::MediaSourceEnum aMediaSource, bool aScary, const char* aMonitorName)
|
||||||
: MediaEngineCameraVideoSource(aIndex, aMonitorName),
|
: MediaEngineCameraVideoSource(aIndex, aMonitorName),
|
||||||
mMediaSource(aMediaSource),
|
mMediaSource(aMediaSource),
|
||||||
mCapEngine(aCapEngine)
|
mCapEngine(aCapEngine),
|
||||||
|
mScary(aScary)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other);
|
MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other);
|
||||||
mSettings.mWidth.Construct(0);
|
mSettings.mWidth.Construct(0);
|
||||||
|
|
|
@ -70,6 +70,7 @@ public:
|
||||||
// MediaEngineCameraVideoSource
|
// MediaEngineCameraVideoSource
|
||||||
MediaEngineRemoteVideoSource(int aIndex, mozilla::camera::CaptureEngine aCapEngine,
|
MediaEngineRemoteVideoSource(int aIndex, mozilla::camera::CaptureEngine aCapEngine,
|
||||||
dom::MediaSourceEnum aMediaSource,
|
dom::MediaSourceEnum aMediaSource,
|
||||||
|
bool aScary = false,
|
||||||
const char* aMonitorName = "RemoteVideo.Monitor");
|
const char* aMonitorName = "RemoteVideo.Monitor");
|
||||||
|
|
||||||
nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
|
nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||||
|
@ -103,6 +104,8 @@ public:
|
||||||
|
|
||||||
void Shutdown() override;
|
void Shutdown() override;
|
||||||
|
|
||||||
|
bool GetScary() const override { return mScary; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
~MediaEngineRemoteVideoSource() { }
|
~MediaEngineRemoteVideoSource() { }
|
||||||
|
|
||||||
|
@ -125,6 +128,7 @@ private:
|
||||||
|
|
||||||
// To only restart camera when needed, we keep track previous settings.
|
// To only restart camera when needed, we keep track previous settings.
|
||||||
webrtc::CaptureCapability mLastCapability;
|
webrtc::CaptureCapability mLastCapability;
|
||||||
|
bool mScary;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,11 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
|
||||||
|
|
||||||
void GetName(nsAString_internal&) const override;
|
void GetName(nsAString_internal&) const override;
|
||||||
void GetUUID(nsACString_internal&) const override;
|
void GetUUID(nsACString_internal&) const override;
|
||||||
|
|
||||||
|
bool GetScary() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
nsresult Allocate(const dom::MediaTrackConstraints &,
|
nsresult Allocate(const dom::MediaTrackConstraints &,
|
||||||
const mozilla::MediaEnginePrefs&,
|
const mozilla::MediaEnginePrefs&,
|
||||||
const nsString& aDeviceId,
|
const nsString& aDeviceId,
|
||||||
|
|
|
@ -157,6 +157,7 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
bool scaryKind = false; // flag sources with cross-origin exploit potential
|
||||||
|
|
||||||
switch (aMediaSource) {
|
switch (aMediaSource) {
|
||||||
case dom::MediaSourceEnum::Window:
|
case dom::MediaSourceEnum::Window:
|
||||||
|
@ -167,9 +168,11 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
||||||
break;
|
break;
|
||||||
case dom::MediaSourceEnum::Screen:
|
case dom::MediaSourceEnum::Screen:
|
||||||
capEngine = mozilla::camera::ScreenEngine;
|
capEngine = mozilla::camera::ScreenEngine;
|
||||||
|
scaryKind = true;
|
||||||
break;
|
break;
|
||||||
case dom::MediaSourceEnum::Browser:
|
case dom::MediaSourceEnum::Browser:
|
||||||
capEngine = mozilla::camera::BrowserEngine;
|
capEngine = mozilla::camera::BrowserEngine;
|
||||||
|
scaryKind = true;
|
||||||
break;
|
break;
|
||||||
case dom::MediaSourceEnum::Camera:
|
case dom::MediaSourceEnum::Camera:
|
||||||
capEngine = mozilla::camera::CameraEngine;
|
capEngine = mozilla::camera::CameraEngine;
|
||||||
|
@ -196,6 +199,7 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; i < num; i++) {
|
||||||
char deviceName[MediaEngineSource::kMaxDeviceNameLength];
|
char deviceName[MediaEngineSource::kMaxDeviceNameLength];
|
||||||
char uniqueId[MediaEngineSource::kMaxUniqueIdLength];
|
char uniqueId[MediaEngineSource::kMaxUniqueIdLength];
|
||||||
|
bool scaryWindow = false;
|
||||||
|
|
||||||
// paranoia
|
// paranoia
|
||||||
deviceName[0] = '\0';
|
deviceName[0] = '\0';
|
||||||
|
@ -215,6 +219,16 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
LOG((" Capture Device Index %d, Name %s", i, deviceName));
|
LOG((" Capture Device Index %d, Name %s", i, deviceName));
|
||||||
|
|
||||||
|
if (aMediaSource == dom::MediaSourceEnum::Window) {
|
||||||
|
// TODO: Detect firefox windows
|
||||||
|
//scaryWindow = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aMediaSource == dom::MediaSourceEnum::Application) {
|
||||||
|
// TODO: Detect firefox application windows
|
||||||
|
//scaryWindow = true;
|
||||||
|
}
|
||||||
|
|
||||||
webrtc::CaptureCapability cap;
|
webrtc::CaptureCapability cap;
|
||||||
int numCaps = mozilla::camera::GetChildAndCall(
|
int numCaps = mozilla::camera::GetChildAndCall(
|
||||||
&mozilla::camera::CamerasChild::NumberOfCapabilities,
|
&mozilla::camera::CamerasChild::NumberOfCapabilities,
|
||||||
|
@ -247,7 +261,8 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
||||||
static_cast<MediaEngineRemoteVideoSource*>(vSource.get())->Refresh(i);
|
static_cast<MediaEngineRemoteVideoSource*>(vSource.get())->Refresh(i);
|
||||||
aVSources->AppendElement(vSource.get());
|
aVSources->AppendElement(vSource.get());
|
||||||
} else {
|
} else {
|
||||||
vSource = new MediaEngineRemoteVideoSource(i, capEngine, aMediaSource);
|
vSource = new MediaEngineRemoteVideoSource(i, capEngine, aMediaSource,
|
||||||
|
scaryKind || scaryWindow);
|
||||||
mVideoSources.Put(uuid, vSource); // Hashtable takes ownership.
|
mVideoSources.Put(uuid, vSource); // Hashtable takes ownership.
|
||||||
aVSources->AppendElement(vSource);
|
aVSources->AppendElement(vSource);
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,8 +165,9 @@ BufferTextureData::CreateForYCbCrWithBufferSize(KnowsCompositor* aAllocator,
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasIntermediateBuffer = ComputeHasIntermediateBuffer(gfx::SurfaceFormat::YUV,
|
bool hasIntermediateBuffer = aAllocator ? ComputeHasIntermediateBuffer(gfx::SurfaceFormat::YUV,
|
||||||
aAllocator->GetCompositorBackendType());
|
aAllocator->GetCompositorBackendType())
|
||||||
|
: true;
|
||||||
|
|
||||||
// Initialize the metadata with something, even if it will have to be rewritten
|
// Initialize the metadata with something, even if it will have to be rewritten
|
||||||
// afterwards since we don't know the dimensions of the texture at this point.
|
// afterwards since we don't know the dimensions of the texture at this point.
|
||||||
|
@ -175,8 +176,8 @@ BufferTextureData::CreateForYCbCrWithBufferSize(KnowsCompositor* aAllocator,
|
||||||
aYUVColorSpace,
|
aYUVColorSpace,
|
||||||
hasIntermediateBuffer);
|
hasIntermediateBuffer);
|
||||||
|
|
||||||
return CreateInternal(aAllocator->GetTextureForwarder(), desc, gfx::BackendType::NONE, aBufferSize,
|
return CreateInternal(aAllocator ? aAllocator->GetTextureForwarder() : nullptr,
|
||||||
aTextureFlags);
|
desc, gfx::BackendType::NONE, aBufferSize, aTextureFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferTextureData*
|
BufferTextureData*
|
||||||
|
|
|
@ -32,8 +32,8 @@ function* test(testDriver) {
|
||||||
testDriver();
|
testDriver();
|
||||||
};
|
};
|
||||||
|
|
||||||
yield synthesizeNativeTouch(v, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT);
|
yield synthesizeNativeTouch(v, 25, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT);
|
||||||
yield synthesizeNativeTouch(v, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE);
|
yield synthesizeNativeTouch(v, 25, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE);
|
||||||
ok(v._gotTouchend, 'Touchend was received on video element');
|
ok(v._gotTouchend, 'Touchend was received on video element');
|
||||||
|
|
||||||
yield synthesizeNativeTouch(a, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT);
|
yield synthesizeNativeTouch(a, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT);
|
||||||
|
|
|
@ -28,20 +28,21 @@ window.addEventListener("load", () => {
|
||||||
target.style.animation = "Opacity0 100s 100s";
|
target.style.animation = "Opacity0 100s 100s";
|
||||||
|
|
||||||
// We need to wait for MozAfterPaint instead of requestAnimationFrame to
|
// We need to wait for MozAfterPaint instead of requestAnimationFrame to
|
||||||
// ensure the stacking context has been updated (removed) on the compositor
|
// ensure the stacking context has been updated on the compositor.
|
||||||
// before we snapshot.
|
|
||||||
window.addEventListener("MozAfterPaint", function firstPaint() {
|
window.addEventListener("MozAfterPaint", function firstPaint() {
|
||||||
window.removeEventListener("MozAfterPaint", firstPaint, false);
|
window.removeEventListener("MozAfterPaint", firstPaint, false);
|
||||||
// Here we have CSS animation on 100% opacity style element, so
|
// Here we have CSS animation on 100% opacity style element, so
|
||||||
// there should be a stacking context.
|
// there should be a stacking context.
|
||||||
|
|
||||||
target.style.animation = "";
|
target.style.animation = "";
|
||||||
window.addEventListener("MozAfterPaint", function secondPaint() {
|
|
||||||
window.removeEventListener("MozAfterPaint", secondPaint, false);
|
// This time we don't need to wait for MozAfterPaint because reftest tool
|
||||||
|
// will be received MozAferPaint event.
|
||||||
|
requestAnimationFrame(() => {
|
||||||
// Now we have only 100% opacity style, so we should not create any
|
// Now we have only 100% opacity style, so we should not create any
|
||||||
// stacking context.
|
// stacking context.
|
||||||
document.documentElement.classList.remove("reftest-wait");
|
document.documentElement.classList.remove("reftest-wait");
|
||||||
}, false);
|
});
|
||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -28,20 +28,20 @@ window.addEventListener("load", () => {
|
||||||
target.style.animation = "TransformNone 100s 100s";
|
target.style.animation = "TransformNone 100s 100s";
|
||||||
|
|
||||||
// We need to wait for MozAfterPaint instead of requestAnimationFrame to
|
// We need to wait for MozAfterPaint instead of requestAnimationFrame to
|
||||||
// ensure the stacking context has been updated (removed) on the compositor
|
// ensure the stacking context has been updated on the compositor.
|
||||||
// before we snapshot.
|
|
||||||
window.addEventListener("MozAfterPaint", function firstPaint() {
|
window.addEventListener("MozAfterPaint", function firstPaint() {
|
||||||
window.removeEventListener("MozAfterPaint", firstPaint, false);
|
window.removeEventListener("MozAfterPaint", firstPaint, false);
|
||||||
// Here we have CSS animation on transform:none style element, so
|
// Here we have CSS animation on transform:none style element, so
|
||||||
// there should be a stacking context.
|
// there should be a stacking context.
|
||||||
|
|
||||||
target.style.animation = "";
|
target.style.animation = "";
|
||||||
window.addEventListener("MozAfterPaint", function secondPaint() {
|
// This time we don't need to wait for MozAfterPaint because reftest tool
|
||||||
window.removeEventListener("MozAfterPaint", secondPaint, false);
|
// will be received MozAferPaint event.
|
||||||
|
requestAnimationFrame(() => {
|
||||||
// Now we have only transform:none style, so we should not create any
|
// Now we have only transform:none style, so we should not create any
|
||||||
// stacking context.
|
// stacking context.
|
||||||
document.documentElement.classList.remove("reftest-wait");
|
document.documentElement.classList.remove("reftest-wait");
|
||||||
}, false);
|
});
|
||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -40,50 +40,71 @@ nsCSSFilterInstance::nsCSSFilterInstance(const nsStyleFilter& aFilter,
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
nsCSSFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs)
|
nsCSSFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
|
||||||
|
bool aInputIsTainted)
|
||||||
{
|
{
|
||||||
FilterPrimitiveDescription descr;
|
FilterPrimitiveDescription descr;
|
||||||
nsresult result;
|
nsresult result;
|
||||||
|
|
||||||
switch(mFilter.GetType()) {
|
switch(mFilter.GetType()) {
|
||||||
case NS_STYLE_FILTER_BLUR:
|
case NS_STYLE_FILTER_BLUR:
|
||||||
descr = CreatePrimitiveDescription(PrimitiveType::GaussianBlur, aPrimitiveDescrs);
|
descr = CreatePrimitiveDescription(PrimitiveType::GaussianBlur,
|
||||||
|
aPrimitiveDescrs,
|
||||||
|
aInputIsTainted);
|
||||||
result = SetAttributesForBlur(descr);
|
result = SetAttributesForBlur(descr);
|
||||||
break;
|
break;
|
||||||
case NS_STYLE_FILTER_BRIGHTNESS:
|
case NS_STYLE_FILTER_BRIGHTNESS:
|
||||||
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer, aPrimitiveDescrs);
|
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer,
|
||||||
|
aPrimitiveDescrs,
|
||||||
|
aInputIsTainted);
|
||||||
result = SetAttributesForBrightness(descr);
|
result = SetAttributesForBrightness(descr);
|
||||||
break;
|
break;
|
||||||
case NS_STYLE_FILTER_CONTRAST:
|
case NS_STYLE_FILTER_CONTRAST:
|
||||||
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer, aPrimitiveDescrs);
|
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer,
|
||||||
|
aPrimitiveDescrs,
|
||||||
|
aInputIsTainted);
|
||||||
result = SetAttributesForContrast(descr);
|
result = SetAttributesForContrast(descr);
|
||||||
break;
|
break;
|
||||||
case NS_STYLE_FILTER_DROP_SHADOW:
|
case NS_STYLE_FILTER_DROP_SHADOW:
|
||||||
descr = CreatePrimitiveDescription(PrimitiveType::DropShadow, aPrimitiveDescrs);
|
descr = CreatePrimitiveDescription(PrimitiveType::DropShadow,
|
||||||
|
aPrimitiveDescrs,
|
||||||
|
aInputIsTainted);
|
||||||
result = SetAttributesForDropShadow(descr);
|
result = SetAttributesForDropShadow(descr);
|
||||||
break;
|
break;
|
||||||
case NS_STYLE_FILTER_GRAYSCALE:
|
case NS_STYLE_FILTER_GRAYSCALE:
|
||||||
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix, aPrimitiveDescrs);
|
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix,
|
||||||
|
aPrimitiveDescrs,
|
||||||
|
aInputIsTainted);
|
||||||
result = SetAttributesForGrayscale(descr);
|
result = SetAttributesForGrayscale(descr);
|
||||||
break;
|
break;
|
||||||
case NS_STYLE_FILTER_HUE_ROTATE:
|
case NS_STYLE_FILTER_HUE_ROTATE:
|
||||||
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix, aPrimitiveDescrs);
|
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix,
|
||||||
|
aPrimitiveDescrs,
|
||||||
|
aInputIsTainted);
|
||||||
result = SetAttributesForHueRotate(descr);
|
result = SetAttributesForHueRotate(descr);
|
||||||
break;
|
break;
|
||||||
case NS_STYLE_FILTER_INVERT:
|
case NS_STYLE_FILTER_INVERT:
|
||||||
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer, aPrimitiveDescrs);
|
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer,
|
||||||
|
aPrimitiveDescrs,
|
||||||
|
aInputIsTainted);
|
||||||
result = SetAttributesForInvert(descr);
|
result = SetAttributesForInvert(descr);
|
||||||
break;
|
break;
|
||||||
case NS_STYLE_FILTER_OPACITY:
|
case NS_STYLE_FILTER_OPACITY:
|
||||||
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer, aPrimitiveDescrs);
|
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer,
|
||||||
|
aPrimitiveDescrs,
|
||||||
|
aInputIsTainted);
|
||||||
result = SetAttributesForOpacity(descr);
|
result = SetAttributesForOpacity(descr);
|
||||||
break;
|
break;
|
||||||
case NS_STYLE_FILTER_SATURATE:
|
case NS_STYLE_FILTER_SATURATE:
|
||||||
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix, aPrimitiveDescrs);
|
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix,
|
||||||
|
aPrimitiveDescrs,
|
||||||
|
aInputIsTainted);
|
||||||
result = SetAttributesForSaturate(descr);
|
result = SetAttributesForSaturate(descr);
|
||||||
break;
|
break;
|
||||||
case NS_STYLE_FILTER_SEPIA:
|
case NS_STYLE_FILTER_SEPIA:
|
||||||
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix, aPrimitiveDescrs);
|
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix,
|
||||||
|
aPrimitiveDescrs,
|
||||||
|
aInputIsTainted);
|
||||||
result = SetAttributesForSepia(descr);
|
result = SetAttributesForSepia(descr);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -106,11 +127,12 @@ nsCSSFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrim
|
||||||
|
|
||||||
FilterPrimitiveDescription
|
FilterPrimitiveDescription
|
||||||
nsCSSFilterInstance::CreatePrimitiveDescription(PrimitiveType aType,
|
nsCSSFilterInstance::CreatePrimitiveDescription(PrimitiveType aType,
|
||||||
const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) {
|
const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
|
||||||
|
bool aInputIsTainted) {
|
||||||
FilterPrimitiveDescription descr(aType);
|
FilterPrimitiveDescription descr(aType);
|
||||||
int32_t inputIndex = GetLastResultIndex(aPrimitiveDescrs);
|
int32_t inputIndex = GetLastResultIndex(aPrimitiveDescrs);
|
||||||
descr.SetInputPrimitive(0, inputIndex);
|
descr.SetInputPrimitive(0, inputIndex);
|
||||||
descr.SetIsTainted(inputIndex < 0 ? true : aPrimitiveDescrs[inputIndex].IsTainted());
|
descr.SetIsTainted(inputIndex < 0 ? aInputIsTainted : aPrimitiveDescrs[inputIndex].IsTainted());
|
||||||
descr.SetInputColorSpace(0, ColorSpace::SRGB);
|
descr.SetInputColorSpace(0, ColorSpace::SRGB);
|
||||||
descr.SetOutputColorSpace(ColorSpace::SRGB);
|
descr.SetOutputColorSpace(ColorSpace::SRGB);
|
||||||
return descr;
|
return descr;
|
||||||
|
|
|
@ -50,15 +50,25 @@ public:
|
||||||
* Creates at least one new FilterPrimitiveDescription based on the filter
|
* Creates at least one new FilterPrimitiveDescription based on the filter
|
||||||
* from the style system. Appends the new FilterPrimitiveDescription(s) to the
|
* from the style system. Appends the new FilterPrimitiveDescription(s) to the
|
||||||
* aPrimitiveDescrs list.
|
* aPrimitiveDescrs list.
|
||||||
|
* aInputIsTainted describes whether the input to this filter is tainted, i.e.
|
||||||
|
* whether it contains security-sensitive content. This is needed to propagate
|
||||||
|
* taintedness to the FilterPrimitive that take tainted inputs. Something being
|
||||||
|
* tainted means that it contains security sensitive content.
|
||||||
|
* The input to this filter is the previous filter's output, i.e. the last
|
||||||
|
* element in aPrimitiveDescrs, or the SourceGraphic input if this is the first
|
||||||
|
* filter in the filter chain.
|
||||||
*/
|
*/
|
||||||
nsresult BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs);
|
nsresult BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
|
||||||
|
bool aInputIsTainted);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Returns a new FilterPrimitiveDescription with its basic properties set up.
|
* Returns a new FilterPrimitiveDescription with its basic properties set up.
|
||||||
|
* See the comment above BuildPrimitives for the meaning of aInputIsTainted.
|
||||||
*/
|
*/
|
||||||
FilterPrimitiveDescription CreatePrimitiveDescription(PrimitiveType aType,
|
FilterPrimitiveDescription CreatePrimitiveDescription(PrimitiveType aType,
|
||||||
const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs);
|
const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
|
||||||
|
bool aInputIsTainted);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets aDescr's attributes using the style info in mFilter.
|
* Sets aDescr's attributes using the style info in mFilter.
|
||||||
|
|
|
@ -32,14 +32,15 @@ using namespace mozilla::gfx;
|
||||||
FilterDescription
|
FilterDescription
|
||||||
nsFilterInstance::GetFilterDescription(nsIContent* aFilteredElement,
|
nsFilterInstance::GetFilterDescription(nsIContent* aFilteredElement,
|
||||||
const nsTArray<nsStyleFilter>& aFilterChain,
|
const nsTArray<nsStyleFilter>& aFilterChain,
|
||||||
|
bool aFilterInputIsTainted,
|
||||||
const UserSpaceMetrics& aMetrics,
|
const UserSpaceMetrics& aMetrics,
|
||||||
const gfxRect& aBBox,
|
const gfxRect& aBBox,
|
||||||
nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages)
|
nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages)
|
||||||
{
|
{
|
||||||
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
||||||
nsFilterInstance instance(nullptr, aFilteredElement, aMetrics,
|
nsFilterInstance instance(nullptr, aFilteredElement, aMetrics,
|
||||||
aFilterChain, nullptr, unused,
|
aFilterChain, aFilterInputIsTainted, nullptr,
|
||||||
nullptr, nullptr, nullptr, &aBBox);
|
unused, nullptr, nullptr, nullptr, &aBBox);
|
||||||
if (!instance.IsInitialized()) {
|
if (!instance.IsInitialized()) {
|
||||||
return FilterDescription();
|
return FilterDescription();
|
||||||
}
|
}
|
||||||
|
@ -65,9 +66,11 @@ nsFilterInstance::PaintFilteredFrame(nsIFrame *aFilteredFrame,
|
||||||
{
|
{
|
||||||
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
||||||
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
||||||
|
// Hardcode InputIsTainted to true because we don't want JS to be able to
|
||||||
|
// read the rendered contents of aFilteredFrame.
|
||||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
||||||
filterChain, aPaintCallback, aTransform,
|
filterChain, /* InputIsTainted */ true, aPaintCallback,
|
||||||
aDirtyArea, nullptr, nullptr, nullptr);
|
aTransform, aDirtyArea, nullptr, nullptr, nullptr);
|
||||||
if (!instance.IsInitialized()) {
|
if (!instance.IsInitialized()) {
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
@ -85,9 +88,11 @@ nsFilterInstance::GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
|
||||||
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
||||||
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
||||||
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
||||||
|
// Hardcode InputIsTainted to true because we don't want JS to be able to
|
||||||
|
// read the rendered contents of aFilteredFrame.
|
||||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
||||||
filterChain, nullptr, unused, nullptr,
|
filterChain, /* InputIsTainted */ true, nullptr, unused,
|
||||||
&aPreFilterDirtyRegion);
|
nullptr, &aPreFilterDirtyRegion);
|
||||||
if (!instance.IsInitialized()) {
|
if (!instance.IsInitialized()) {
|
||||||
return nsRegion();
|
return nsRegion();
|
||||||
}
|
}
|
||||||
|
@ -105,8 +110,10 @@ nsFilterInstance::GetPreFilterNeededArea(nsIFrame *aFilteredFrame,
|
||||||
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
||||||
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
||||||
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
||||||
|
// Hardcode InputIsTainted to true because we don't want JS to be able to
|
||||||
|
// read the rendered contents of aFilteredFrame.
|
||||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
||||||
filterChain, nullptr, unused,
|
filterChain, /* InputIsTainted */ true, nullptr, unused,
|
||||||
&aPostFilterDirtyRegion);
|
&aPostFilterDirtyRegion);
|
||||||
if (!instance.IsInitialized()) {
|
if (!instance.IsInitialized()) {
|
||||||
return nsRect();
|
return nsRect();
|
||||||
|
@ -136,9 +143,11 @@ nsFilterInstance::GetPostFilterBounds(nsIFrame *aFilteredFrame,
|
||||||
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
||||||
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
||||||
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
||||||
|
// Hardcode InputIsTainted to true because we don't want JS to be able to
|
||||||
|
// read the rendered contents of aFilteredFrame.
|
||||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
||||||
filterChain, nullptr, unused, nullptr,
|
filterChain, /* InputIsTainted */ true, nullptr, unused,
|
||||||
preFilterRegionPtr, aPreFilterBounds,
|
nullptr, preFilterRegionPtr, aPreFilterBounds,
|
||||||
aOverrideBBox);
|
aOverrideBBox);
|
||||||
if (!instance.IsInitialized()) {
|
if (!instance.IsInitialized()) {
|
||||||
return nsRect();
|
return nsRect();
|
||||||
|
@ -151,6 +160,7 @@ nsFilterInstance::nsFilterInstance(nsIFrame *aTargetFrame,
|
||||||
nsIContent* aTargetContent,
|
nsIContent* aTargetContent,
|
||||||
const UserSpaceMetrics& aMetrics,
|
const UserSpaceMetrics& aMetrics,
|
||||||
const nsTArray<nsStyleFilter>& aFilterChain,
|
const nsTArray<nsStyleFilter>& aFilterChain,
|
||||||
|
bool aFilterInputIsTainted,
|
||||||
nsSVGFilterPaintCallback *aPaintCallback,
|
nsSVGFilterPaintCallback *aPaintCallback,
|
||||||
const gfxMatrix& aPaintTransform,
|
const gfxMatrix& aPaintTransform,
|
||||||
const nsRegion *aPostFilterDirtyRegion,
|
const nsRegion *aPostFilterDirtyRegion,
|
||||||
|
@ -213,7 +223,7 @@ nsFilterInstance::nsFilterInstance(nsIFrame *aTargetFrame,
|
||||||
mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds);
|
mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds);
|
||||||
|
|
||||||
// Build the filter graph.
|
// Build the filter graph.
|
||||||
rv = BuildPrimitives(aFilterChain, aTargetFrame);
|
rv = BuildPrimitives(aFilterChain, aTargetFrame, aFilterInputIsTainted);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -274,13 +284,17 @@ nsFilterInstance::FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
nsFilterInstance::BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain,
|
nsFilterInstance::BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain,
|
||||||
nsIFrame* aTargetFrame)
|
nsIFrame* aTargetFrame,
|
||||||
|
bool aFilterInputIsTainted)
|
||||||
{
|
{
|
||||||
NS_ASSERTION(!mPrimitiveDescriptions.Length(),
|
NS_ASSERTION(!mPrimitiveDescriptions.Length(),
|
||||||
"expected to start building primitives from scratch");
|
"expected to start building primitives from scratch");
|
||||||
|
|
||||||
for (uint32_t i = 0; i < aFilterChain.Length(); i++) {
|
for (uint32_t i = 0; i < aFilterChain.Length(); i++) {
|
||||||
nsresult rv = BuildPrimitivesForFilter(aFilterChain[i], aTargetFrame);
|
bool inputIsTainted =
|
||||||
|
mPrimitiveDescriptions.IsEmpty() ? aFilterInputIsTainted :
|
||||||
|
mPrimitiveDescriptions.LastElement().IsTainted();
|
||||||
|
nsresult rv = BuildPrimitivesForFilter(aFilterChain[i], aTargetFrame, inputIsTainted);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
@ -293,7 +307,8 @@ nsFilterInstance::BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain,
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
nsFilterInstance::BuildPrimitivesForFilter(const nsStyleFilter& aFilter,
|
nsFilterInstance::BuildPrimitivesForFilter(const nsStyleFilter& aFilter,
|
||||||
nsIFrame* aTargetFrame)
|
nsIFrame* aTargetFrame,
|
||||||
|
bool aInputIsTainted)
|
||||||
{
|
{
|
||||||
NS_ASSERTION(mUserSpaceToFilterSpaceScale.width > 0.0f &&
|
NS_ASSERTION(mUserSpaceToFilterSpaceScale.width > 0.0f &&
|
||||||
mFilterSpaceToUserSpaceScale.height > 0.0f,
|
mFilterSpaceToUserSpaceScale.height > 0.0f,
|
||||||
|
@ -310,7 +325,8 @@ nsFilterInstance::BuildPrimitivesForFilter(const nsStyleFilter& aFilter,
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return svgFilterInstance.BuildPrimitives(mPrimitiveDescriptions, mInputImages);
|
return svgFilterInstance.BuildPrimitives(mPrimitiveDescriptions, mInputImages,
|
||||||
|
aInputIsTainted);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build primitives for a CSS filter.
|
// Build primitives for a CSS filter.
|
||||||
|
@ -323,7 +339,7 @@ nsFilterInstance::BuildPrimitivesForFilter(const nsStyleFilter& aFilter,
|
||||||
nsCSSFilterInstance cssFilterInstance(aFilter, shadowFallbackColor,
|
nsCSSFilterInstance cssFilterInstance(aFilter, shadowFallbackColor,
|
||||||
mTargetBounds,
|
mTargetBounds,
|
||||||
mFrameSpaceInCSSPxToFilterSpaceTransform);
|
mFrameSpaceInCSSPxToFilterSpaceTransform);
|
||||||
return cssFilterInstance.BuildPrimitives(mPrimitiveDescriptions);
|
return cssFilterInstance.BuildPrimitives(mPrimitiveDescriptions, aInputIsTainted);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -60,12 +60,19 @@ public:
|
||||||
/**
|
/**
|
||||||
* Create a FilterDescription for the supplied filter. All coordinates in
|
* Create a FilterDescription for the supplied filter. All coordinates in
|
||||||
* the description are in filter space.
|
* the description are in filter space.
|
||||||
|
* @param aFilterInputIsTainted Describes whether the SourceImage / SourceAlpha
|
||||||
|
* input is tainted. This affects whether feDisplacementMap will respect
|
||||||
|
* the filter input as its map input, and it affects the IsTainted() state
|
||||||
|
* on the filter primitives in the FilterDescription. "Tainted" is a term
|
||||||
|
* from the filters spec and means security-sensitive content, i.e. pixels
|
||||||
|
* that JS should not be able to read in any way.
|
||||||
* @param aOutAdditionalImages Will contain additional images needed to
|
* @param aOutAdditionalImages Will contain additional images needed to
|
||||||
* render the filter (from feImage primitives).
|
* render the filter (from feImage primitives).
|
||||||
* @return A FilterDescription describing the filter.
|
* @return A FilterDescription describing the filter.
|
||||||
*/
|
*/
|
||||||
static FilterDescription GetFilterDescription(nsIContent* aFilteredElement,
|
static FilterDescription GetFilterDescription(nsIContent* aFilteredElement,
|
||||||
const nsTArray<nsStyleFilter>& aFilterChain,
|
const nsTArray<nsStyleFilter>& aFilterChain,
|
||||||
|
bool aFilterInputIsTainted,
|
||||||
const UserSpaceMetrics& aMetrics,
|
const UserSpaceMetrics& aMetrics,
|
||||||
const gfxRect& aBBox,
|
const gfxRect& aBBox,
|
||||||
nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages);
|
nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages);
|
||||||
|
@ -118,6 +125,9 @@ public:
|
||||||
* @param aTargetContent The filtered element itself.
|
* @param aTargetContent The filtered element itself.
|
||||||
* @param aMetrics The metrics to resolve SVG lengths against.
|
* @param aMetrics The metrics to resolve SVG lengths against.
|
||||||
* @param aFilterChain The list of filters to apply.
|
* @param aFilterChain The list of filters to apply.
|
||||||
|
* @param aFilterInputIsTainted Describes whether the SourceImage / SourceAlpha
|
||||||
|
* input is tainted. This affects whether feDisplacementMap will respect
|
||||||
|
* the filter input as its map input.
|
||||||
* @param aPaintCallback [optional] The callback that Render() should use to
|
* @param aPaintCallback [optional] The callback that Render() should use to
|
||||||
* paint. Only required if you will call Render().
|
* paint. Only required if you will call Render().
|
||||||
* @param aPaintTransform The transform to apply to convert to
|
* @param aPaintTransform The transform to apply to convert to
|
||||||
|
@ -137,6 +147,7 @@ public:
|
||||||
nsIContent* aTargetContent,
|
nsIContent* aTargetContent,
|
||||||
const UserSpaceMetrics& aMetrics,
|
const UserSpaceMetrics& aMetrics,
|
||||||
const nsTArray<nsStyleFilter>& aFilterChain,
|
const nsTArray<nsStyleFilter>& aFilterChain,
|
||||||
|
bool aFilterInputIsTainted,
|
||||||
nsSVGFilterPaintCallback *aPaintCallback,
|
nsSVGFilterPaintCallback *aPaintCallback,
|
||||||
const gfxMatrix& aPaintTransform,
|
const gfxMatrix& aPaintTransform,
|
||||||
const nsRegion *aPostFilterDirtyRegion = nullptr,
|
const nsRegion *aPostFilterDirtyRegion = nullptr,
|
||||||
|
@ -236,18 +247,22 @@ private:
|
||||||
/**
|
/**
|
||||||
* Build the list of FilterPrimitiveDescriptions that describes the filter's
|
* Build the list of FilterPrimitiveDescriptions that describes the filter's
|
||||||
* filter primitives and their connections. This populates
|
* filter primitives and their connections. This populates
|
||||||
* mPrimitiveDescriptions and mInputImages.
|
* mPrimitiveDescriptions and mInputImages. aFilterInputIsTainted describes
|
||||||
|
* whether the SourceGraphic is tainted.
|
||||||
*/
|
*/
|
||||||
nsresult BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain,
|
nsresult BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain,
|
||||||
nsIFrame* aTargetFrame);
|
nsIFrame* aTargetFrame,
|
||||||
|
bool aFilterInputIsTainted);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add to the list of FilterPrimitiveDescriptions for a particular SVG
|
* Add to the list of FilterPrimitiveDescriptions for a particular SVG
|
||||||
* reference filter or CSS filter. This populates mPrimitiveDescrs and
|
* reference filter or CSS filter. This populates mPrimitiveDescriptions and
|
||||||
* mInputImages.
|
* mInputImages. aInputIsTainted describes whether the input to aFilter is
|
||||||
|
* tainted.
|
||||||
*/
|
*/
|
||||||
nsresult BuildPrimitivesForFilter(const nsStyleFilter& aFilter,
|
nsresult BuildPrimitivesForFilter(const nsStyleFilter& aFilter,
|
||||||
nsIFrame* aTargetFrame);
|
nsIFrame* aTargetFrame,
|
||||||
|
bool aInputIsTainted);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the filter space bounds of the areas that we actually *need* from
|
* Computes the filter space bounds of the areas that we actually *need* from
|
||||||
|
|
|
@ -366,23 +366,10 @@ nsSVGFilterInstance::GetSourceIndices(nsSVGFE* aPrimitiveElement,
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
IsFilterInputTainted(nsIContent* aElement)
|
|
||||||
{
|
|
||||||
// When the filter is applied during canvas drawing, we might be allowed to
|
|
||||||
// read from the canvas.
|
|
||||||
if (HTMLCanvasElement* canvas =
|
|
||||||
HTMLCanvasElement::FromContentOrNull(aElement)) {
|
|
||||||
return canvas->IsWriteOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always treat normal filtered elements as tainted.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
|
nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
|
||||||
nsTArray<RefPtr<SourceSurface>>& aInputImages)
|
nsTArray<RefPtr<SourceSurface>>& aInputImages,
|
||||||
|
bool aInputIsTainted)
|
||||||
{
|
{
|
||||||
mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs);
|
mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs);
|
||||||
|
|
||||||
|
@ -410,8 +397,6 @@ nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrim
|
||||||
// The principal that we check principals of any loaded images against.
|
// The principal that we check principals of any loaded images against.
|
||||||
nsCOMPtr<nsIPrincipal> principal = mTargetContent->NodePrincipal();
|
nsCOMPtr<nsIPrincipal> principal = mTargetContent->NodePrincipal();
|
||||||
|
|
||||||
bool filterInputIsTainted = IsFilterInputTainted(mTargetContent);
|
|
||||||
|
|
||||||
for (uint32_t primitiveElementIndex = 0;
|
for (uint32_t primitiveElementIndex = 0;
|
||||||
primitiveElementIndex < primitives.Length();
|
primitiveElementIndex < primitives.Length();
|
||||||
++primitiveElementIndex) {
|
++primitiveElementIndex) {
|
||||||
|
@ -427,7 +412,7 @@ nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrim
|
||||||
ComputeFilterPrimitiveSubregion(filter, aPrimitiveDescrs, sourceIndices);
|
ComputeFilterPrimitiveSubregion(filter, aPrimitiveDescrs, sourceIndices);
|
||||||
|
|
||||||
nsTArray<bool> sourcesAreTainted;
|
nsTArray<bool> sourcesAreTainted;
|
||||||
GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, filterInputIsTainted, sourcesAreTainted);
|
GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, aInputIsTainted, sourcesAreTainted);
|
||||||
|
|
||||||
FilterPrimitiveDescription descr =
|
FilterPrimitiveDescription descr =
|
||||||
filter->GetPrimitiveDescription(this, primitiveSubregion, sourcesAreTainted, aInputImages);
|
filter->GetPrimitiveDescription(this, primitiveSubregion, sourcesAreTainted, aInputImages);
|
||||||
|
|
|
@ -98,9 +98,17 @@ public:
|
||||||
* FilterPrimitiveDescription for each one. Appends the new
|
* FilterPrimitiveDescription for each one. Appends the new
|
||||||
* FilterPrimitiveDescription(s) to the aPrimitiveDescrs list. Also, appends
|
* FilterPrimitiveDescription(s) to the aPrimitiveDescrs list. Also, appends
|
||||||
* new images from feImage filter primitive elements to the aInputImages list.
|
* new images from feImage filter primitive elements to the aInputImages list.
|
||||||
|
* aInputIsTainted describes whether the input to this filter is tainted, i.e.
|
||||||
|
* whether it contains security-sensitive content. This is needed to propagate
|
||||||
|
* taintedness to the FilterPrimitive that take tainted inputs. Something being
|
||||||
|
* tainted means that it contains security sensitive content.
|
||||||
|
* The input to this filter is the previous filter's output, i.e. the last
|
||||||
|
* element in aPrimitiveDescrs, or the SourceGraphic input if this is the first
|
||||||
|
* filter in the filter chain.
|
||||||
*/
|
*/
|
||||||
nsresult BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
|
nsresult BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
|
||||||
nsTArray<RefPtr<SourceSurface>>& aInputImages);
|
nsTArray<RefPtr<SourceSurface>>& aInputImages,
|
||||||
|
bool aInputIsTainted);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the user specified "filter region", in the filtered element's user
|
* Returns the user specified "filter region", in the filtered element's user
|
||||||
|
|
|
@ -51,7 +51,7 @@ win32/pgo:
|
||||||
worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
|
worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
|
||||||
worker:
|
worker:
|
||||||
implementation: generic-worker
|
implementation: generic-worker
|
||||||
max-run-time: 7200
|
max-run-time: 9000
|
||||||
run:
|
run:
|
||||||
using: mozharness
|
using: mozharness
|
||||||
options: [enable-pgo]
|
options: [enable-pgo]
|
||||||
|
@ -112,7 +112,7 @@ win64/pgo:
|
||||||
worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
|
worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
|
||||||
worker:
|
worker:
|
||||||
implementation: generic-worker
|
implementation: generic-worker
|
||||||
max-run-time: 7200
|
max-run-time: 9000
|
||||||
run:
|
run:
|
||||||
using: mozharness
|
using: mozharness
|
||||||
options: [enable-pgo]
|
options: [enable-pgo]
|
||||||
|
|
|
@ -211,10 +211,17 @@ def mozharness_on_windows(config, job, taskdesc):
|
||||||
mh_command.append(r'--skip-buildbot-actions --work-dir %cd:Z:=z:%\build')
|
mh_command.append(r'--skip-buildbot-actions --work-dir %cd:Z:=z:%\build')
|
||||||
for option in run.get('options', []):
|
for option in run.get('options', []):
|
||||||
mh_command.append('--' + option)
|
mh_command.append('--' + option)
|
||||||
hg = r'c:\Program Files\Mercurial\hg.exe'
|
|
||||||
|
hg_command = ['"c:\\Program Files\\Mercurial\\hg.exe"']
|
||||||
|
hg_command.append('robustcheckout')
|
||||||
|
hg_command.extend(['--sharebase', 'y:\\hg-shared'])
|
||||||
|
hg_command.append('--purge')
|
||||||
|
hg_command.extend(['--upstream', 'https://hg.mozilla.org/mozilla-unified'])
|
||||||
|
hg_command.extend(['--revision', env['GECKO_HEAD_REV']])
|
||||||
|
hg_command.append(env['GECKO_HEAD_REPOSITORY'])
|
||||||
|
hg_command.append('.\\build\\src')
|
||||||
|
|
||||||
worker['command'] = [
|
worker['command'] = [
|
||||||
r'mkdir .\build\src',
|
' '.join(hg_command),
|
||||||
r'"{}" share c:\builds\hg-shared\mozilla-central .\build\src'.format(hg),
|
' '.join(mh_command)
|
||||||
r'"{}" pull -u -R .\build\src --rev %GECKO_HEAD_REV% %GECKO_HEAD_REPOSITORY%'.format(hg),
|
|
||||||
' '.join(mh_command),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -36,12 +36,4 @@ def setup_task(config, tasks):
|
||||||
'mount-point': "/home/worker/.tc-vcs",
|
'mount-point': "/home/worker/.tc-vcs",
|
||||||
}]
|
}]
|
||||||
|
|
||||||
if int(config.params['level']) > 1:
|
|
||||||
task['worker'].setdefault('caches', []).append({
|
|
||||||
'type': 'persistent',
|
|
||||||
'name': 'level-{}-{}-test-workspace'.format(
|
|
||||||
config.params['level'], config.params['project']),
|
|
||||||
'mount-point': "/home/worker/workspace",
|
|
||||||
})
|
|
||||||
|
|
||||||
yield task
|
yield task
|
||||||
|
|
|
@ -30,6 +30,10 @@ import urllib2
|
||||||
|
|
||||||
|
|
||||||
FINGERPRINT_URL = 'http://taskcluster/secrets/v1/secret/project/taskcluster/gecko/hgfingerprint'
|
FINGERPRINT_URL = 'http://taskcluster/secrets/v1/secret/project/taskcluster/gecko/hgfingerprint'
|
||||||
|
FALLBACK_FINGERPRINT = {
|
||||||
|
'fingerprints':
|
||||||
|
"sha256:8e:ad:f7:6a:eb:44:06:15:ed:f3:e4:69:a6:64:60:37:2d:ff:98:88:37"
|
||||||
|
":bf:d7:b8:40:84:01:48:9c:26:ce:d9"}
|
||||||
|
|
||||||
|
|
||||||
def print_line(prefix, m):
|
def print_line(prefix, m):
|
||||||
|
@ -95,15 +99,17 @@ def vcs_checkout(source_repo, dest, base_repo=None, revision=None, branch=None):
|
||||||
FINGERPRINT_URL)
|
FINGERPRINT_URL)
|
||||||
res = urllib2.urlopen(FINGERPRINT_URL, timeout=10)
|
res = urllib2.urlopen(FINGERPRINT_URL, timeout=10)
|
||||||
secret = res.read()
|
secret = res.read()
|
||||||
except urllib2.URLError as e:
|
try:
|
||||||
print('error retrieving hg fingerprint: %s' % e)
|
secret = json.loads(secret, encoding='utf-8')
|
||||||
sys.exit(1)
|
except ValueError:
|
||||||
|
print_line(b'vcs', 'invalid JSON in hg fingerprint secret')
|
||||||
try:
|
sys.exit(1)
|
||||||
secret = json.loads(secret, encoding='utf-8')
|
except urllib2.URLError:
|
||||||
except ValueError:
|
print_line(b'vcs', 'Unable to retrieve current hg.mozilla.org fingerprint'
|
||||||
print('invalid JSON in hg fingerprint secret')
|
'using the secret service, using fallback instead.')
|
||||||
sys.exit(1)
|
# XXX This fingerprint will not be accurate if running on an old
|
||||||
|
# revision after the server fingerprint has changed.
|
||||||
|
secret = {'secret': FALLBACK_FINGERPRINT}
|
||||||
|
|
||||||
hgmo_fingerprint = secret['secret']['fingerprints'].encode('ascii')
|
hgmo_fingerprint = secret['secret']['fingerprints'].encode('ascii')
|
||||||
|
|
||||||
|
@ -193,6 +199,7 @@ def main(args):
|
||||||
gids = [g.gr_gid for g in grp.getgrall() if args.group in g.gr_mem]
|
gids = [g.gr_gid for g in grp.getgrall() if args.group in g.gr_mem]
|
||||||
|
|
||||||
wanted_dir_mode = stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
|
wanted_dir_mode = stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
|
||||||
|
|
||||||
def set_dir_permissions(path, uid, gid):
|
def set_dir_permissions(path, uid, gid):
|
||||||
st = os.lstat(path)
|
st = os.lstat(path)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import os
|
||||||
config = {
|
config = {
|
||||||
"locales_file": "src/browser/locales/all-locales",
|
"locales_file": "src/browser/locales/all-locales",
|
||||||
"tools_repo": "https://hg.mozilla.org/build/tools",
|
"tools_repo": "https://hg.mozilla.org/build/tools",
|
||||||
"mozconfig": "src/browser/config/mozconfigs/linux64/l10n-mozconfig",
|
"mozconfig": "src/browser/config/mozconfigs/linux32/l10n-mozconfig",
|
||||||
"bootstrap_env": {
|
"bootstrap_env": {
|
||||||
"NO_MERCURIAL_SETUP_CHECK": "1",
|
"NO_MERCURIAL_SETUP_CHECK": "1",
|
||||||
"MOZ_OBJDIR": "obj-l10n",
|
"MOZ_OBJDIR": "obj-l10n",
|
||||||
|
|
|
@ -2188,16 +2188,10 @@ TextInputHandler::InsertText(NSAttributedString* aAttrString,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str.Length() != 1 || IsIMEComposing()) {
|
// If this is not caused by pressing a key or there is a composition, let's
|
||||||
|
// insert the text as committing a composition.
|
||||||
|
if (!currentKeyEvent || IsIMEComposing()) {
|
||||||
InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
|
InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
|
||||||
// For now, consume keypress events when we dispatch the string with a
|
|
||||||
// composition for preventing to dispatch keypress events later.
|
|
||||||
// TODO: When there is a currentKeyEvent, we should dispatch keypress
|
|
||||||
// events even if the length of the string is over 1.
|
|
||||||
if (currentKeyEvent) {
|
|
||||||
currentKeyEvent->mKeyPressHandled = true;
|
|
||||||
currentKeyEvent->mKeyPressDispatched = true;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1906,6 +1906,21 @@ function* runKeyEventTests()
|
||||||
yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_I,
|
yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_I,
|
||||||
modifiers:{metaKey:1, altKey:1, shiftKey:1}, chars:"\u02C6", unmodifiedChars:"C"},
|
modifiers:{metaKey:1, altKey:1, shiftKey:1}, chars:"\u02C6", unmodifiedChars:"C"},
|
||||||
"\u02C6", "KeyI", nsIDOMKeyEvent.DOM_VK_I, "\u02C6", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
|
"\u02C6", "KeyI", nsIDOMKeyEvent.DOM_VK_I, "\u02C6", SHOULD_DELIVER_KEYDOWN_KEYPRESS, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
|
||||||
|
|
||||||
|
// Arabic - PC keyboard layout inputs 2 or more characters with some key.
|
||||||
|
yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_G,
|
||||||
|
modifiers:{shiftKey:1}, chars:"\u0644\u0623", unmodifiedChars:"\u0644\u0623"},
|
||||||
|
["\u0644\u0623", "\u0644", "\u0623", "\u0644\u0623"], "KeyG", nsIDOMKeyEvent.DOM_VK_G, "\u0644\u0623", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
|
||||||
|
yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_T,
|
||||||
|
modifiers:{shiftKey:1}, chars:"\u0644\u0625", unmodifiedChars:"\u0644\u0625"},
|
||||||
|
["\u0644\u0625", "\u0644", "\u0625", "\u0644\u0625"], "KeyT", nsIDOMKeyEvent.DOM_VK_T, "\u0644\u0625", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
|
||||||
|
yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_B,
|
||||||
|
modifiers:{shiftKey:1}, chars:"\u0644\u0622", unmodifiedChars:"\u0644\u0622"},
|
||||||
|
["\u0644\u0622", "\u0644", "\u0622", "\u0644\u0622"], "KeyB", nsIDOMKeyEvent.DOM_VK_B, "\u0644\u0622", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
|
||||||
|
yield testKey({layout:KEYBOARD_LAYOUT_ARABIC_PC, keyCode:MAC_VK_ANSI_B,
|
||||||
|
modifiers:{}, chars:"\u0644\u0627", unmodifiedChars:"\u0644\u0627"},
|
||||||
|
["\u0644\u0627", "\u0644", "\u0627", "\u0644\u0627"], "KeyB", nsIDOMKeyEvent.DOM_VK_B, "\u0644\u0627", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4197,11 +4212,37 @@ function* runTextInputTests()
|
||||||
textbox.value = "";
|
textbox.value = "";
|
||||||
textbox.focus();
|
textbox.focus();
|
||||||
|
|
||||||
|
var name = eventToString(aEvent);
|
||||||
|
|
||||||
|
// Check if the text comes with keypress events rather than composition events.
|
||||||
|
var keypress = 0;
|
||||||
|
function onKeypress(aEvent) {
|
||||||
|
keypress++;
|
||||||
|
if (keypress == 1 && aExpectText == "") {
|
||||||
|
if (!aEvent.ctrlKey && !aEvent.altKey) {
|
||||||
|
is(aEvent.charCode, 0, name + ", the charCode value should be 0 when it shouldn't cause inputting text");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (keypress > aExpectText.length) {
|
||||||
|
ok(false, name + " causes too many keypress events");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
is(aEvent.key, aExpectText[keypress - 1],
|
||||||
|
name + ", " + keypress + "th keypress event's key value should be '" + aExpectText[keypress - 1] + "'");
|
||||||
|
is(aEvent.charCode, aExpectText.charCodeAt(keypress - 1),
|
||||||
|
name + ", " + keypress + "th keypress event's charCode value should be 0x" + parseInt(aExpectText.charCodeAt(keypress - 1), 16));
|
||||||
|
}
|
||||||
|
textbox.addEventListener("keypress", onKeypress, true);
|
||||||
|
|
||||||
return synthesizeKey(aEvent, "textbox", function() {
|
return synthesizeKey(aEvent, "textbox", function() {
|
||||||
|
textbox.removeEventListener("keypress", onKeypress, true);
|
||||||
var name = eventToString(aEvent);
|
if (aExpectText == "") {
|
||||||
|
is(keypress, 1, name + " should cause one keypress event because it doesn't cause inputting text");
|
||||||
is(textbox.value, aExpectText, name + " does not input correct text.");
|
} else {
|
||||||
|
is(keypress, aExpectText.length, name + " should cause " + aExpectText.length + " keypress events");
|
||||||
|
is(textbox.value, aExpectText, name + " does not input correct text.");
|
||||||
|
}
|
||||||
|
|
||||||
continueTest();
|
continueTest();
|
||||||
});
|
});
|
||||||
|
|
Загрузка…
Ссылка в новой задаче