зеркало из 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();
|
||||
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 expectNoObserverCalled();
|
||||
yield checkNotSharing();
|
||||
|
|
|
@ -58,6 +58,10 @@ var gTests = [
|
|||
|
||||
// And finally verify the attribute is removed when closing the stream.
|
||||
yield closeStream();
|
||||
|
||||
// TODO(Bug 1304997): Fix the race in closeStream() and remove this
|
||||
// promiseWaitForCondition().
|
||||
yield promiseWaitForCondition(() => !tab.getAttribute("sharing"));
|
||||
is(tab.getAttribute("sharing"), "",
|
||||
"the tab no longer has the 'sharing' attribute after closing the stream");
|
||||
}
|
||||
|
|
|
@ -110,9 +110,6 @@ var gTests = [
|
|||
yield promiseReloadFrame("frame1");
|
||||
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 expectNoObserverCalled();
|
||||
yield checkNotSharing();
|
||||
|
@ -188,9 +185,6 @@ var gTests = [
|
|||
|
||||
yield expectObserverCalled("recording-window-ended");
|
||||
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 closeStream(false, "frame1");
|
||||
|
@ -225,9 +219,6 @@ var gTests = [
|
|||
content.location.reload();
|
||||
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 expectNoObserverCalled();
|
||||
yield checkNotSharing();
|
||||
|
|
|
@ -51,11 +51,8 @@ var gTests = [
|
|||
gBrowser.selectedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
|
||||
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-device-events");
|
||||
yield expectNoObserverCalled();
|
||||
yield checkNotSharing();
|
||||
}
|
||||
|
|
|
@ -35,17 +35,18 @@ function requestDevice(aAudio, aVideo, aShare) {
|
|||
opts.fake = true;
|
||||
}
|
||||
|
||||
window.navigator.mozGetUserMedia(opts, function(stream) {
|
||||
gStream = stream;
|
||||
message("ok");
|
||||
}, function(err) { message("error: " + err); });
|
||||
window.navigator.mediaDevices.getUserMedia(opts)
|
||||
.then(stream => {
|
||||
gStream = stream;
|
||||
message("ok");
|
||||
}, err => message("error: " + err));
|
||||
}
|
||||
message("pending");
|
||||
|
||||
function closeStream() {
|
||||
if (!gStream)
|
||||
return;
|
||||
gStream.stop();
|
||||
gStream.getTracks().forEach(t => t.stop());
|
||||
gStream = null;
|
||||
message("closed");
|
||||
}
|
||||
|
|
|
@ -34,13 +34,6 @@ addMessageListener("Test:ExpectObserverCalled", ({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 => {
|
||||
sendAsyncMessage("Test:ExpectNoObserverCalled:Reply", 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) {
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
let mm = _mm();
|
||||
|
@ -357,11 +345,6 @@ function* stopSharing(aType = "camera") {
|
|||
yield promiseRecordingEvent;
|
||||
yield expectObserverCalled("getUserMedia:revoke");
|
||||
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* checkNotSharing();
|
||||
}
|
||||
|
@ -398,12 +381,6 @@ function* closeStream(aAlreadyClosed, aFrameId) {
|
|||
if (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);
|
||||
}
|
||||
|
||||
|
|
|
@ -93,13 +93,15 @@ exports.getHighlighterUtils = function (toolbox) {
|
|||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
let togglePicker = exported.togglePicker = function () {
|
||||
let togglePicker = exported.togglePicker = function (doFocus) {
|
||||
if (isPicking) {
|
||||
return stopPicker();
|
||||
return cancelPicker();
|
||||
} else {
|
||||
return startPicker();
|
||||
return startPicker(doFocus);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -109,10 +111,12 @@ exports.getHighlighterUtils = function (toolbox) {
|
|||
* on the target page to highlight the hovered/picked element.
|
||||
* Depending on the server-side capabilities, this may fire events when nodes
|
||||
* 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
|
||||
* if it is already started
|
||||
*/
|
||||
let startPicker = exported.startPicker = requireInspector(function* () {
|
||||
let startPicker = exported.startPicker = requireInspector(function* (doFocus = false) {
|
||||
if (isPicking) {
|
||||
return;
|
||||
}
|
||||
|
@ -120,14 +124,14 @@ exports.getHighlighterUtils = function (toolbox) {
|
|||
|
||||
toolbox.pickerButtonChecked = true;
|
||||
yield toolbox.selectTool("inspector");
|
||||
toolbox.on("select", stopPicker);
|
||||
toolbox.on("select", cancelPicker);
|
||||
|
||||
if (isRemoteHighlightable()) {
|
||||
toolbox.walker.on("picker-node-hovered", onPickerNodeHovered);
|
||||
toolbox.walker.on("picker-node-picked", onPickerNodePicked);
|
||||
toolbox.walker.on("picker-node-canceled", onPickerNodeCanceled);
|
||||
|
||||
yield toolbox.highlighter.pick();
|
||||
yield toolbox.highlighter.pick(doFocus);
|
||||
toolbox.emit("picker-started");
|
||||
} else {
|
||||
// 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();
|
||||
}
|
||||
|
||||
toolbox.off("select", stopPicker);
|
||||
toolbox.off("select", cancelPicker);
|
||||
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
|
||||
* @param {Object} data Information about the node being hovered
|
||||
|
@ -190,7 +202,7 @@ exports.getHighlighterUtils = function (toolbox) {
|
|||
* gets the focus.
|
||||
*/
|
||||
function onPickerNodeCanceled() {
|
||||
stopPicker();
|
||||
cancelPicker();
|
||||
toolbox.win.focus();
|
||||
}
|
||||
|
||||
|
|
|
@ -123,6 +123,10 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
|
|||
this._toggleMinimizeMode = this._toggleMinimizeMode.bind(this);
|
||||
this._onTabbarFocus = this._onTabbarFocus.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);
|
||||
|
||||
|
@ -149,6 +153,9 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
|
|||
|
||||
gDevTools.on("tool-registered", this._toolRegistered);
|
||||
gDevTools.on("tool-unregistered", this._toolUnregistered);
|
||||
|
||||
this.on("picker-started", this._onPickerStarted);
|
||||
this.on("picker-stopped", this._onPickerStopped);
|
||||
}
|
||||
exports.Toolbox = Toolbox;
|
||||
|
||||
|
@ -984,8 +991,39 @@ Toolbox.prototype = {
|
|||
let container = this.doc.querySelector("#toolbox-picker-container");
|
||||
container.appendChild(this._pickerButton);
|
||||
|
||||
this._togglePicker = this.highlighterUtils.togglePicker.bind(this.highlighterUtils);
|
||||
this._pickerButton.addEventListener("click", this._togglePicker, false);
|
||||
this._pickerButton.addEventListener("click", this._onPickerClick, 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/tabs/tabs.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/shared/components/splitter/split-box.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._onMouseMove = this._onMouseMove.bind(this);
|
||||
this._onMouseOut = this._onMouseOut.bind(this);
|
||||
this._onToolboxPickerCanceled = this._onToolboxPickerCanceled.bind(this);
|
||||
this._onToolboxPickerHover = this._onToolboxPickerHover.bind(this);
|
||||
this._onCollapseAttributesPrefChange =
|
||||
this._onCollapseAttributesPrefChange.bind(this);
|
||||
|
@ -127,6 +128,7 @@ function MarkupView(inspector, frame, controllerWindow) {
|
|||
this.walker.on("mutations", this._mutationObserver);
|
||||
this.walker.on("display-change", this._onDisplayChange);
|
||||
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._onNewSelection();
|
||||
|
@ -189,6 +191,16 @@ MarkupView.prototype = {
|
|||
}, 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,
|
||||
|
||||
_onMouseMove: function (event) {
|
||||
|
@ -375,6 +387,9 @@ MarkupView.prototype = {
|
|||
|
||||
this.getContainer(nodeFront).hovered = true;
|
||||
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) {
|
||||
|
|
|
@ -26,6 +26,7 @@ support-files =
|
|||
doc_inspector_infobar_02.html
|
||||
doc_inspector_infobar_03.html
|
||||
doc_inspector_infobar_textnode.html
|
||||
doc_inspector_long-divs.html
|
||||
doc_inspector_menu.html
|
||||
doc_inspector_outerhtml.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-04.js]
|
||||
[browser_inspector_highlighter-by-type.js]
|
||||
[browser_inspector_highlighter-cancel.js]
|
||||
[browser_inspector_highlighter-comments.js]
|
||||
[browser_inspector_highlighter-cssgrid_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.
|
||||
* @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");
|
||||
toolbox.win.focus();
|
||||
yield toolbox.highlighterUtils.startPicker();
|
||||
// Make sure the content window is focused since the picker does not focus
|
||||
// the content window by default.
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
|
||||
content.focus();
|
||||
});
|
||||
if (!skipFocus) {
|
||||
// By default make sure the content window is focused since the picker may not focus
|
||||
// the content window by default.
|
||||
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.
|
||||
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
|
||||
# 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
|
||||
|
|
|
@ -76,6 +76,11 @@ const ResponsiveUIManager = exports.ResponsiveUIManager = {
|
|||
this.showRemoteOnlyNotification(window, tab, options);
|
||||
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)) {
|
||||
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
|
||||
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)) {
|
||||
// Notification already displayed
|
||||
return;
|
||||
}
|
||||
|
||||
nbox.appendNotification(
|
||||
getStr("responsive.remoteOnly"),
|
||||
msg,
|
||||
value,
|
||||
null,
|
||||
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-map.js',
|
||||
'grip.js',
|
||||
'infinity.js',
|
||||
'nan.js',
|
||||
'null.js',
|
||||
'number.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) {
|
||||
return type == "boolean" || type == "number" ||
|
||||
(type == "object" && object.type == "-0");
|
||||
return ["boolean", "number", "-0"].includes(type);
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
|
|
@ -21,6 +21,8 @@ define(function (require, exports, module) {
|
|||
const { ArrayRep } = require("./array");
|
||||
const { Obj } = require("./object");
|
||||
const { SymbolRep } = require("./symbol");
|
||||
const { InfinityRep } = require("./infinity");
|
||||
const { NaNRep } = require("./nan");
|
||||
|
||||
// DOM types (grips)
|
||||
const { Attribute } = require("./attribute");
|
||||
|
@ -62,6 +64,8 @@ define(function (require, exports, module) {
|
|||
StringRep,
|
||||
Number,
|
||||
SymbolRep,
|
||||
InfinityRep,
|
||||
NaNRep,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -102,8 +106,8 @@ define(function (require, exports, module) {
|
|||
let type = typeof object;
|
||||
if (type == "object" && object instanceof String) {
|
||||
type = "string";
|
||||
} else if (object && type == "object" && object.type === "symbol") {
|
||||
type = "symbol";
|
||||
} else if (object && type == "object" && object.type) {
|
||||
type = object.type;
|
||||
}
|
||||
|
||||
if (isGrip(object)) {
|
||||
|
|
|
@ -16,6 +16,8 @@ support-files =
|
|||
[test_reps_grip.html]
|
||||
[test_reps_grip-array.html]
|
||||
[test_reps_grip-map.html]
|
||||
[test_reps_infinity.html]
|
||||
[test_reps_nan.html]
|
||||
[test_reps_null.html]
|
||||
[test_reps_number.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");
|
||||
useXulWrapper = true;
|
||||
|
||||
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);
|
||||
}
|
||||
yield runTests(doc);
|
||||
});
|
||||
|
||||
function* runTests(doc) {
|
||||
|
|
|
@ -229,6 +229,7 @@ function HTMLTooltip(toolboxDoc, {
|
|||
this._position = null;
|
||||
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onXulPanelHidden = this._onXulPanelHidden.bind(this);
|
||||
|
||||
this._toggle = new TooltipToggle(this);
|
||||
this.startTogglingOnHover = this._toggle.start.bind(this._toggle);
|
||||
|
@ -539,6 +540,12 @@ HTMLTooltip.prototype = {
|
|||
return false;
|
||||
},
|
||||
|
||||
_onXulPanelHidden: function () {
|
||||
if (this.isVisible()) {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* If the tootlip is configured to autofocus and a focusable element can be found,
|
||||
* focus it.
|
||||
|
@ -572,7 +579,6 @@ HTMLTooltip.prototype = {
|
|||
panel.setAttribute("animate", false);
|
||||
panel.setAttribute("consumeoutsideclicks", false);
|
||||
panel.setAttribute("noautofocus", true);
|
||||
panel.setAttribute("noautohide", true);
|
||||
panel.setAttribute("ignorekeys", true);
|
||||
panel.setAttribute("tooltip", "aHTMLTooltip");
|
||||
|
||||
|
@ -586,12 +592,20 @@ HTMLTooltip.prototype = {
|
|||
},
|
||||
|
||||
_showXulWrapperAt: function (left, top) {
|
||||
this.xulPanelWrapper.addEventListener("popuphidden", this._onXulPanelHidden);
|
||||
let onPanelShown = listenOnce(this.xulPanelWrapper, "popupshown");
|
||||
this.xulPanelWrapper.openPopupAtScreen(left, top, false);
|
||||
return onPanelShown;
|
||||
},
|
||||
|
||||
_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");
|
||||
this.xulPanelWrapper.hidePopup();
|
||||
return onPanelHidden;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
padding: 2px;
|
||||
}
|
||||
|
||||
*|*:root[platform="mac"] > scrollbar,
|
||||
*|*:root[platform="mac"] *|*:not(html|select) > scrollbar {
|
||||
border: none;
|
||||
}
|
||||
|
@ -40,10 +41,15 @@
|
|||
border-radius: 3px !important;
|
||||
}
|
||||
|
||||
*|*:root[platform="mac"] > scrollbar slider,
|
||||
*|*:root[platform="mac"] *|*:not(html|select) > scrollbar slider {
|
||||
-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="linux"] *|*:not(html|select) > scrollbar scrollbarbutton,
|
||||
*|*:root[platform="win"] *|*:not(html|select) > scrollbar gripper,
|
||||
|
|
|
@ -353,6 +353,16 @@ var HighlighterActor = exports.HighlighterActor = protocol.ActorClassWithSpec(hi
|
|||
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) {
|
||||
// originalTarget allows access to the "real" element before any retargeting
|
||||
// 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/. */
|
||||
"use strict";
|
||||
|
||||
const { FrontClassWithSpec } = require("devtools/shared/protocol");
|
||||
const { FrontClassWithSpec, custom } = require("devtools/shared/protocol");
|
||||
const {
|
||||
customHighlighterSpec,
|
||||
highlighterSpec
|
||||
|
@ -15,7 +15,16 @@ const HighlighterFront = FrontClassWithSpec(highlighterSpec, {
|
|||
this.actorID = json.actor;
|
||||
// FF42+ HighlighterActors starts exposing custom form, with traits object
|
||||
this.traits = json.traits || {};
|
||||
}
|
||||
},
|
||||
|
||||
pick: custom(function (doFocus) {
|
||||
if (doFocus && this.pickAndFocus) {
|
||||
return this.pickAndFocus();
|
||||
}
|
||||
return this._pick();
|
||||
}, {
|
||||
impl: "_pick"
|
||||
})
|
||||
});
|
||||
|
||||
exports.HighlighterFront = HighlighterFront;
|
||||
|
|
|
@ -28,6 +28,7 @@ const highlighterSpec = generateActorSpec({
|
|||
request: {}
|
||||
},
|
||||
pick: {},
|
||||
pickAndFocus: {},
|
||||
cancelPick: {}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -2908,9 +2908,13 @@ CanvasRenderingContext2D::UpdateFilter()
|
|||
// with.
|
||||
presShell->FlushPendingNotifications(Flush_Frames);
|
||||
|
||||
bool sourceGraphicIsTainted =
|
||||
(mCanvasElement && mCanvasElement->IsWriteOnly());
|
||||
|
||||
CurrentState().filter =
|
||||
nsFilterInstance::GetFilterDescription(mCanvasElement,
|
||||
CurrentState().filterChain,
|
||||
sourceGraphicIsTainted,
|
||||
CanvasUserSpaceMetrics(GetSize(),
|
||||
CurrentState().fontFont,
|
||||
CurrentState().fontLanguage,
|
||||
|
@ -2918,8 +2922,7 @@ CanvasRenderingContext2D::UpdateFilter()
|
|||
presShell->GetPresContext()),
|
||||
gfxRect(0, 0, mWidth, mHeight),
|
||||
CurrentState().filterAdditionalImages);
|
||||
CurrentState().filterSourceGraphicTainted =
|
||||
(mCanvasElement && mCanvasElement->IsWriteOnly());
|
||||
CurrentState().filterSourceGraphicTainted = sourceGraphicIsTainted;
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -264,12 +264,6 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1040965
|
|||
[test_filter.html]
|
||||
skip-if = (e10s && debug && os == 'win')
|
||||
[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]
|
||||
subsuite = gpu
|
||||
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">
|
||||
<svg style="display: block; width: 0; height: 0">
|
||||
<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"/>
|
||||
</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>
|
||||
</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>
|
||||
|
||||
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 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);
|
||||
var checkSucceeded = r - d <= pr && pr <= r + d &&
|
||||
g - d <= pg && pg <= g + d &&
|
||||
b - d <= pb && pb <= b + d &&
|
||||
a - d <= pa && pa <= a + 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() {
|
||||
|
||||
SpecialPowers.pushPrefEnv({ 'set': [['canvas.filters.enabled', true]] }, function () {
|
||||
|
||||
var canvas = document.getElementById('c');
|
||||
var sameOriginGreenImage = document.getElementById('same-origin-green');
|
||||
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');
|
||||
|
||||
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.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');
|
||||
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');
|
||||
// cross-origin feImage should be ignored as map input to a displacement map, and taint the canvas.
|
||||
ctx.filter = 'url(#use-cross-origin-green-feImage-as-map-on-same-origin-red)';
|
||||
ctx.rect(0, 0, 16, 16);
|
||||
ctx.fill();
|
||||
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);
|
||||
|
||||
// Create new untainted canvas.
|
||||
canvas = newCanvas();
|
||||
ctx = canvas.getContext('2d');
|
||||
|
||||
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();
|
||||
|
||||
});
|
||||
|
|
|
@ -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)
|
||||
, mRemoved(false)
|
||||
, mAudioStopped(false)
|
||||
, mVideoStopped(false) {}
|
||||
, mAudioStopPending(false)
|
||||
, mVideoStopped(false)
|
||||
, mVideoStopPending(false)
|
||||
, mChromeNotificationTaskPosted(false)
|
||||
{}
|
||||
|
||||
~GetUserMediaCallbackMediaStreamListener()
|
||||
{
|
||||
|
@ -302,6 +306,8 @@ public:
|
|||
|
||||
void StopTrack(TrackID aID);
|
||||
|
||||
void NotifyChromeOfTrackStops();
|
||||
|
||||
typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
|
||||
|
||||
already_AddRefed<PledgeVoid>
|
||||
|
@ -494,10 +500,22 @@ private:
|
|||
// MainThread only.
|
||||
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.
|
||||
// MainThread only.
|
||||
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
|
||||
|
||||
// Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
|
||||
|
@ -743,7 +761,8 @@ protected:
|
|||
NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
|
||||
|
||||
MediaDevice::MediaDevice(MediaEngineSource* aSource, bool aIsVideo)
|
||||
: mMediaSource(aSource->GetMediaSource())
|
||||
: mScary(aSource->GetScary())
|
||||
, mMediaSource(aSource->GetMediaSource())
|
||||
, mSource(aSource)
|
||||
, mIsVideo(aIsVideo)
|
||||
{
|
||||
|
@ -881,6 +900,13 @@ MediaDevice::GetRawId(nsAString& aID)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MediaDevice::GetScary(bool* aScary)
|
||||
{
|
||||
*aScary = mScary;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
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",
|
||||
aTrackID,
|
||||
aTrackID == kAudioTrack ? "audio" : "video",
|
||||
aTrackID == kAudioTrack ? !!mAudioDevice : !!mVideoDevice,
|
||||
aTrackID == kAudioTrack ? mAudioStopped : mVideoStopped));
|
||||
stopAudio ? "audio" : "video",
|
||||
stopAudio ? !!mAudioDevice : !!mVideoDevice,
|
||||
stopAudio ? mAudioStopped : mVideoStopped));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3558,6 +3584,59 @@ GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID)
|
|||
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 =
|
||||
new MediaOperationTask(MEDIA_STOP_TRACK,
|
||||
this, nullptr, nullptr,
|
||||
|
@ -3565,8 +3644,6 @@ GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID)
|
|||
stopVideo ? mVideoDevice.get() : nullptr,
|
||||
false , mWindowID, nullptr);
|
||||
MediaManager::PostTask(mediaOperation.forget());
|
||||
mAudioStopped |= stopAudio;
|
||||
mVideoStopped |= stopVideo;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -94,6 +94,7 @@ protected:
|
|||
nsString mName;
|
||||
nsString mID;
|
||||
nsString mRawID;
|
||||
bool mScary;
|
||||
dom::MediaSourceEnum mMediaSource;
|
||||
RefPtr<MediaEngineSource> mSource;
|
||||
RefPtr<MediaEngineSource::AllocationHandle> mAllocationHandle;
|
||||
|
|
|
@ -13,6 +13,7 @@ interface nsIMediaDevice : nsISupports
|
|||
readonly attribute DOMString id;
|
||||
readonly attribute DOMString mediaSource;
|
||||
readonly attribute DOMString rawId;
|
||||
readonly attribute boolean scary;
|
||||
};
|
||||
|
||||
[scriptable, function, uuid(24544878-d35e-4962-8c5f-fb84e97bdfee)]
|
||||
|
|
|
@ -214,6 +214,9 @@ public:
|
|||
/* Populate the UUID of this device in the nsACString */
|
||||
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
|
||||
{
|
||||
public:
|
||||
|
@ -249,9 +252,14 @@ public:
|
|||
return a.get() == b.get();
|
||||
}
|
||||
};
|
||||
MOZ_ASSERT(mRegisteredHandles.Contains(handle, Comparator()));
|
||||
mRegisteredHandles.RemoveElementAt(mRegisteredHandles.IndexOf(handle, 0,
|
||||
Comparator()));
|
||||
|
||||
auto ix = mRegisteredHandles.IndexOf(handle, 0, Comparator());
|
||||
if (ix == mRegisteredHandles.NoIndex) {
|
||||
MOZ_ASSERT(false);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mRegisteredHandles.RemoveElementAt(ix);
|
||||
if (mRegisteredHandles.Length() && !mInShutdown) {
|
||||
// Whenever constraints are removed, other parties may get closer to ideal.
|
||||
auto& first = mRegisteredHandles[0];
|
||||
|
|
|
@ -29,10 +29,11 @@ NS_IMPL_ISUPPORTS0(MediaEngineRemoteVideoSource)
|
|||
|
||||
MediaEngineRemoteVideoSource::MediaEngineRemoteVideoSource(
|
||||
int aIndex, mozilla::camera::CaptureEngine aCapEngine,
|
||||
dom::MediaSourceEnum aMediaSource, const char* aMonitorName)
|
||||
dom::MediaSourceEnum aMediaSource, bool aScary, const char* aMonitorName)
|
||||
: MediaEngineCameraVideoSource(aIndex, aMonitorName),
|
||||
mMediaSource(aMediaSource),
|
||||
mCapEngine(aCapEngine)
|
||||
mCapEngine(aCapEngine),
|
||||
mScary(aScary)
|
||||
{
|
||||
MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other);
|
||||
mSettings.mWidth.Construct(0);
|
||||
|
|
|
@ -70,6 +70,7 @@ public:
|
|||
// MediaEngineCameraVideoSource
|
||||
MediaEngineRemoteVideoSource(int aIndex, mozilla::camera::CaptureEngine aCapEngine,
|
||||
dom::MediaSourceEnum aMediaSource,
|
||||
bool aScary = false,
|
||||
const char* aMonitorName = "RemoteVideo.Monitor");
|
||||
|
||||
nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
|
@ -103,6 +104,8 @@ public:
|
|||
|
||||
void Shutdown() override;
|
||||
|
||||
bool GetScary() const override { return mScary; }
|
||||
|
||||
protected:
|
||||
~MediaEngineRemoteVideoSource() { }
|
||||
|
||||
|
@ -125,6 +128,7 @@ private:
|
|||
|
||||
// To only restart camera when needed, we keep track previous settings.
|
||||
webrtc::CaptureCapability mLastCapability;
|
||||
bool mScary;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,11 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
|
|||
|
||||
void GetName(nsAString_internal&) const override;
|
||||
void GetUUID(nsACString_internal&) const override;
|
||||
|
||||
bool GetScary() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsresult Allocate(const dom::MediaTrackConstraints &,
|
||||
const mozilla::MediaEnginePrefs&,
|
||||
const nsString& aDeviceId,
|
||||
|
|
|
@ -157,6 +157,7 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
|||
return;
|
||||
}
|
||||
#endif
|
||||
bool scaryKind = false; // flag sources with cross-origin exploit potential
|
||||
|
||||
switch (aMediaSource) {
|
||||
case dom::MediaSourceEnum::Window:
|
||||
|
@ -167,9 +168,11 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
|||
break;
|
||||
case dom::MediaSourceEnum::Screen:
|
||||
capEngine = mozilla::camera::ScreenEngine;
|
||||
scaryKind = true;
|
||||
break;
|
||||
case dom::MediaSourceEnum::Browser:
|
||||
capEngine = mozilla::camera::BrowserEngine;
|
||||
scaryKind = true;
|
||||
break;
|
||||
case dom::MediaSourceEnum::Camera:
|
||||
capEngine = mozilla::camera::CameraEngine;
|
||||
|
@ -196,6 +199,7 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
|||
for (int i = 0; i < num; i++) {
|
||||
char deviceName[MediaEngineSource::kMaxDeviceNameLength];
|
||||
char uniqueId[MediaEngineSource::kMaxUniqueIdLength];
|
||||
bool scaryWindow = false;
|
||||
|
||||
// paranoia
|
||||
deviceName[0] = '\0';
|
||||
|
@ -215,6 +219,16 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
|||
#ifdef DEBUG
|
||||
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;
|
||||
int numCaps = mozilla::camera::GetChildAndCall(
|
||||
&mozilla::camera::CamerasChild::NumberOfCapabilities,
|
||||
|
@ -247,7 +261,8 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
|||
static_cast<MediaEngineRemoteVideoSource*>(vSource.get())->Refresh(i);
|
||||
aVSources->AppendElement(vSource.get());
|
||||
} else {
|
||||
vSource = new MediaEngineRemoteVideoSource(i, capEngine, aMediaSource);
|
||||
vSource = new MediaEngineRemoteVideoSource(i, capEngine, aMediaSource,
|
||||
scaryKind || scaryWindow);
|
||||
mVideoSources.Put(uuid, vSource); // Hashtable takes ownership.
|
||||
aVSources->AppendElement(vSource);
|
||||
}
|
||||
|
|
|
@ -165,8 +165,9 @@ BufferTextureData::CreateForYCbCrWithBufferSize(KnowsCompositor* aAllocator,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool hasIntermediateBuffer = ComputeHasIntermediateBuffer(gfx::SurfaceFormat::YUV,
|
||||
aAllocator->GetCompositorBackendType());
|
||||
bool hasIntermediateBuffer = aAllocator ? ComputeHasIntermediateBuffer(gfx::SurfaceFormat::YUV,
|
||||
aAllocator->GetCompositorBackendType())
|
||||
: true;
|
||||
|
||||
// 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.
|
||||
|
@ -175,8 +176,8 @@ BufferTextureData::CreateForYCbCrWithBufferSize(KnowsCompositor* aAllocator,
|
|||
aYUVColorSpace,
|
||||
hasIntermediateBuffer);
|
||||
|
||||
return CreateInternal(aAllocator->GetTextureForwarder(), desc, gfx::BackendType::NONE, aBufferSize,
|
||||
aTextureFlags);
|
||||
return CreateInternal(aAllocator ? aAllocator->GetTextureForwarder() : nullptr,
|
||||
desc, gfx::BackendType::NONE, aBufferSize, aTextureFlags);
|
||||
}
|
||||
|
||||
BufferTextureData*
|
||||
|
|
|
@ -32,8 +32,8 @@ function* test(testDriver) {
|
|||
testDriver();
|
||||
};
|
||||
|
||||
yield synthesizeNativeTouch(v, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT);
|
||||
yield synthesizeNativeTouch(v, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE);
|
||||
yield synthesizeNativeTouch(v, 25, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT);
|
||||
yield synthesizeNativeTouch(v, 25, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE);
|
||||
ok(v._gotTouchend, 'Touchend was received on video element');
|
||||
|
||||
yield synthesizeNativeTouch(a, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT);
|
||||
|
|
|
@ -28,20 +28,21 @@ window.addEventListener("load", () => {
|
|||
target.style.animation = "Opacity0 100s 100s";
|
||||
|
||||
// We need to wait for MozAfterPaint instead of requestAnimationFrame to
|
||||
// ensure the stacking context has been updated (removed) on the compositor
|
||||
// before we snapshot.
|
||||
// ensure the stacking context has been updated on the compositor.
|
||||
window.addEventListener("MozAfterPaint", function firstPaint() {
|
||||
window.removeEventListener("MozAfterPaint", firstPaint, false);
|
||||
// Here we have CSS animation on 100% opacity style element, so
|
||||
// there should be a stacking context.
|
||||
|
||||
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
|
||||
// stacking context.
|
||||
document.documentElement.classList.remove("reftest-wait");
|
||||
}, false);
|
||||
});
|
||||
}, false);
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -28,20 +28,20 @@ window.addEventListener("load", () => {
|
|||
target.style.animation = "TransformNone 100s 100s";
|
||||
|
||||
// We need to wait for MozAfterPaint instead of requestAnimationFrame to
|
||||
// ensure the stacking context has been updated (removed) on the compositor
|
||||
// before we snapshot.
|
||||
// ensure the stacking context has been updated on the compositor.
|
||||
window.addEventListener("MozAfterPaint", function firstPaint() {
|
||||
window.removeEventListener("MozAfterPaint", firstPaint, false);
|
||||
// Here we have CSS animation on transform:none style element, so
|
||||
// there should be a stacking context.
|
||||
|
||||
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 transform:none style, so we should not create any
|
||||
// stacking context.
|
||||
document.documentElement.classList.remove("reftest-wait");
|
||||
}, false);
|
||||
});
|
||||
}, false);
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -40,50 +40,71 @@ nsCSSFilterInstance::nsCSSFilterInstance(const nsStyleFilter& aFilter,
|
|||
}
|
||||
|
||||
nsresult
|
||||
nsCSSFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs)
|
||||
nsCSSFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
|
||||
bool aInputIsTainted)
|
||||
{
|
||||
FilterPrimitiveDescription descr;
|
||||
nsresult result;
|
||||
|
||||
switch(mFilter.GetType()) {
|
||||
case NS_STYLE_FILTER_BLUR:
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::GaussianBlur, aPrimitiveDescrs);
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::GaussianBlur,
|
||||
aPrimitiveDescrs,
|
||||
aInputIsTainted);
|
||||
result = SetAttributesForBlur(descr);
|
||||
break;
|
||||
case NS_STYLE_FILTER_BRIGHTNESS:
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer, aPrimitiveDescrs);
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer,
|
||||
aPrimitiveDescrs,
|
||||
aInputIsTainted);
|
||||
result = SetAttributesForBrightness(descr);
|
||||
break;
|
||||
case NS_STYLE_FILTER_CONTRAST:
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer, aPrimitiveDescrs);
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer,
|
||||
aPrimitiveDescrs,
|
||||
aInputIsTainted);
|
||||
result = SetAttributesForContrast(descr);
|
||||
break;
|
||||
case NS_STYLE_FILTER_DROP_SHADOW:
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::DropShadow, aPrimitiveDescrs);
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::DropShadow,
|
||||
aPrimitiveDescrs,
|
||||
aInputIsTainted);
|
||||
result = SetAttributesForDropShadow(descr);
|
||||
break;
|
||||
case NS_STYLE_FILTER_GRAYSCALE:
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix, aPrimitiveDescrs);
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix,
|
||||
aPrimitiveDescrs,
|
||||
aInputIsTainted);
|
||||
result = SetAttributesForGrayscale(descr);
|
||||
break;
|
||||
case NS_STYLE_FILTER_HUE_ROTATE:
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix, aPrimitiveDescrs);
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix,
|
||||
aPrimitiveDescrs,
|
||||
aInputIsTainted);
|
||||
result = SetAttributesForHueRotate(descr);
|
||||
break;
|
||||
case NS_STYLE_FILTER_INVERT:
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer, aPrimitiveDescrs);
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer,
|
||||
aPrimitiveDescrs,
|
||||
aInputIsTainted);
|
||||
result = SetAttributesForInvert(descr);
|
||||
break;
|
||||
case NS_STYLE_FILTER_OPACITY:
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer, aPrimitiveDescrs);
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer,
|
||||
aPrimitiveDescrs,
|
||||
aInputIsTainted);
|
||||
result = SetAttributesForOpacity(descr);
|
||||
break;
|
||||
case NS_STYLE_FILTER_SATURATE:
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix, aPrimitiveDescrs);
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix,
|
||||
aPrimitiveDescrs,
|
||||
aInputIsTainted);
|
||||
result = SetAttributesForSaturate(descr);
|
||||
break;
|
||||
case NS_STYLE_FILTER_SEPIA:
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix, aPrimitiveDescrs);
|
||||
descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix,
|
||||
aPrimitiveDescrs,
|
||||
aInputIsTainted);
|
||||
result = SetAttributesForSepia(descr);
|
||||
break;
|
||||
default:
|
||||
|
@ -106,11 +127,12 @@ nsCSSFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrim
|
|||
|
||||
FilterPrimitiveDescription
|
||||
nsCSSFilterInstance::CreatePrimitiveDescription(PrimitiveType aType,
|
||||
const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) {
|
||||
const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
|
||||
bool aInputIsTainted) {
|
||||
FilterPrimitiveDescription descr(aType);
|
||||
int32_t inputIndex = GetLastResultIndex(aPrimitiveDescrs);
|
||||
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.SetOutputColorSpace(ColorSpace::SRGB);
|
||||
return descr;
|
||||
|
|
|
@ -50,15 +50,25 @@ public:
|
|||
* Creates at least one new FilterPrimitiveDescription based on the filter
|
||||
* from the style system. Appends the new FilterPrimitiveDescription(s) to the
|
||||
* 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:
|
||||
/**
|
||||
* Returns a new FilterPrimitiveDescription with its basic properties set up.
|
||||
* See the comment above BuildPrimitives for the meaning of aInputIsTainted.
|
||||
*/
|
||||
FilterPrimitiveDescription CreatePrimitiveDescription(PrimitiveType aType,
|
||||
const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs);
|
||||
const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
|
||||
bool aInputIsTainted);
|
||||
|
||||
/**
|
||||
* Sets aDescr's attributes using the style info in mFilter.
|
||||
|
|
|
@ -32,14 +32,15 @@ using namespace mozilla::gfx;
|
|||
FilterDescription
|
||||
nsFilterInstance::GetFilterDescription(nsIContent* aFilteredElement,
|
||||
const nsTArray<nsStyleFilter>& aFilterChain,
|
||||
bool aFilterInputIsTainted,
|
||||
const UserSpaceMetrics& aMetrics,
|
||||
const gfxRect& aBBox,
|
||||
nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages)
|
||||
{
|
||||
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
||||
nsFilterInstance instance(nullptr, aFilteredElement, aMetrics,
|
||||
aFilterChain, nullptr, unused,
|
||||
nullptr, nullptr, nullptr, &aBBox);
|
||||
aFilterChain, aFilterInputIsTainted, nullptr,
|
||||
unused, nullptr, nullptr, nullptr, &aBBox);
|
||||
if (!instance.IsInitialized()) {
|
||||
return FilterDescription();
|
||||
}
|
||||
|
@ -65,9 +66,11 @@ nsFilterInstance::PaintFilteredFrame(nsIFrame *aFilteredFrame,
|
|||
{
|
||||
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
||||
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
||||
// Hardcode InputIsTainted to true because we don't want JS to be able to
|
||||
// read the rendered contents of aFilteredFrame.
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
||||
filterChain, aPaintCallback, aTransform,
|
||||
aDirtyArea, nullptr, nullptr, nullptr);
|
||||
filterChain, /* InputIsTainted */ true, aPaintCallback,
|
||||
aTransform, aDirtyArea, nullptr, nullptr, nullptr);
|
||||
if (!instance.IsInitialized()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -85,9 +88,11 @@ nsFilterInstance::GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
|
|||
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
||||
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
||||
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
||||
// Hardcode InputIsTainted to true because we don't want JS to be able to
|
||||
// read the rendered contents of aFilteredFrame.
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
||||
filterChain, nullptr, unused, nullptr,
|
||||
&aPreFilterDirtyRegion);
|
||||
filterChain, /* InputIsTainted */ true, nullptr, unused,
|
||||
nullptr, &aPreFilterDirtyRegion);
|
||||
if (!instance.IsInitialized()) {
|
||||
return nsRegion();
|
||||
}
|
||||
|
@ -105,8 +110,10 @@ nsFilterInstance::GetPreFilterNeededArea(nsIFrame *aFilteredFrame,
|
|||
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
||||
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
||||
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
||||
// Hardcode InputIsTainted to true because we don't want JS to be able to
|
||||
// read the rendered contents of aFilteredFrame.
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
||||
filterChain, nullptr, unused,
|
||||
filterChain, /* InputIsTainted */ true, nullptr, unused,
|
||||
&aPostFilterDirtyRegion);
|
||||
if (!instance.IsInitialized()) {
|
||||
return nsRect();
|
||||
|
@ -136,9 +143,11 @@ nsFilterInstance::GetPostFilterBounds(nsIFrame *aFilteredFrame,
|
|||
gfxMatrix unused; // aPaintTransform arg not used since we're not painting
|
||||
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
|
||||
UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
|
||||
// Hardcode InputIsTainted to true because we don't want JS to be able to
|
||||
// read the rendered contents of aFilteredFrame.
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
|
||||
filterChain, nullptr, unused, nullptr,
|
||||
preFilterRegionPtr, aPreFilterBounds,
|
||||
filterChain, /* InputIsTainted */ true, nullptr, unused,
|
||||
nullptr, preFilterRegionPtr, aPreFilterBounds,
|
||||
aOverrideBBox);
|
||||
if (!instance.IsInitialized()) {
|
||||
return nsRect();
|
||||
|
@ -151,6 +160,7 @@ nsFilterInstance::nsFilterInstance(nsIFrame *aTargetFrame,
|
|||
nsIContent* aTargetContent,
|
||||
const UserSpaceMetrics& aMetrics,
|
||||
const nsTArray<nsStyleFilter>& aFilterChain,
|
||||
bool aFilterInputIsTainted,
|
||||
nsSVGFilterPaintCallback *aPaintCallback,
|
||||
const gfxMatrix& aPaintTransform,
|
||||
const nsRegion *aPostFilterDirtyRegion,
|
||||
|
@ -213,7 +223,7 @@ nsFilterInstance::nsFilterInstance(nsIFrame *aTargetFrame,
|
|||
mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds);
|
||||
|
||||
// Build the filter graph.
|
||||
rv = BuildPrimitives(aFilterChain, aTargetFrame);
|
||||
rv = BuildPrimitives(aFilterChain, aTargetFrame, aFilterInputIsTainted);
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
}
|
||||
|
@ -274,13 +284,17 @@ nsFilterInstance::FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const
|
|||
|
||||
nsresult
|
||||
nsFilterInstance::BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain,
|
||||
nsIFrame* aTargetFrame)
|
||||
nsIFrame* aTargetFrame,
|
||||
bool aFilterInputIsTainted)
|
||||
{
|
||||
NS_ASSERTION(!mPrimitiveDescriptions.Length(),
|
||||
"expected to start building primitives from scratch");
|
||||
|
||||
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)) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -293,7 +307,8 @@ nsFilterInstance::BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain,
|
|||
|
||||
nsresult
|
||||
nsFilterInstance::BuildPrimitivesForFilter(const nsStyleFilter& aFilter,
|
||||
nsIFrame* aTargetFrame)
|
||||
nsIFrame* aTargetFrame,
|
||||
bool aInputIsTainted)
|
||||
{
|
||||
NS_ASSERTION(mUserSpaceToFilterSpaceScale.width > 0.0f &&
|
||||
mFilterSpaceToUserSpaceScale.height > 0.0f,
|
||||
|
@ -310,7 +325,8 @@ nsFilterInstance::BuildPrimitivesForFilter(const nsStyleFilter& aFilter,
|
|||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return svgFilterInstance.BuildPrimitives(mPrimitiveDescriptions, mInputImages);
|
||||
return svgFilterInstance.BuildPrimitives(mPrimitiveDescriptions, mInputImages,
|
||||
aInputIsTainted);
|
||||
}
|
||||
|
||||
// Build primitives for a CSS filter.
|
||||
|
@ -323,7 +339,7 @@ nsFilterInstance::BuildPrimitivesForFilter(const nsStyleFilter& aFilter,
|
|||
nsCSSFilterInstance cssFilterInstance(aFilter, shadowFallbackColor,
|
||||
mTargetBounds,
|
||||
mFrameSpaceInCSSPxToFilterSpaceTransform);
|
||||
return cssFilterInstance.BuildPrimitives(mPrimitiveDescriptions);
|
||||
return cssFilterInstance.BuildPrimitives(mPrimitiveDescriptions, aInputIsTainted);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -60,12 +60,19 @@ public:
|
|||
/**
|
||||
* Create a FilterDescription for the supplied filter. All coordinates in
|
||||
* 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
|
||||
* render the filter (from feImage primitives).
|
||||
* @return A FilterDescription describing the filter.
|
||||
*/
|
||||
static FilterDescription GetFilterDescription(nsIContent* aFilteredElement,
|
||||
const nsTArray<nsStyleFilter>& aFilterChain,
|
||||
bool aFilterInputIsTainted,
|
||||
const UserSpaceMetrics& aMetrics,
|
||||
const gfxRect& aBBox,
|
||||
nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages);
|
||||
|
@ -118,6 +125,9 @@ public:
|
|||
* @param aTargetContent The filtered element itself.
|
||||
* @param aMetrics The metrics to resolve SVG lengths against.
|
||||
* @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
|
||||
* paint. Only required if you will call Render().
|
||||
* @param aPaintTransform The transform to apply to convert to
|
||||
|
@ -137,6 +147,7 @@ public:
|
|||
nsIContent* aTargetContent,
|
||||
const UserSpaceMetrics& aMetrics,
|
||||
const nsTArray<nsStyleFilter>& aFilterChain,
|
||||
bool aFilterInputIsTainted,
|
||||
nsSVGFilterPaintCallback *aPaintCallback,
|
||||
const gfxMatrix& aPaintTransform,
|
||||
const nsRegion *aPostFilterDirtyRegion = nullptr,
|
||||
|
@ -236,18 +247,22 @@ private:
|
|||
/**
|
||||
* Build the list of FilterPrimitiveDescriptions that describes the filter's
|
||||
* 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,
|
||||
nsIFrame* aTargetFrame);
|
||||
nsIFrame* aTargetFrame,
|
||||
bool aFilterInputIsTainted);
|
||||
|
||||
/**
|
||||
* Add to the list of FilterPrimitiveDescriptions for a particular SVG
|
||||
* reference filter or CSS filter. This populates mPrimitiveDescrs and
|
||||
* mInputImages.
|
||||
* reference filter or CSS filter. This populates mPrimitiveDescriptions and
|
||||
* mInputImages. aInputIsTainted describes whether the input to aFilter is
|
||||
* tainted.
|
||||
*/
|
||||
nsresult BuildPrimitivesForFilter(const nsStyleFilter& aFilter,
|
||||
nsIFrame* aTargetFrame);
|
||||
nsIFrame* aTargetFrame,
|
||||
bool aInputIsTainted);
|
||||
|
||||
/**
|
||||
* Computes the filter space bounds of the areas that we actually *need* from
|
||||
|
|
|
@ -366,23 +366,10 @@ nsSVGFilterInstance::GetSourceIndices(nsSVGFE* aPrimitiveElement,
|
|||
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
|
||||
nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
|
||||
nsTArray<RefPtr<SourceSurface>>& aInputImages)
|
||||
nsTArray<RefPtr<SourceSurface>>& aInputImages,
|
||||
bool aInputIsTainted)
|
||||
{
|
||||
mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs);
|
||||
|
||||
|
@ -410,8 +397,6 @@ nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrim
|
|||
// The principal that we check principals of any loaded images against.
|
||||
nsCOMPtr<nsIPrincipal> principal = mTargetContent->NodePrincipal();
|
||||
|
||||
bool filterInputIsTainted = IsFilterInputTainted(mTargetContent);
|
||||
|
||||
for (uint32_t primitiveElementIndex = 0;
|
||||
primitiveElementIndex < primitives.Length();
|
||||
++primitiveElementIndex) {
|
||||
|
@ -427,7 +412,7 @@ nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrim
|
|||
ComputeFilterPrimitiveSubregion(filter, aPrimitiveDescrs, sourceIndices);
|
||||
|
||||
nsTArray<bool> sourcesAreTainted;
|
||||
GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, filterInputIsTainted, sourcesAreTainted);
|
||||
GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, aInputIsTainted, sourcesAreTainted);
|
||||
|
||||
FilterPrimitiveDescription descr =
|
||||
filter->GetPrimitiveDescription(this, primitiveSubregion, sourcesAreTainted, aInputImages);
|
||||
|
|
|
@ -98,9 +98,17 @@ public:
|
|||
* FilterPrimitiveDescription for each one. Appends the new
|
||||
* FilterPrimitiveDescription(s) to the aPrimitiveDescrs list. Also, appends
|
||||
* 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,
|
||||
nsTArray<RefPtr<SourceSurface>>& aInputImages);
|
||||
nsTArray<RefPtr<SourceSurface>>& aInputImages,
|
||||
bool aInputIsTainted);
|
||||
|
||||
/**
|
||||
* 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:
|
||||
implementation: generic-worker
|
||||
max-run-time: 7200
|
||||
max-run-time: 9000
|
||||
run:
|
||||
using: mozharness
|
||||
options: [enable-pgo]
|
||||
|
@ -112,7 +112,7 @@ win64/pgo:
|
|||
worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
|
||||
worker:
|
||||
implementation: generic-worker
|
||||
max-run-time: 7200
|
||||
max-run-time: 9000
|
||||
run:
|
||||
using: mozharness
|
||||
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')
|
||||
for option in run.get('options', []):
|
||||
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'] = [
|
||||
r'mkdir .\build\src',
|
||||
r'"{}" share c:\builds\hg-shared\mozilla-central .\build\src'.format(hg),
|
||||
r'"{}" pull -u -R .\build\src --rev %GECKO_HEAD_REV% %GECKO_HEAD_REPOSITORY%'.format(hg),
|
||||
' '.join(mh_command),
|
||||
' '.join(hg_command),
|
||||
' '.join(mh_command)
|
||||
]
|
||||
|
|
|
@ -36,12 +36,4 @@ def setup_task(config, tasks):
|
|||
'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
|
||||
|
|
|
@ -30,6 +30,10 @@ import urllib2
|
|||
|
||||
|
||||
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):
|
||||
|
@ -95,15 +99,17 @@ def vcs_checkout(source_repo, dest, base_repo=None, revision=None, branch=None):
|
|||
FINGERPRINT_URL)
|
||||
res = urllib2.urlopen(FINGERPRINT_URL, timeout=10)
|
||||
secret = res.read()
|
||||
except urllib2.URLError as e:
|
||||
print('error retrieving hg fingerprint: %s' % e)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
secret = json.loads(secret, encoding='utf-8')
|
||||
except ValueError:
|
||||
print('invalid JSON in hg fingerprint secret')
|
||||
sys.exit(1)
|
||||
try:
|
||||
secret = json.loads(secret, encoding='utf-8')
|
||||
except ValueError:
|
||||
print_line(b'vcs', 'invalid JSON in hg fingerprint secret')
|
||||
sys.exit(1)
|
||||
except urllib2.URLError:
|
||||
print_line(b'vcs', 'Unable to retrieve current hg.mozilla.org fingerprint'
|
||||
'using the secret service, using fallback instead.')
|
||||
# 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')
|
||||
|
||||
|
@ -193,6 +199,7 @@ def main(args):
|
|||
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
|
||||
|
||||
def set_dir_permissions(path, uid, gid):
|
||||
st = os.lstat(path)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import os
|
|||
config = {
|
||||
"locales_file": "src/browser/locales/all-locales",
|
||||
"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": {
|
||||
"NO_MERCURIAL_SETUP_CHECK": "1",
|
||||
"MOZ_OBJDIR": "obj-l10n",
|
||||
|
|
|
@ -2188,16 +2188,10 @@ TextInputHandler::InsertText(NSAttributedString* aAttrString,
|
|||
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);
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1906,6 +1906,21 @@ function* runKeyEventTests()
|
|||
yield testKey({layout:KEYBOARD_LAYOUT_DVORAK_QWERTY, keyCode:MAC_VK_ANSI_I,
|
||||
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);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
@ -4197,11 +4212,37 @@ function* runTextInputTests()
|
|||
textbox.value = "";
|
||||
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() {
|
||||
|
||||
var name = eventToString(aEvent);
|
||||
|
||||
is(textbox.value, aExpectText, name + " does not input correct text.");
|
||||
textbox.removeEventListener("keypress", onKeypress, true);
|
||||
if (aExpectText == "") {
|
||||
is(keypress, 1, name + " should cause one keypress event because it doesn't cause inputting text");
|
||||
} else {
|
||||
is(keypress, aExpectText.length, name + " should cause " + aExpectText.length + " keypress events");
|
||||
is(textbox.value, aExpectText, name + " does not input correct text.");
|
||||
}
|
||||
|
||||
continueTest();
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче