Merge m-c to m-i

This commit is contained in:
Phil Ringnalda 2016-09-05 18:11:14 -07:00
commit 69f7ad4a4b
38 changed files with 479 additions and 144 deletions

View File

@ -629,7 +629,7 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
// Destroy toolboxes for closed window
for (let [target, toolbox] of gDevTools._toolboxes) {
if (toolbox.frame && toolbox.frame.ownerDocument.defaultView == win) {
if (toolbox.win == win) {
toolbox.destroy();
}
}

View File

@ -201,7 +201,7 @@ function destroyToolbox(toolbox) {
toolbox.destroy().then(function () {
let target = TargetFactory.forTab(gBrowser.selectedTab);
ok(gDevTools._toolboxes.get(target) == null, "gDevTools doesn't know about target");
ok(toolbox._target == null, "toolbox doesn't know about target.");
ok(toolbox.target == null, "toolbox doesn't know about target.");
finishUp();
});
}

View File

@ -39,10 +39,8 @@ function checkToolLoading() {
}
function selectAndCheckById(id) {
let doc = toolbox.frame.contentDocument;
return toolbox.selectTool(id).then(function () {
let tab = doc.getElementById("toolbox-tab-" + id);
let tab = toolbox.doc.getElementById("toolbox-tab-" + id);
is(tab.hasAttribute("selected"), true, "The " + id + " tab is selected");
});
}

View File

@ -40,7 +40,7 @@ function toolRegistered(event, toolId)
ok(gDevTools.getToolDefinitionMap().has(toolId), "tool added to map");
// test that it appeared in the UI
let doc = toolbox.frame.contentDocument;
let doc = toolbox.doc;
let tab = doc.getElementById("toolbox-tab-" + toolId);
ok(tab, "new tool's tab exists in toolbox UI");
@ -82,7 +82,7 @@ function toolUnregistered(event, toolDefinition)
ok(!gDevTools.getToolDefinitionMap().has(toolId), "tool removed from map");
// test that it disappeared from the UI
let doc = toolbox.frame.contentDocument;
let doc = toolbox.doc;
let tab = doc.getElementById("toolbox-tab-" + toolId);
ok(!tab, "tool's tab was removed from the toolbox UI");

View File

@ -52,8 +52,9 @@ function sendZoomKey(shortcut, times) {
}
function getCurrentZoom() {
var contViewer = toolbox.frame.docShell.contentViewer;
return contViewer.fullZoom;
let windowUtils = toolbox.win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
return windowUtils.fullZoom;
}
function tidyUp() {

View File

@ -191,7 +191,7 @@ exports.getHighlighterUtils = function (toolbox) {
*/
function onPickerNodeCanceled() {
stopPicker();
toolbox.frame.focus();
toolbox.win.focus();
}
/**

View File

@ -12,8 +12,8 @@ let url = new window.URL(href);
if (url.search.length > 1) {
const Cu = Components.utils;
const Ci = Components.interfaces;
const { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const { gDevTools } = require("devtools/client/framework/devtools");
const { targetFromURL } = require("devtools/client/framework/target-from-url");
const { Toolbox } = require("devtools/client/framework/toolbox");
const { TargetFactory } = require("devtools/client/framework/target");
@ -66,18 +66,7 @@ if (url.search.length > 1) {
target = yield targetFromURL(url);
}
let options = { customIframe: host };
let toolbox = yield gDevTools.showToolbox(target, tool, Toolbox.HostType.CUSTOM, options);
// Watch for toolbox.xul unload in order to cleanup things when we close
// about:devtools-toolbox tabs
function onUnload() {
window.removeEventListener("unload", onUnload);
toolbox.destroy();
}
window.addEventListener("unload", onUnload);
toolbox.on("destroy", function () {
window.removeEventListener("unload", onUnload);
});
yield gDevTools.showToolbox(target, tool, Toolbox.HostType.CUSTOM, options);
}).catch(error => {
console.error("Exception while loading the toolbox", error);
});

View File

@ -108,6 +108,7 @@ const ToolboxButtons = exports.ToolboxButtons = [
*/
function Toolbox(target, selectedTool, hostType, hostOptions) {
this._target = target;
this._win = null;
this._toolPanels = new Map();
this._telemetry = new Telemetry();
if (Services.prefs.getBoolPref("devtools.sourcemap.locations.enabled")) {
@ -272,25 +273,18 @@ Toolbox.prototype = {
return this._host.type;
},
/**
* Get the iframe containing the toolbox UI.
*/
get frame() {
return this._host.frame;
},
/**
* Shortcut to the window containing the toolbox UI
*/
get win() {
return this.frame.contentWindow;
return this._win;
},
/**
* Shortcut to the document containing the toolbox UI
*/
get doc() {
return this.frame.contentDocument;
return this.win.document;
},
/**
@ -359,6 +353,8 @@ Toolbox.prototype = {
open: function () {
return Task.spawn(function* () {
let iframe = yield this._host.create();
this._win = iframe.contentWindow;
let domReady = defer();
// Prevent reloading the document when the toolbox is opened in a tab
@ -625,8 +621,17 @@ Toolbox.prototype = {
});
this.doc.addEventListener("keypress", this._splitConsoleOnKeypress, false);
this.doc.addEventListener("focus", this._onFocus, true);
this.win.addEventListener("unload", this.destroy);
},
_removeHostListeners: function () {
// The host iframe's contentDocument may already be gone.
if (this.doc) {
this.doc.removeEventListener("keypress", this._splitConsoleOnKeypress, false);
this.doc.removeEventListener("focus", this._onFocus, true);
this.win.removeEventListener("unload", this.destroy);
}
},
_registerOverlays: function () {
@ -1803,7 +1808,7 @@ Toolbox.prototype = {
// Handle the case where the previous host happens to match the current
// host. If so, switch to bottom if it's not already used, and side if not.
if (hostType === this._host.type) {
if (hostType === this.hostType) {
if (hostType === Toolbox.HostType.BOTTOM) {
hostType = Toolbox.HostType.SIDE;
} else {
@ -1822,7 +1827,7 @@ Toolbox.prototype = {
* The host type of the new host object
*/
switchHost: function (hostType) {
if (hostType == this._host.type || !this._target.isLocalTab) {
if (hostType == this.hostType || !this._target.isLocalTab) {
return null;
}
@ -1838,7 +1843,7 @@ Toolbox.prototype = {
return newHost.create().then(iframe => {
// change toolbox document's parent to the new host
iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
iframe.swapFrameLoaders(this.frame);
iframe.swapFrameLoaders(this._host.frame);
this._host.off("window-closed", this.destroy);
this.destroyHost();
@ -2035,12 +2040,7 @@ Toolbox.prototype = {
* @return {promise} to be resolved when the host is destroyed.
*/
destroyHost: function () {
// The host iframe's contentDocument may already be gone.
if (this.doc) {
this.doc.removeEventListener("keypress",
this._splitConsoleOnKeypress, false);
this.doc.removeEventListener("focus", this._onFocus, true);
}
this._removeHostListeners();
return this._host.destroy();
},
@ -2127,7 +2127,7 @@ Toolbox.prototype = {
this._threadClient = null;
// We need to grab a reference to win before this._host is destroyed.
let win = this.frame.ownerDocument.defaultView;
let win = this.win;
if (this._requisition) {
CommandUtils.destroyRequisition(this._requisition, this.target);
@ -2143,6 +2143,8 @@ Toolbox.prototype = {
.then(() => this.destroyHost())
.catch(console.error)
.then(() => {
this._win = null;
// Targets need to be notified that the toolbox is being torn down.
// This is done after other destruction tasks since it may tear down
// fronts and the debugger transport which earlier destroy methods may

View File

@ -75,7 +75,7 @@ const TOOLBOX_L10N = new LocalizationHelper("devtools/locale/toolbox.properties"
*/
function InspectorPanel(iframeWindow, toolbox) {
this._toolbox = toolbox;
this._target = toolbox._target;
this._target = toolbox.target;
this.panelDoc = iframeWindow.document;
this.panelWin = iframeWindow;
this.panelWin.inspector = this;

View File

@ -512,7 +512,7 @@ CssRuleView.prototype = {
_onAddRule: function () {
let elementStyle = this._elementStyle;
let element = elementStyle.element;
let client = this.inspector.toolbox._target.client;
let client = this.inspector.toolbox.target.client;
let pseudoClasses = element.pseudoClassLocks;
if (!client.traits.addNewRule) {

View File

@ -54,10 +54,6 @@
- tooltip for the button that toggles the promise debugger. -->
<!ENTITY debuggerUI.sources.togglePromiseDebugger "Toggle Promise Debugger">
<!-- LOCALIZATION NOTE (debuggerUI.startTracing): This is the text displayed in
- the button to start execution tracing. -->
<!ENTITY debuggerUI.startTracing "Start Tracing">
<!-- LOCALIZATION NOTE (debuggerUI.clearButton): This is the label for
- the button that clears the collected tracing data in the tracing tab. -->
<!ENTITY debuggerUI.clearButton "Clear">

View File

@ -30,14 +30,6 @@ pausePendingButtonTooltip=Waiting for next execution
# button when the debugger is in a paused state.
resumeButtonTooltip=Click to resume (%S)
# LOCALIZATION NOTE (startTracingTooltip): The label that is displayed on the trace
# button when execution tracing is stopped.
startTracingTooltip=Click to start tracing
# LOCALIZATION NOTE (stopTracingTooltip): The label that is displayed on the trace
# button when execution tracing is started.
stopTracingTooltip=Click to stop tracing
# LOCALIZATION NOTE (stepOverTooltip): The label that is displayed on the
# button that steps over a function call.
stepOverTooltip=Step Over (%S)
@ -50,10 +42,6 @@ stepInTooltip=Step In (%S)
# button that steps out of a function call.
stepOutTooltip=Step Out (%S)
# LOCALIZATION NOTE (emptyGlobalsText): The text to display in the menulist
# when there are no chrome globals available.
noGlobalsText=No globals
# LOCALIZATION NOTE (noWorkersText): The text to display in the workers list
# when there are no workers.
noWorkersText=This page has no workers.
@ -62,10 +50,6 @@ noWorkersText=This page has no workers.
# when there are no sources.
noSourcesText=This page has no sources.
# LOCALIZATION NOTE (loadingSourcesText): The text to display in the sources menu
# when waiting for scripts to load.
loadingSourcesText=Waiting for sources…
# LOCALIZATION NOTE (noEventListenersText): The text to display in the events tab
# when there are no events.
noEventListenersText=No event listeners to display
@ -74,14 +58,6 @@ noEventListenersText=No event listeners to display
# when there are no stack frames.
noStackFramesText=No stack frames to display
# LOCALIZATION NOTE (noStackFramesText): The text to display in the traces tab
# when there are no function calls.
noFunctionCallsText=No function calls to display
# LOCALIZATION NOTE (tracingNotStartedText): The text to display in the traces tab
# when when tracing hasn't started yet.
tracingNotStartedText=Tracing has not started
# LOCALIZATION NOTE (eventCheckboxTooltip): The tooltip text to display when
# the user hovers over the checkbox used to toggle an event breakpoint.
eventCheckboxTooltip=Toggle breaking on this event
@ -140,11 +116,6 @@ noMatchingStringsText=No matches found
# filter text box when it is empty and the scripts container is selected.
emptySearchText=Search scripts (%S)
# LOCALIZATION NOTE (emptyChromeGlobalsFilterText): This is the text that
# appears in the filter text box when it is empty and the chrome globals
# container is selected.
emptyChromeGlobalsFilterText=Filter chrome globals (%S)
# LOCALIZATION NOTE (emptyVariablesFilterText): This is the text that
# appears in the filter text box for the variables view container.
emptyVariablesFilterText=Filter variables

View File

@ -9,12 +9,6 @@
# A good criteria is the language in which you'd find the best documentation
# on web development on the web.
# LOCALIZATION NOTE (storage.tooltip3):
# This string is displayed in the tooltip of the tab when the storage editor is
# displayed inside the developer tools window.
# A keyboard shortcut for Storage Inspector will be shown inside the brackets.
storage.tooltip3=Storage Inspector (Cookies, Local Storage, …) (%S)
# LOCALIZATION NOTE (storage.filter.key):
# Key shortcut used to focus the filter box on top of the data view
storage.filter.key=CmdOrCtrl+F

View File

@ -7,7 +7,7 @@ var toolbox;
add_task(function* () {
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = yield gDevTools.showToolbox(target);
let doc = toolbox.frame.contentDocument;
let doc = toolbox.doc;
let root = doc.documentElement;
let platform = root.getAttribute("platform");

View File

@ -31,7 +31,7 @@ function CssDocsTooltip(toolbox) {
this.widget.on("visitlink", this._onVisitLink);
// Initialize keyboard shortcuts
this.shortcuts = new KeyShortcuts({ window: toolbox.doc.defaultView });
this.shortcuts = new KeyShortcuts({ window: toolbox.win });
this._onShortcut = this._onShortcut.bind(this);
this.shortcuts.on("Escape", this._onShortcut);

View File

@ -65,7 +65,7 @@ add_task(function* () {
is(toolbox.getCurrentPanel(), scratchpadPanel,
"Clicking link switches to Scratchpad panel");
is(Services.ww.activeWindow, toolbox.frame.ownerGlobal,
is(Services.ww.activeWindow, toolbox.win.parent,
"Scratchpad's toolbox is focused");
Tools.scratchpad.isTargetSupported = isTargetSupported;

View File

@ -40,14 +40,17 @@ window.onload = function() {
hasActor: () => useActor,
client: attachmentA.client,
form: attachmentA.tab
}
},
// Fake the window for css-properties.js's getClientBrowserVersion to work
win: window
};
const toolboxMockB = {
target: {
hasActor: () => useActor,
client: attachmentB.client,
form: attachmentB.tab
}
},
win: window
};
yield initCssProperties(toolboxMockA);

View File

@ -40,15 +40,7 @@ window.onload = function() {
client: attachmentA.client,
form: attachmentA.tab,
},
_host: { frame: { contentWindow: window } }
};
const toolboxUnknownVersions = {
target: {
hasActor: () => true,
client: attachmentB.client,
form: attachmentB.tab
}
// Don't add a host here.
win: window
};
const toolboxDifferentVersions = {
target: {
@ -56,9 +48,9 @@ window.onload = function() {
client: attachmentB.client,
form: attachmentB.tab
},
_host: { frame: { contentWindow: { navigator: { userAgent:
win: { navigator: { userAgent:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:49.0) Gecko/20100101 " +
"Firefox/30.0" }}}}
"Firefox/30.0" }}
};
// Modify a property on the static database, to differentiate between a generated
@ -66,17 +58,13 @@ window.onload = function() {
CSS_PROPERTIES_DB.properties.color.isStatic = true;
yield initCssProperties(toolboxMatchingVersions);
yield initCssProperties(toolboxUnknownVersions);
yield initCssProperties(toolboxDifferentVersions);
const cssPropertiesMatching = getCssProperties(toolboxMatchingVersions);
const cssPropertiesUnknown = getCssProperties(toolboxUnknownVersions);
const cssPropertiesDifferent = getCssProperties(toolboxDifferentVersions);
is(cssPropertiesMatching.properties.color.isStatic, true,
"The static CSS database is used when the client and platform versions match.");
is(cssPropertiesUnknown.properties.color.isStatic, true,
"The static CSS database is used when the client is not a known Firefox.");
isnot(cssPropertiesDifferent.properties.color.isStatic, undefined,
"The generated CSS database is used when the client and platform versions do " +
"not match, but the client is a Firefox.");

View File

@ -192,10 +192,7 @@ function getClientCssProperties() {
* @returns {string} The browser version.
*/
function getClientBrowserVersion(toolbox) {
if (!toolbox._host) {
return "0";
}
const regexResult = toolbox._host.frame.contentWindow.navigator
const regexResult = toolbox.win.navigator
.userAgent.match(/Firefox\/(\d+)\.\d/);
return Array.isArray(regexResult) ? regexResult[1] : "0";
}

View File

@ -12,6 +12,7 @@
#include "mozilla/dom/KeyframeEffectBinding.h"
#include "mozilla/KeyframeUtils.h"
#include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
#include "nsIScriptError.h"
namespace mozilla {
namespace dom {
@ -125,5 +126,59 @@ KeyframeEffect::SetTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget)
}
}
void
KeyframeEffect::SetSpacing(JSContext* aCx,
const nsAString& aSpacing,
ErrorResult& aRv)
{
SpacingMode spacingMode = SpacingMode::distribute;
nsCSSPropertyID pacedProperty = eCSSProperty_UNKNOWN;
nsAutoString invalidPacedProperty;
KeyframeEffectParams::ParseSpacing(aSpacing,
spacingMode,
pacedProperty,
invalidPacedProperty,
aRv);
if (aRv.Failed()) {
return;
}
if (!invalidPacedProperty.IsEmpty()) {
const char16_t* params[] = { invalidPacedProperty.get() };
nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aCx);
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("Animation"),
doc,
nsContentUtils::eDOM_PROPERTIES,
"UnanimatablePacedProperty",
params, ArrayLength(params));
}
if (mEffectOptions.mSpacingMode == spacingMode &&
mEffectOptions.mPacedProperty == pacedProperty) {
return;
}
mEffectOptions.mSpacingMode = spacingMode;
mEffectOptions.mPacedProperty = pacedProperty;
// Apply spacing. We apply distribute here. If the new spacing is paced,
// UpdateProperties() will apply it.
if (mEffectOptions.mSpacingMode == SpacingMode::distribute) {
KeyframeUtils::ApplyDistributeSpacing(mKeyframes);
}
if (mAnimation && mAnimation->IsRelevant()) {
nsNodeUtils::AnimationChanged(mAnimation);
}
if (mTarget) {
RefPtr<nsStyleContext> styleContext = GetTargetStyleContext();
if (styleContext) {
UpdateProperties(styleContext);
}
}
}
} // namespace dom
} // namespace mozilla

View File

@ -65,6 +65,8 @@ public:
// that to update the properties rather than calling
// GetStyleContextForElement.
void SetTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget);
void SetSpacing(JSContext* aCx, const nsAString& aSpacing, ErrorResult& aRv);
};
} // namespace dom

View File

@ -1896,6 +1896,73 @@ addAsyncAnimTest("set_effect_with_previous_animation",
yield await_frame();
});
addAsyncAnimTest("set_spacing",
{ observe: div, subtree: true }, function*() {
var anim = div.animate([ { marginLeft: "0px" },
{ marginLeft: "-20px" },
{ marginLeft: "100px" },
{ marginLeft: "50px" } ],
{ duration: 100 * MS_PER_SEC });
yield await_frame();
assert_records([{ added: [anim], changed: [], removed: [] }],
"records after animation is added");
anim.effect.spacing = "paced(margin-left)";
yield await_frame();
assert_records([{ added: [], changed: [anim], removed: [] }],
"records after animation is changed");
anim.cancel();
yield await_frame();
assert_records([{ added: [], changed: [], removed: [anim] }],
"records after animation end");
});
addAsyncAnimTest("set_spacing_on_a_non-animatable_property",
{ observe: div, subtree: true }, function*() {
var anim = div.animate([ { marginLeft: "0px" },
{ marginLeft: "-20px" },
{ marginLeft: "100px" },
{ marginLeft: "50px" } ],
{ duration: 100 * MS_PER_SEC,
spacing: "paced(margin-left)" });
yield await_frame();
assert_records([{ added: [anim], changed: [], removed: [] }],
"records after animation is added");
anim.effect.spacing = "paced(animation-duration)";
yield await_frame();
assert_records([{ added: [], changed: [anim], removed: [] }],
"records after setting a non-animatable paced property");
anim.cancel();
yield await_frame();
assert_records([{ added: [], changed: [], removed: [anim] }],
"records after animation end");
});
addAsyncAnimTest("set_the_same_spacing",
{ observe: div, subtree: true }, function*() {
var anim = div.animate([ { marginLeft: "0px" },
{ marginLeft: "-20px" },
{ marginLeft: "100px" },
{ marginLeft: "50px" } ],
{ duration: 100 * MS_PER_SEC,
spacing: "paced(margin-left)" });
yield await_frame();
assert_records([{ added: [anim], changed: [], removed: [] }],
"records after animation is added");
anim.effect.spacing = "paced(margin-left)";
yield await_frame();
assert_records([], "no record after setting the same spacing");
anim.cancel();
yield await_frame();
assert_records([{ added: [], changed: [], removed: [anim] }],
"records after animation end");
});
// Run the tests.
SimpleTest.requestLongerTimeout(2);
SimpleTest.waitForExplicitFinish();

View File

@ -48,6 +48,7 @@ support-files =
style/file_animation-seeking-with-current-time.html
style/file_animation-seeking-with-start-time.html
style/file_animation-setting-effect.html
style/file_animation-setting-spacing.html
testcommon.js
[css-animations/test_animations-dynamic-changes.html]
@ -101,3 +102,4 @@ skip-if = (toolkit == 'gonk' && debug)
[style/test_animation-seeking-with-current-time.html]
[style/test_animation-seeking-with-start-time.html]
[style/test_animation-setting-effect.html]
[style/test_animation-setting-spacing.html]

View File

@ -0,0 +1,111 @@
<!doctype html>
<head>
<meta charset=utf-8>
<title>Tests for setting spacing by using KeyframeEffect.spacing</title>
<script src='../testcommon.js'></script>
</head>
<body>
<script>
'use strict';
function calculateInterpolation(pacedDistances, values, progress) {
if (progress == 0.0) {
return values[0];
} else if (progress == 1.0) {
return values[valus.length - 1];
}
const cumDist = pacedDistances.reduce( (prev, curr) => {
prev.push(prev.length == 0 ? curr : curr + prev[prev.length - 1]);
return prev;
}, []);
const last = cumDist[cumDist.length - 1];
const offsets = cumDist.map( (curr) => { return curr / last; } );
let idx = 0;
for (let i = 0; i < offsets.length - 1; ++i) {
if (progress >= offsets[i] && progress < offsets[i + 1]) {
idx = i;
break;
}
}
const ratio = (progress - offsets[idx]) / (offsets[idx + 1] - offsets[idx]);
return values[idx] + ratio * (values[idx + 1] - values[idx]) + 'px';
}
promise_test(function(t) {
var target = addDiv(t);
var anim = target.animate([ { marginLeft: '0px' },
{ marginLeft: '-20px' },
{ marginLeft: '100px' },
{ marginLeft: '50px' } ],
100 * MS_PER_SEC);
return anim.ready.then(function() {
anim.currentTime = 50 * MS_PER_SEC;
assert_equals(getComputedStyle(target).marginLeft, '40px',
'computed value before setting a new spacing');
var dist = [0, 20, 120, 50];
var marginLeftValues = [0, -20, 100, 50];
anim.effect.spacing = 'paced(margin-left)';
assert_equals(getComputedStyle(target).marginLeft,
calculateInterpolation(dist, marginLeftValues, 0.5),
'computed value after setting a new spacing');
});
}, 'Test for setting spacing from distribute to paced');
promise_test(function(t) {
var target = addDiv(t);
var anim = target.animate([ { marginLeft: '0px' },
{ marginLeft: '-20px' },
{ marginLeft: '100px' },
{ marginLeft: '50px' } ],
{ duration: 100 * MS_PER_SEC,
spacing: 'paced(margin-left)' });
return anim.ready.then(function() {
var dist = [0, 20, 120, 50];
var marginLeftValues = [0, -20, 100, 50];
anim.currentTime = 50 * MS_PER_SEC;
assert_equals(getComputedStyle(target).marginLeft,
calculateInterpolation(dist, marginLeftValues, 0.5),
'computed value before setting a new spacing');
anim.effect.spacing = 'distribute';
assert_equals(getComputedStyle(target).marginLeft, '40px',
'computed value after setting a new spacing');
});
}, 'Test for setting spacing from paced to distribute');
promise_test(function(t) {
var target = addDiv(t);
var anim =
target.animate([ { marginLeft: '0px', borderRadius: '0%' },
{ marginLeft: '-20px', borderRadius: '50%' },
{ marginLeft: '100px', borderRadius: '25%' },
{ marginLeft: '50px', borderRadius: '100%' } ],
{ duration: 100 * MS_PER_SEC,
spacing: 'paced(margin-left)' });
return anim.ready.then(function() {
var dist = [0, 20, 120, 50];
var marginLeftValues = [0, -20, 100, 50];
anim.currentTime = 50 * MS_PER_SEC;
assert_equals(getComputedStyle(target).marginLeft,
calculateInterpolation(dist, marginLeftValues, 0.5),
'computed value before setting a new spacing');
dist = [0, 50, 25, 75];
anim.effect.spacing = 'paced(border-radius)';
assert_equals(getComputedStyle(target).marginLeft,
calculateInterpolation(dist, marginLeftValues, 0.5),
'computed value after setting a new spacing');
});
}, 'Test for setting spacing from paced to a different paced');
done();
</script>
</body>

View File

@ -0,0 +1,14 @@
<!doctype html>
<meta charset=utf-8>
<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>
<div id='log'></div>
<script>
'use strict';
setup({explicit_done: true});
SpecialPowers.pushPrefEnv(
{ 'set': [['dom.animations-api.core.enabled', true]]},
function() {
window.open('file_animation-setting-spacing.html');
});
</script>

View File

@ -624,6 +624,10 @@ DOMInterfaces = {
'concrete': False
},
'KeyframeEffect': {
'implicitJSContext': { 'setterOnly': [ 'spacing' ] }
},
'LegacyMozTCPSocket': {
'headerFile': 'TCPSocket.h',
'wrapperCache': False,

View File

@ -73,8 +73,8 @@ interface KeyframeEffect : KeyframeEffectReadOnly {
// inherit attribute IterationCompositeOperation iterationComposite;
// Bug 1216844 - implement additive animation
// inherit attribute CompositeOperation composite;
// Bug 1244590 - implement spacing modes
// inherit attribute DOMString spacing;
[SetterThrows]
inherit attribute DOMString spacing;
[Throws]
void setKeyframes (object? keyframes);
};

View File

@ -6513,10 +6513,7 @@ nsCSSFrameConstructor::IsValidSibling(nsIFrame* aSibling,
StyleDisplay& aDisplay)
{
nsIFrame* parentFrame = aSibling->GetParent();
nsIAtom* parentType = nullptr;
if (parentFrame) {
parentType = parentFrame->GetType();
}
nsIAtom* parentType = parentFrame->GetType();
StyleDisplay siblingDisplay = aSibling->GetDisplay();
if (StyleDisplay::TableColumnGroup == siblingDisplay ||

View File

@ -3036,6 +3036,30 @@ nsCSSRendering::PaintGradient(nsPresContext* aPresContext,
}
}
static CompositionOp
DetermineCompositionOp(const nsCSSRendering::PaintBGParams& aParams,
const nsStyleImageLayers& aLayers,
uint32_t aLayerIndex)
{
if (aParams.layer >= 0) {
// When drawing a single layer, use the specified composition op.
return aParams.compositionOp;
}
const nsStyleImageLayers::Layer& layer = aLayers.mLayers[aLayerIndex];
// When drawing all layers, get the compositon op from each image layer.
if (aParams.paintFlags & nsCSSRendering::PAINTBG_MASK_IMAGE) {
// Always using OP_OVER mode while drawing the bottom mask layer.
if (aLayerIndex == (aLayers.mImageCount - 1)) {
return CompositionOp::OP_OVER;
}
return nsCSSRendering::GetGFXCompositeMode(layer.mComposite);
}
return nsCSSRendering::GetGFXBlendMode(layer.mBlendMode);
}
DrawResult
nsCSSRendering::PaintBackgroundWithSC(const PaintBGParams& aParams,
nsStyleContext *aBackgroundSC,
@ -3226,21 +3250,14 @@ nsCSSRendering::PaintBackgroundWithSC(const PaintBGParams& aParams,
}
if ((aParams.layer < 0 || i == (uint32_t)startLayer) &&
!clipState.mDirtyRectGfx.IsEmpty()) {
// When we're drawing a single layer, use the specified composition op,
// otherwise get the compositon op from the image layer.
CompositionOp co = (aParams.layer >= 0) ? aParams.compositionOp :
(paintMask ? GetGFXCompositeMode(layer.mComposite) :
GetGFXBlendMode(layer.mBlendMode));
CompositionOp co = DetermineCompositionOp(aParams, layers, i);
nsBackgroundLayerState state =
PrepareImageLayer(&aParams.presCtx, aParams.frame,
aParams.paintFlags, paintBorderArea, clipState.mBGClipArea,
layer, nullptr, co);
layer, nullptr);
result &= state.mImageRenderer.PrepareResult();
if (!state.mFillArea.IsEmpty()) {
// Always using OP_OVER mode while drawing the bottom mask layer.
bool isBottomMaskLayer = paintMask ?
(i == (layers.mImageCount - 1)) : false;
if (co != CompositionOp::OP_OVER && !isBottomMaskLayer) {
if (co != CompositionOp::OP_OVER) {
NS_ASSERTION(ctx->CurrentOp() == CompositionOp::OP_OVER,
"It is assumed the initial op is OP_OVER, when it is restored later");
ctx->SetOp(co);
@ -3512,8 +3529,7 @@ nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext,
const nsRect& aBorderArea,
const nsRect& aBGClipRect,
const nsStyleImageLayers::Layer& aLayer,
bool* aOutIsTransformedFixed,
CompositionOp aCompositonOp)
bool* aOutIsTransformedFixed)
{
/*
* The properties we need to keep in mind when drawing style image

View File

@ -551,8 +551,7 @@ struct nsCSSRendering {
const nsRect& aBorderArea,
const nsRect& aBGClipRect,
const nsStyleImageLayers::Layer& aLayer,
bool* aOutIsTransformedFixed = nullptr,
CompositionOp aCompositionOp = CompositionOp::OP_OVER);
bool* aOutIsTransformedFixed = nullptr);
struct ImageLayerClipState {
nsRect mBGClipArea; // Affected by mClippedRadii

View File

@ -3356,10 +3356,7 @@ public class BrowserApp extends GeckoApp
String url = tab.getURL();
if (AboutPages.isAboutReader(url)) {
String urlFromReader = ReaderModeUtils.getUrlFromAboutReader(url);
if (urlFromReader != null) {
url = urlFromReader;
}
url = ReaderModeUtils.stripAboutReaderUrl(url);
}
// Disable share menuitem for about:, chrome:, file:, and resource: URIs
@ -3538,9 +3535,7 @@ public class BrowserApp extends GeckoApp
if (tab != null) {
String url = tab.getURL();
if (url != null) {
if (AboutPages.isAboutReader(url)) {
url = ReaderModeUtils.getUrlFromAboutReader(url);
}
url = ReaderModeUtils.stripAboutReaderUrl(url);
// Context: Sharing via chrome list (no explicit session is active)
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "menu");

View File

@ -745,16 +745,15 @@ public class Tabs implements GeckoEventListener {
return null;
}
if (AboutPages.isAboutReader(url)) {
url = ReaderModeUtils.getUrlFromAboutReader(url);
}
url = ReaderModeUtils.stripAboutReaderUrl(url);
for (Tab tab : mOrder) {
if (isPrivate != tab.isPrivate()) {
continue;
}
String tabUrl = tab.getURL();
if (AboutPages.isAboutReader(tabUrl)) {
tabUrl = ReaderModeUtils.getUrlFromAboutReader(tabUrl);
tabUrl = ReaderModeUtils.stripAboutReaderUrl(tabUrl);
if (url.equals(tabUrl)) {
return tab;
}

View File

@ -20,7 +20,7 @@ public class ReaderModeUtils {
* URLs.
* @return <code>null</code> if the URL is malformed or doesn't contain a URL parameter.
*/
public static String getUrlFromAboutReader(String aboutReaderUrl) {
private static String getUrlFromAboutReader(String aboutReaderUrl) {
return StringUtils.getQueryParameter(aboutReaderUrl, "url");
}
@ -45,6 +45,12 @@ public class ReaderModeUtils {
return getAboutReaderForUrl(url, -1);
}
/**
* Obtain the underlying URL from an about:reader URL.
* This will return the input URL if either of the following is true:
* 1. the input URL is a non about:reader URL
* 2. the input URL is an invalid/unparseable about:reader URL
*/
public static String stripAboutReaderUrl(String url) {
if (!AboutPages.isAboutReader(url)) {
return url;

View File

@ -37856,6 +37856,12 @@
"url": "/web-animations/interfaces/Animation/effect.html"
}
],
"web-animations/interfaces/KeyframeEffect/spacing.html": [
{
"path": "web-animations/interfaces/KeyframeEffect/spacing.html",
"url": "/web-animations/interfaces/KeyframeEffect/spacing.html"
}
],
"web-animations/timing-model/animation-effects/phases-and-states.html": [
{
"path": "web-animations/timing-model/animation-effects/phases-and-states.html",

View File

@ -322,7 +322,70 @@ test(function(t) {
'and falling back to distribute spacing for the reset with some specific ' +
'offsets');
// Bug 1276193: Test for mixing percent and pixel values.
// Tests for setting spacing by KeyframeEffect.spacing.
test(function(t) {
var anim = createDiv(t).animate([ { marginLeft: '0px' },
{ marginLeft: '-20px' },
{ marginLeft: '100px' },
{ marginLeft: '50px' } ],
{ duration: 100 * MS_PER_SEC });
anim.effect.spacing = 'paced(margin-left)';
var frames = anim.effect.getKeyframes();
var cumDist = [0, 20, 140, 190];
assert_equals(frames[0].computedOffset, 0.0,
'1st frame offset');
assert_equals(frames[1].computedOffset, cumDist[1] / cumDist[3],
'2nd frame offset');
assert_equals(frames[2].computedOffset, cumDist[2] / cumDist[3],
'3rd frame offset');
assert_equals(frames[3].computedOffset, 1.0,
'last frame offset');
}, 'Test paced spacing by setter');
test(function(t) {
var anim = createDiv(t).animate([ { marginLeft: '0px' },
{ marginLeft: '-20px' },
{ marginLeft: '100px' },
{ marginLeft: '50px' } ],
{ duration: 100 * MS_PER_SEC,
spacing: 'paced(margin-left)' });
anim.effect.spacing = 'distribute';
var frames = anim.effect.getKeyframes();
var slots = frames.length - 1;
assert_equals(frames[0].computedOffset, 0.0, '1st frame offset');
assert_equals(frames[1].computedOffset, 1.0 / slots, '2nd frame offset');
assert_equals(frames[2].computedOffset, 2.0 / slots, '3rd frame offset');
assert_equals(frames[3].computedOffset, 1.0, 'last frame offset');
}, 'Test distribute spacing by setter');
test(function(t) {
var anim =
createDiv(t).animate([ { marginLeft: '0px', borderRadius: '0%' },
{ marginLeft: '-20px', borderRadius: '50%' },
{ marginLeft: '100px', borderRadius: '25%' },
{ marginLeft: '50px', borderRadius: '100%' } ],
{ duration: 100 * MS_PER_SEC,
spacing: 'paced(margin-left)' });
anim.effect.spacing = 'paced(border-radius)';
var frames = anim.effect.getKeyframes();
var cumDist = [0, 50, 50 + 25, 50 + 25 + 75];
assert_equals(frames[0].computedOffset, 0.0,
'1st frame offset');
assert_equals(frames[1].computedOffset, cumDist[1] / cumDist[3],
'2nd frame offset');
assert_equals(frames[2].computedOffset, cumDist[2] / cumDist[3],
'3rd frame offset');
assert_equals(frames[3].computedOffset, 1.0,
'last frame offset');
}, 'Test paced spacing by changing the paced property');
</script>
</body>

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>KeyframeEffect spacing attribute tests</title>
<link rel="help"
href="https://w3c.github.io/web-animations/#dom-keyframeeffect-spacing">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../testcommon.js"></script>
<body>
<div id="log"></div>
<script>
"use strict";
test(function(t) {
var anim = createDiv(t).animate(null);
assert_throws(new TypeError, function() {
anim.effect.spacing = '';
});
}, 'Test throwing TypeError if using empty string');
test(function(t) {
var anim = createDiv(t).animate(null);
assert_throws(new TypeError, function() {
anim.effect.spacing = 'dist';
});
}, 'Test throwing TypeError if not using the correct keyword');
test(function(t) {
var anim = createDiv(t).animate(null);
anim.effect.spacing = 'paced(A)';
assert_equals(anim.effect.spacing, 'distribute', 'spacing mode');
}, 'Test falling back to distribute spacing if using a unrecognized property');
test(function(t) {
var anim = createDiv(t).animate(null);
anim.effect.spacing = 'paced(--bg-color)';
assert_equals(anim.effect.spacing, 'distribute', 'spacing mode');
}, 'Test falling back to distribute spacing if using CSS variables');
test(function(t) {
var anim = createDiv(t).animate(null);
anim.effect.spacing = 'paced(animation-duration)';
assert_equals(anim.effect.spacing, 'distribute', 'spacing mode');
}, 'Test falling back to distribute spacing if using a non-animatable ' +
'property');
test(function(t) {
var anim = createDiv(t).animate(null);
anim.effect.spacing = 'distribute';
assert_equals(anim.effect.spacing, 'distribute', 'spacing mode');
}, 'Test spacing value if setting distribute');
test(function(t) {
var anim = createDiv(t).animate(null);
anim.effect.spacing = 'paced(transform)';
assert_equals(anim.effect.spacing, 'paced(transform)', 'spacing mode');
}, 'Test spacing value if setting paced');
</script>
</body>

View File

@ -50,7 +50,7 @@ SpecialPowers.pushPrefEnv(
["urlclassifier.phishTable", "test-phish-simple"]]},
function() {
classifierHelper.waitForInit()
.then(() => { classifierHelper.addUrlToDB(testData); })
.then(() => classifierHelper.addUrlToDB(testData))
.then(updateSuccess)
.catch(err => {
updateError(err);

View File

@ -57,7 +57,7 @@ SpecialPowers.pushPrefEnv(
["urlclassifier.phishTable", "test-phish-simple"]]},
function() {
classifierHelper.waitForInit()
.then(() => { classifierHelper.addUrlToDB(testData); })
.then(() => classifierHelper.addUrlToDB(testData))
.then(updateSuccess)
.catch(err => {
updateError(err);